From 19bf69e63d2aca899e6f00fa6adcb010e2a5cfde Mon Sep 17 00:00:00 2001 From: lds Date: Tue, 19 Nov 2024 14:29:58 +0100 Subject: [PATCH] Modron 1.0. See changelog for more details --- .cloudignore | 342 + .dockerignore | 4 + .git-cc.yaml | 11 + .gitignore | 11 +- .golangci.yml | 26 +- CHANGELOG.md | 65 + README.md | 400 +- buf.gen.yaml | 30 + buf.lock | 9 + buf.yaml | 10 + cloudbuild-ui.yaml | 12 + cloudbuild.yaml | 12 + docker-compose.dev.yaml | 53 + docker-compose.ui.yaml | 34 +- docker-compose.yaml | 36 +- docker/Dockerfile.buf | 51 + docs/FINDINGS.md | 165 + docs/RISK_SCORE.md | 120 + docs/pics/architecture.svg | 534 + docs/pics/modron.svg | 75 + docs/pics/risk_score_matrix.png | Bin 0 -> 51408 bytes docs/pics/severities.png | Bin 0 -> 20307 bytes docs/screenshots/home.jpg | Bin 0 -> 257739 bytes docs/screenshots/nagatha-1.jpg | Bin 0 -> 75675 bytes docs/screenshots/nagatha-2.jpg | Bin 0 -> 53488 bytes docs/screenshots/observations.jpg | Bin 0 -> 311413 bytes docs/screenshots/risk-score.jpg | Bin 0 -> 294728 bytes docs/screenshots/single-observation.jpg | Bin 0 -> 267793 bytes docs/screenshots/stats.jpg | Bin 0 -> 130064 bytes env.example | 25 + go.mod | 2 +- go.sum | 0 otel/config/config.yaml | 34 + src/.gitignore | 1 + src/Dockerfile | 20 +- src/Dockerfile.e2e | 21 +- src/README.md | 29 +- src/acl/fakeacl/checkerFake.go | 16 +- src/acl/gcpacl/cache.go | 55 + src/acl/gcpacl/cache_test.go | 80 + src/acl/gcpacl/checker.go | 129 +- src/acl/gcpacl/checker_real_test.go | 37 + src/acl/gcpacl/checker_test.go | 45 +- src/collector/collector.go | 18 + src/collector/gcpcollector/api_fake.go | 1295 + src/collector/gcpcollector/api_gcp.go | 608 + src/collector/gcpcollector/api_key.go | 52 + src/collector/gcpcollector/bucket.go | 17 +- src/collector/gcpcollector/cloudsql.go | 87 +- src/collector/gcpcollector/collector.go | 367 + src/collector/gcpcollector/collector_call.go | 52 + .../gcpcollector/collector_generic.go | 43 + .../collector_integration_test.go | 94 + .../gcpcollector/collector_rg_call.go | 43 + src/collector/gcpcollector/collector_test.go | 482 + .../gcpcollector/kubernetes_cluster.go | 202 + .../gcpcollector/kubernetes_cluster_test.go | 106 + src/collector/gcpcollector/load_balancer.go | 325 + src/collector/gcpcollector/metrics.go | 26 + src/collector/gcpcollector/network.go | 93 +- src/collector/gcpcollector/network_test.go | 119 + src/collector/gcpcollector/ratelimiter.go | 62 + src/collector/gcpcollector/resource_group.go | 241 + .../gcpcollector/resource_group_test.go | 31 + src/collector/gcpcollector/scc_findings.go | 233 + .../gcpcollector/scc_findings_test.go | 223 + src/collector/gcpcollector/service_account.go | 153 + .../gcpcollector/service_account_test.go | 30 + src/collector/gcpcollector/spanner.go | 13 +- src/collector/gcpcollector/vm_instance.go | 43 + src/collector/testcollector/testcollector.go | 56 + src/common/protoutils.go | 55 +- src/constants/constants.go | 79 +- src/constants/context.go | 8 + src/constants/gcp_sa_projects.go | 254 + src/engine/framework.go | 56 +- src/engine/framework_test.go | 4 +- src/engine/rules/api_key_overbroad_scope.go | 35 +- .../rules/api_key_overbroad_scope_test.go | 69 +- src/engine/rules/bucket_is_public.go | 17 +- src/engine/rules/bucket_is_public_test.go | 41 +- .../rules/cluster_nodes_have_public_ips.go | 16 +- .../cluster_nodes_have_public_ips_test.go | 24 +- src/engine/rules/common.go | 25 +- src/engine/rules/container_running.go | 114 + src/engine/rules/container_running_test.go | 81 + .../rules/cross_environment_permission.go | 112 + src/engine/rules/cross_project_permissions.go | 203 +- .../rules/cross_project_permissions_test.go | 332 +- ...database_allows_unencrypted_connections.go | 15 +- ...ase_allows_unencrypted_connections_test.go | 51 +- ...atabase_authorized_network_not_set_test.go | 53 +- .../database_authorized_networks_not_set.go | 15 +- .../rules/exported_key_expiry_too_long.go | 15 +- .../exported_key_expiry_too_long_test.go | 49 +- .../exported_key_with_admin_privileges.go | 20 +- ...exported_key_with_admin_privileges_test.go | 109 +- .../human_with_overprivileged_basic_role.go | 95 + ...man_with_overprivileged_basic_role_test.go | 154 + src/engine/rules/iap_disabled.go | 74 + src/engine/rules/iap_disabled_test.go | 79 + .../kubernetes_vulnerability_scanning.go | 75 + ...ernetes_vulnerability_scanning_e2e_test.go | 19 + .../kubernetes_vulnerability_scanning_test.go | 89 + src/engine/rules/lb_tls_cert_expiring_soon.go | 87 + .../rules/lb_tls_cert_expiring_soon_test.go | 106 + .../rules/lb_tls_min_version_too_old.go | 41 +- .../rules/lb_tls_min_version_too_old_test.go | 57 +- src/engine/rules/lb_user_managed_cert.go | 18 +- src/engine/rules/lb_user_managed_cert_test.go | 131 +- .../master_authorized_neworks_not_set.go | 16 +- .../master_authorized_neworks_not_set_test.go | 47 +- .../rules/outdated_kubernetes_version.go | 23 +- .../rules/outdated_kubernetes_version_test.go | 51 +- .../rules/private_google_access_disabled.go | 17 +- .../private_google_access_disabled_test.go | 49 +- src/engine/rules/registry.go | 10 +- src/engine/rules/registry_test.go | 8 +- .../rules/svc_account_too_high_privileges.go | 21 +- .../svc_account_too_high_privileges_test.go | 188 +- src/engine/rules/testing.go | 189 +- src/engine/rules/testing_e2e.go | 61 + .../rules/unused_exported_credentials.go | 47 +- .../rules/unused_exported_credentials_test.go | 49 +- src/engine/rules/vm_has_public_ip.go | 14 +- src/engine/rules/vm_has_public_ip_test.go | 47 +- src/engine/runner.go | 316 +- src/engine/runner_integration_test.go | 66 + src/engine/runner_test.go | 474 +- src/go.mod | 183 +- src/go.sum | 854 + src/log.go | 91 + src/lognotifier/lognotifier.go | 47 +- src/main.go | 277 + src/metric/keys.go | 13 + src/metric/status.go | 10 + src/model/acl.go | 7 +- src/model/collector.go | 17 +- src/model/engine.go | 20 + src/model/exception.go | 12 +- src/model/model.go | 30 +- src/model/stateManager.go | 14 +- src/model/storage.go | 5 +- src/nagatha/convert.go | 14 +- src/nagatha/nagatha.go | 128 +- src/nagatha/notification.go | 39 + src/nagatha/notification_test.go | 43 + src/nagatha/proto/nagatha.proto | 167 + src/nagatha/rest.go | 240 +- src/nagatha/rest_test.go | 163 + src/proto/.gitignore | 3 + src/proto/generated/go.mod | 33 + src/proto/generated/go.sum | 129 + src/proto/modron.proto | 470 +- src/proto/notification.proto | 2 +- src/risk/risk.go | 206 + src/server.go | 512 +- src/service/mock_notifier_test.go | 70 + src/service/service.go | 694 + src/service/service_test.go | 527 + .../requestDependenciesStateManager.go | 78 +- .../requestDependenciesStateManager_test.go | 140 +- src/storage/gormstorage/gorm.go | 598 + .../gormstorage/gorm_integration_test.go | 65 + src/storage/gormstorage/gorm_test.go | 32 + src/storage/gormstorage/impact.go | 31 + src/storage/gormstorage/model.go | 133 + .../gormstorage/observation_category.go | 31 + src/storage/gormstorage/observation_source.go | 31 + src/storage/gormstorage/operation.go | 40 + src/storage/gormstorage/severity.go | 59 + src/storage/memstorage/memstorage.go | 309 +- src/storage/storage.go | 8 + src/storage/test/test.go | 344 +- src/storage/utils/utils.go | 19 + src/test/e2e_test.go | 142 +- src/test/fake_notification_service.go | 2 +- src/test/go.mod | 37 +- src/test/go.sum | 125 + src/ui/.gitignore | 1 + src/ui/.yarnrc.yml | 1 + src/ui/Dockerfile | 23 +- src/ui/README.md | 41 +- src/ui/client/.dockerignore | 6 + src/ui/client/.eslintignore | 1 + src/ui/client/Dockerfile | 4 +- src/ui/client/Dockerfile.e2e | 6 +- src/ui/client/angular.json | 8 +- src/ui/client/cypress/.gitignore | 2 + src/ui/client/cypress/e2e/spec.cy.ts | 34 +- src/ui/client/package-lock.json | 20469 ++++++++++++++++ src/ui/client/package.json | 60 +- src/ui/client/src/.gitignore | 2 + src/ui/client/src/app/app-routing.module.ts | 5 + src/ui/client/src/app/app.module.ts | 74 +- src/ui/client/src/app/filter.pipe.ts | 62 +- .../impact-indicator.component.html | 3 + .../impact-indicator.component.scss | 0 .../impact-indicator.component.ts | 30 + src/ui/client/src/app/model/modron.model.ts | 7 +- .../app/modron-app/modron-app.component.html | 53 +- .../app/modron-app/modron-app.component.scss | 105 +- .../app/modron-app/modron-app.component.ts | 21 +- src/ui/client/src/app/modron.service.ts | 67 +- .../notif-bell-button.component.html | 36 + .../notif-bell-button.component.scss | 0 .../notif-bell-button.component.ts | 71 + ...notification-exception-form.component.html | 3 +- ...ification-exception-form.component.spec.ts | 39 +- src/ui/client/src/app/notification.service.ts | 6 +- ...tion-details-dialog-content.component.html | 100 + ...tion-details-dialog-content.component.scss | 69 + ...vation-details-dialog-content.component.ts | 15 + ...servation-details-dialog-content.filter.ts | 21 + .../observation-details-dialog.component.html | 5 + .../observation-details-dialog.component.scss | 3 + .../observation-details-dialog.component.ts | 17 + .../observation-details.component.html | 188 +- .../observation-details.component.scss | 30 +- .../observation-details.component.spec.ts | 6 + .../observation-details.component.ts | 143 +- .../observations-stats.component.html | 5 + .../observations-stats.component.scss | 0 .../observations-stats.component.spec.ts | 22 + .../observations-stats.component.ts | 44 + .../observations-table.component.html | 104 + .../observations-table.component.scss | 49 + .../observations-table.component.ts | 97 + .../resource-group-details.component.html | 56 +- .../resource-group-details.component.scss | 80 +- .../resource-group-details.component.ts | 103 +- .../resource-group-details.pipe.ts | 4 +- .../resource-group.component.html | 88 +- .../resource-group.component.scss | 83 + .../resource-group.component.ts | 15 +- .../app/resource-group/resource-group.pipe.ts | 9 + .../resource-groups.component.html | 121 +- .../resource-groups.component.scss | 104 +- .../resource-groups.component.spec.ts | 18 +- .../resource-groups.component.ts | 11 +- .../resource-groups/resource-groups.pipe.ts | 17 + .../severity-indicator.component.html | 22 + .../severity-indicator.component.scss | 47 + .../severity-indicator.component.ts | 36 + .../severity-indicator.pipe.ts | 48 + .../src/app/sidenav/sidenav.component.html | 29 + .../src/app/sidenav/sidenav.component.scss | 64 + .../src/app/sidenav/sidenav.component.spec.ts | 47 + .../src/app/sidenav/sidenav.component.ts | 23 + src/ui/client/src/app/state/modron.store.ts | 40 +- .../client/src/app/stats/stats.component.html | 181 +- .../client/src/app/stats/stats.component.scss | 50 +- .../client/src/app/stats/stats.component.ts | 67 +- .../src/app/ui-demo/ui-demo.component.html | 48 + .../src/app/ui-demo/ui-demo.component.scss | 21 + .../src/app/ui-demo/ui-demo.component.ts | 32 + src/ui/client/src/assets/modron-white.svg | 42 + src/ui/client/src/assets/modron.svg | 42 + src/ui/client/src/colors.scss | 23 + src/ui/client/src/main.ts | 2 +- src/ui/client/src/styles.scss | 24 +- src/ui/client/tsconfig.app.json | 1 + src/ui/docker-compose.yml | 17 + src/ui/go.mod | 42 +- src/ui/go.sum | 202 + src/ui/mock-grpc-server/.dockerignore | 1 + src/ui/mock-grpc-server/Dockerfile | 6 + src/ui/mock-grpc-server/copy-proto.sh | 9 + src/ui/mock-grpc-server/envoy.yaml | 2 +- src/ui/mock-grpc-server/package-lock.json | 915 + src/ui/mock-grpc-server/package.json | 2 +- src/ui/mock-grpc-server/proto/.gitkeep | 0 src/ui/mock-grpc-server/server.ts | 91 +- src/ui/package-lock.json | 321 + src/ui/package.json | 21 +- src/ui/server.go | 1 + src/utils/gcp.go | 101 + src/utils/gke.go | 33 + src/utils/gke_test.go | 31 + src/utils/groups.go | 15 + src/utils/hierarchy.go | 72 + src/utils/keys.go | 44 + src/utils/keys_test.go | 69 + src/utils/name.go | 26 + src/utils/name_test.go | 62 + src/utils/protobuf.go | 40 + src/utils/protobuf_test.go | 104 + src/utils/ref.go | 9 + src/utils/resource_ref.go | 15 + src/utils/rule.go | 17 + src/utils/service_account.go | 7 + src/utils/service_account_test.go | 35 + src/validation.go | 119 + terraform/dev/main.tf.example | 8 +- terraform/modron/artifact_registry.tf | 23 + terraform/modron/cloud_run.tf | 367 +- terraform/modron/cloud_sql.tf | 128 + terraform/modron/gitlab.tf | 42 + terraform/modron/load_balancer.tf | 2 + terraform/modron/main.tf | 6 +- terraform/modron/network.tf | 10 +- terraform/modron/otel/README.md | 3 + terraform/modron/otel/config.yaml | 55 + terraform/modron/project.tf | 21 + terraform/modron/secret.tf | 2 +- terraform/modron/service_account.tf | 38 + terraform/modron/tracing.tf | 35 + terraform/modron/variables.tf | 84 +- terraform/prod/main.tf.example | 8 +- utils/gcp_service_agents/.gitignore | 1 + utils/gcp_service_agents/README.md | 15 + utils/gcp_service_agents/go.mod | 17 + utils/gcp_service_agents/go.sum | 72 + utils/gcp_service_agents/main.go | 166 + 314 files changed, 43091 insertions(+), 3619 deletions(-) create mode 100644 .cloudignore create mode 100644 .dockerignore create mode 100644 .git-cc.yaml create mode 100644 CHANGELOG.md create mode 100644 buf.gen.yaml create mode 100644 buf.lock create mode 100644 buf.yaml create mode 100644 cloudbuild-ui.yaml create mode 100644 cloudbuild.yaml create mode 100644 docker-compose.dev.yaml create mode 100644 docker/Dockerfile.buf create mode 100644 docs/FINDINGS.md create mode 100644 docs/RISK_SCORE.md create mode 100644 docs/pics/architecture.svg create mode 100644 docs/pics/modron.svg create mode 100644 docs/pics/risk_score_matrix.png create mode 100644 docs/pics/severities.png create mode 100644 docs/screenshots/home.jpg create mode 100644 docs/screenshots/nagatha-1.jpg create mode 100644 docs/screenshots/nagatha-2.jpg create mode 100644 docs/screenshots/observations.jpg create mode 100644 docs/screenshots/risk-score.jpg create mode 100644 docs/screenshots/single-observation.jpg create mode 100644 docs/screenshots/stats.jpg create mode 100644 env.example create mode 100644 go.sum create mode 100644 otel/config/config.yaml create mode 100644 src/.gitignore create mode 100644 src/acl/gcpacl/cache.go create mode 100644 src/acl/gcpacl/cache_test.go create mode 100644 src/acl/gcpacl/checker_real_test.go create mode 100644 src/collector/collector.go create mode 100644 src/collector/gcpcollector/api_fake.go create mode 100644 src/collector/gcpcollector/api_gcp.go create mode 100644 src/collector/gcpcollector/api_key.go create mode 100644 src/collector/gcpcollector/collector.go create mode 100644 src/collector/gcpcollector/collector_call.go create mode 100644 src/collector/gcpcollector/collector_generic.go create mode 100644 src/collector/gcpcollector/collector_integration_test.go create mode 100644 src/collector/gcpcollector/collector_rg_call.go create mode 100644 src/collector/gcpcollector/collector_test.go create mode 100644 src/collector/gcpcollector/kubernetes_cluster.go create mode 100644 src/collector/gcpcollector/kubernetes_cluster_test.go create mode 100644 src/collector/gcpcollector/load_balancer.go create mode 100644 src/collector/gcpcollector/metrics.go create mode 100644 src/collector/gcpcollector/network_test.go create mode 100644 src/collector/gcpcollector/ratelimiter.go create mode 100644 src/collector/gcpcollector/resource_group.go create mode 100644 src/collector/gcpcollector/resource_group_test.go create mode 100644 src/collector/gcpcollector/scc_findings.go create mode 100644 src/collector/gcpcollector/scc_findings_test.go create mode 100644 src/collector/gcpcollector/service_account.go create mode 100644 src/collector/gcpcollector/service_account_test.go create mode 100644 src/collector/gcpcollector/vm_instance.go create mode 100644 src/collector/testcollector/testcollector.go create mode 100644 src/constants/context.go create mode 100644 src/constants/gcp_sa_projects.go create mode 100644 src/engine/rules/container_running.go create mode 100644 src/engine/rules/container_running_test.go create mode 100644 src/engine/rules/cross_environment_permission.go create mode 100644 src/engine/rules/human_with_overprivileged_basic_role.go create mode 100644 src/engine/rules/human_with_overprivileged_basic_role_test.go create mode 100644 src/engine/rules/iap_disabled.go create mode 100644 src/engine/rules/iap_disabled_test.go create mode 100644 src/engine/rules/kubernetes_vulnerability_scanning.go create mode 100644 src/engine/rules/kubernetes_vulnerability_scanning_e2e_test.go create mode 100644 src/engine/rules/kubernetes_vulnerability_scanning_test.go create mode 100644 src/engine/rules/lb_tls_cert_expiring_soon.go create mode 100644 src/engine/rules/lb_tls_cert_expiring_soon_test.go create mode 100644 src/engine/rules/testing_e2e.go create mode 100644 src/engine/runner_integration_test.go create mode 100644 src/go.sum create mode 100644 src/log.go create mode 100644 src/main.go create mode 100644 src/metric/keys.go create mode 100644 src/metric/status.go create mode 100644 src/model/engine.go create mode 100644 src/nagatha/notification.go create mode 100644 src/nagatha/notification_test.go create mode 100644 src/nagatha/proto/nagatha.proto create mode 100644 src/nagatha/rest_test.go create mode 100644 src/proto/.gitignore create mode 100644 src/proto/generated/go.mod create mode 100644 src/proto/generated/go.sum create mode 100644 src/risk/risk.go create mode 100644 src/service/mock_notifier_test.go create mode 100644 src/service/service.go create mode 100644 src/service/service_test.go create mode 100644 src/storage/gormstorage/gorm.go create mode 100644 src/storage/gormstorage/gorm_integration_test.go create mode 100644 src/storage/gormstorage/gorm_test.go create mode 100644 src/storage/gormstorage/impact.go create mode 100644 src/storage/gormstorage/model.go create mode 100644 src/storage/gormstorage/observation_category.go create mode 100644 src/storage/gormstorage/observation_source.go create mode 100644 src/storage/gormstorage/operation.go create mode 100644 src/storage/gormstorage/severity.go create mode 100644 src/storage/storage.go create mode 100644 src/storage/utils/utils.go create mode 100644 src/test/go.sum create mode 100644 src/ui/.gitignore create mode 100644 src/ui/.yarnrc.yml create mode 100644 src/ui/client/.dockerignore create mode 100644 src/ui/client/.eslintignore create mode 100644 src/ui/client/cypress/.gitignore create mode 100644 src/ui/client/package-lock.json create mode 100644 src/ui/client/src/.gitignore create mode 100644 src/ui/client/src/app/impact-indicator/impact-indicator.component.html create mode 100644 src/ui/client/src/app/impact-indicator/impact-indicator.component.scss create mode 100644 src/ui/client/src/app/impact-indicator/impact-indicator.component.ts create mode 100644 src/ui/client/src/app/notif-bell-button/notif-bell-button.component.html create mode 100644 src/ui/client/src/app/notif-bell-button/notif-bell-button.component.scss create mode 100644 src/ui/client/src/app/notif-bell-button/notif-bell-button.component.ts create mode 100644 src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.component.html create mode 100644 src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.component.scss create mode 100644 src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.component.ts create mode 100644 src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.filter.ts create mode 100644 src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.html create mode 100644 src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.scss create mode 100644 src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.ts create mode 100644 src/ui/client/src/app/observations-stats/observations-stats.component.html create mode 100644 src/ui/client/src/app/observations-stats/observations-stats.component.scss create mode 100644 src/ui/client/src/app/observations-stats/observations-stats.component.spec.ts create mode 100644 src/ui/client/src/app/observations-stats/observations-stats.component.ts create mode 100644 src/ui/client/src/app/observations-table/observations-table.component.html create mode 100644 src/ui/client/src/app/observations-table/observations-table.component.scss create mode 100644 src/ui/client/src/app/observations-table/observations-table.component.ts create mode 100644 src/ui/client/src/app/resource-group/resource-group.pipe.ts create mode 100644 src/ui/client/src/app/severity-indicator/severity-indicator.component.html create mode 100644 src/ui/client/src/app/severity-indicator/severity-indicator.component.scss create mode 100644 src/ui/client/src/app/severity-indicator/severity-indicator.component.ts create mode 100644 src/ui/client/src/app/severity-indicator/severity-indicator.pipe.ts create mode 100644 src/ui/client/src/app/sidenav/sidenav.component.html create mode 100644 src/ui/client/src/app/sidenav/sidenav.component.scss create mode 100644 src/ui/client/src/app/sidenav/sidenav.component.spec.ts create mode 100644 src/ui/client/src/app/sidenav/sidenav.component.ts create mode 100644 src/ui/client/src/app/ui-demo/ui-demo.component.html create mode 100644 src/ui/client/src/app/ui-demo/ui-demo.component.scss create mode 100644 src/ui/client/src/app/ui-demo/ui-demo.component.ts create mode 100644 src/ui/client/src/assets/modron-white.svg create mode 100644 src/ui/client/src/assets/modron.svg create mode 100644 src/ui/client/src/colors.scss create mode 100644 src/ui/docker-compose.yml create mode 100644 src/ui/go.sum create mode 100644 src/ui/mock-grpc-server/.dockerignore create mode 100644 src/ui/mock-grpc-server/Dockerfile create mode 100755 src/ui/mock-grpc-server/copy-proto.sh create mode 100644 src/ui/mock-grpc-server/package-lock.json create mode 100644 src/ui/mock-grpc-server/proto/.gitkeep create mode 100644 src/ui/package-lock.json create mode 100644 src/utils/gcp.go create mode 100644 src/utils/gke.go create mode 100644 src/utils/gke_test.go create mode 100644 src/utils/groups.go create mode 100644 src/utils/hierarchy.go create mode 100644 src/utils/keys.go create mode 100644 src/utils/keys_test.go create mode 100644 src/utils/name.go create mode 100644 src/utils/name_test.go create mode 100644 src/utils/protobuf.go create mode 100644 src/utils/protobuf_test.go create mode 100644 src/utils/ref.go create mode 100644 src/utils/resource_ref.go create mode 100644 src/utils/rule.go create mode 100644 src/utils/service_account.go create mode 100644 src/utils/service_account_test.go create mode 100644 src/validation.go create mode 100644 terraform/modron/artifact_registry.tf create mode 100644 terraform/modron/cloud_sql.tf create mode 100644 terraform/modron/gitlab.tf create mode 100644 terraform/modron/otel/README.md create mode 100644 terraform/modron/otel/config.yaml create mode 100644 terraform/modron/project.tf create mode 100644 terraform/modron/tracing.tf create mode 100644 utils/gcp_service_agents/.gitignore create mode 100644 utils/gcp_service_agents/README.md create mode 100644 utils/gcp_service_agents/go.mod create mode 100644 utils/gcp_service_agents/go.sum create mode 100644 utils/gcp_service_agents/main.go diff --git a/.cloudignore b/.cloudignore new file mode 100644 index 0000000..bc4dd39 --- /dev/null +++ b/.cloudignore @@ -0,0 +1,342 @@ +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,go,macos,terraform,node,angular,react +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,go,macos,terraform,node,angular,react + +secrets/ + +### Angular ### +## Angular ## +# compiled output +dist/ +tmp/ +app/**/*.js +app/**/*.js.map + +# dependencies +node_modules/ +bower_components/ + +# IDEs and editors +.idea/ + +# misc +.sass-cache/ +connect.lock/ +coverage/ +libpeerconnection.log/ +npm-debug.log +testem.log +typings/ +.angular/ + +# e2e +e2e/*.js +e2e/*.map + +# System Files +.DS_Store/ + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### Go Patch ### +/vendor/ +/Godeps/ + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local +.env.fake + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### react ### +.DS_* +**/*.backup.* +**/*.back.* + +node_modules + +*.sublime* + +psd +thumb +sketch + +### Terraform ### +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Support for Project snippet scope +.vscode/*.code-snippets + +# Ignore code-workspaces +*.code-workspace + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,go,macos,terraform,node,angular,react + +# readd removed values +# Snyk cache. +.dccache +# terraform plan +tf.plan +.vscode/launch.json +src/storage/main.go +gitlab_sa.key + +# Cypress +cypress/screenshots +cypress/videos + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Test certificates +src/test/certs/* + +# Nix Shell +/shell.nix + +# Linter output +modron-lint.xml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ca809ac --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +**/node_modules +**/.angular +/.git +/terraform \ No newline at end of file diff --git a/.git-cc.yaml b/.git-cc.yaml new file mode 100644 index 0000000..4c48cde --- /dev/null +++ b/.git-cc.yaml @@ -0,0 +1,11 @@ +scopes: + collector: Collector changes + docker: Docker related changes + nagatha: Nagatha related changes + otel: OpenTelemetry related changes + rules: Changes to the rules + scc: Security Command Center related changes + server: Changes to the Modron backend + storage: Changes to the storage layer + terraform: Terraform related changes + ui: UI changes diff --git a/.gitignore b/.gitignore index 68188c5..bc4dd39 100644 --- a/.gitignore +++ b/.gitignore @@ -175,6 +175,7 @@ web_modules/ .env.test.local .env.production.local .env.local +.env.fake # parcel-bundler cache (https://parceljs.org/) .cache @@ -308,7 +309,6 @@ terraform.rc .dccache # terraform plan tf.plan -src/storage/bigquerystorage/bqsa_key.json .vscode/launch.json src/storage/main.go gitlab_sa.key @@ -331,3 +331,12 @@ cypress/videos *.launch .settings/ *.sublime-workspace + +# Test certificates +src/test/certs/* + +# Nix Shell +/shell.nix + +# Linter output +modron-lint.xml diff --git a/.golangci.yml b/.golangci.yml index 1c67b1c..2ea66a9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,18 +1,34 @@ linters: disable-all: true enable: - - deadcode + - contextcheck - errcheck + - errorlint + - gocritic + - mnd + - gosec - gosimple - govet - ineffassign + - misspell + - nestif + - nilerr + - nilnil + - revive - staticcheck - - structcheck - typecheck - unused - - varcheck + - wastedassign +issues: + exclude-dirs: + - "terraform" run: timeout: 5m - skip-dirs: - - "terraform" + +output: + formats: + - format: colored-line-number + path: stdout + - format: junit-xml + path: modron-lint.xml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..66916f6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,65 @@ +# Changelog + +## v1.0.0 + +### Structure + +- Support Resource Group Hierarchy + +### Observations + +- Add [Risk Score](docs/RISK_SCORE.md) to observations, calculated from the severity of the observation (as defined in the rule) and the impact of the observation (detected from the environment) +- Collectors can now collect observations + +### Stats + +- Improved stats view +- Improved export to CSV + +### GCP + +- Add support for [Security Command Center (SCC)](https://cloud.google.com/security-command-center/docs/concepts-security-command-center-overview) +- Start collecting Kubernetes resources + +### Storage + +- Use [GORM](https://gorm.io/) for both the PSQL and SQLite storage backends +- Use SQLite for the in-memory database for testing + +### Performance + +- Increase performance overall by optimizing the DB queries, parallelizing the scans, and reducing the number of external calls +- Introduce rate limiting for the collectors + +### Observability + +- Use [logrus](https://github.com/sirupsen/logrus) with structured logging for GCP Logging (Stackdriver) +- Add support for OpenTelemetry + - Add an otel-collector to receive traces and metrics + - Send traces to [Google Cloud Trace](https://cloud.google.com/trace) + - Send metrics to [Google Cloud Monitoring](https://cloud.google.com/monitoring) + +### UI + +- Completely rework the UI with an improved design +- Show observations as a table, sorted by Risk Score by default +- Add a detailed view dialog for the observations + +### Misc + +- Use [`go-arg`](https://github.com/alexflint/go-arg) for the CLI arguments / environment variables +- Switch to [buf](https://buf.build/) for the protobuf generation +- Bug fixes +- Upgrade to Go 1.23 +- Rules now support external configuration + +## v0.2 + +- Moved to go 1.19 +- Added automated runs for scans +- Fixed issue where last reported observation would still appear even if newer scans reported no observations +- Fixed group member ship resolution when checking for accesses to GCP projects + +## v0.1 + +- Initial public release diff --git a/README.md b/README.md index 2155a0d..069e592 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,143 @@ # Modron - Cloud security compliance -![Modron logo](images/modron.png) +

+Modron Logo +

-``` -” -We are the ultimate law. All other law is tainted when compared to us. -We are order. All other order disappears when held to our light. -We are structure. All other structure crumbles when brought against us. -We are perfect law. -” -— A spokesmodron -``` +> _We are the ultimate law. All other law is tainted when compared to us. +> We are order. All other order disappears when held to our light. +> We are structure. All other structure crumbles when brought against us. +> We are perfect law._ +> +> — A spokesmodron + +Monte Cook, Colin McComb (1997-10-28). The Great Modron March. Edited by Michele Carter. (TSR, Inc.), p. 26. ISBN +0-7869-0648-0. -Monte Cook, Colin McComb (1997-10-28). The Great Modron March. Edited by Michele Carter. (TSR, Inc.), p. 26. ISBN 0-7869-0648-0. -The rise of cloud computing has sharply increased the number of resources that need to be managed in a production environment. This has increased the load on security teams. At the same time, vulnerability and compliance scanning on the cloud have made little progress. The process of inventory, data collection, analysis and remediation have scaled up, but did not evolve to manage the scale and diversity of cloud computing assets. Numerous security tools still assume that maintaining inventory, collecting data, looking at results and fixing issues is performed by the same person. This leads to increased pressure on teams already overwhelmed by the size of their infrastructure. +## Introduction -Maintaining a secure cloud infrastructure is surprisingly hard. Cloud computing came with the promise of automation and ease of use, yet there is a lot of progress to be made on both of these fronts. Infrastructure security also suffered from the explosion of assets under management and lack of security controls on new and existing assets. +Modron is a cloud security compliance tool. It is designed to help organizations manage their cloud infrastructure +and ensure that it is compliant with their security policies. -Modron addresses the inventory and ownership issues raising with large cloud infrastructure, as well as the scalability of the remediation process by resolving ownership of assets and handling communication with different asset owners. -Modron still has the security practitioners and leadership teams in mind and provides organization wide statistics about the reported issues. +Users can navigate the Modron UI and view their resource groups, together with the respective observations. +Resource Groups that require attention are immediately visible and users can dig deeper to assess the observations. -Designed with multi cloud and scalability in mind, Modron is based on GCP today. The model allows for writing detection rules once and apply them across multiple platforms. +Resource Group Observations -## Taxonomy +A detailed explanation of why Modron was created can be read on [the original blog post](https://nianticlabs.com/news/modron). + +## Problem Statement + +The rise of cloud computing has sharply increased the number of resources that need to be managed in a production +environment. This has increased the load on security teams. At the same time, vulnerability and compliance scanning on +the cloud have made little progress. The process of inventory, data collection, analysis and remediation have scaled up, +but did not evolve to manage the scale and diversity of cloud computing assets. Numerous security tools still assume +that maintaining inventory, collecting data, looking at results and fixing issues is performed by the same person. This +leads to increased pressure on teams already overwhelmed by the size of their infrastructure. + +Maintaining a secure cloud infrastructure is surprisingly hard. Cloud computing came with the promise of automation and +ease of use, yet there is a lot of progress to be made on both of these fronts. Infrastructure security also suffered +from the explosion of assets under management and lack of security controls on new and existing assets. + +Modron addresses the inventory and ownership issues raising with large cloud infrastructure, as well as the scalability +of the remediation process by resolving ownership of assets and handling communication with different asset owners. +Modron still has the security practitioners and leadership teams in mind and provides organization wide statistics about +the reported issues. + +Designed with multi cloud and scalability in mind, Modron is based on GCP today. The model allows for writing detection +rules once and apply them across multiple platforms. + +## The Modron solution + +With the help of Modron, organizations can: +- Automatically collect data from their cloud infrastructure +- Run security rules against the collected data +- Notify the owners of the resources that are not compliant with the security rules +- Provide a personalized dashboard to visualize the compliance status of the organization +- Provide engineers information on how to remediate the issues + +### Analyzing the Resource Group observations + +Through the Modron UI, users can view a list of their resource groups. By clicking on a resource group, they can +see a list of observations that have been made against that resource group: + +Resource Group Observations + +This view provides a list of observations for the resource group. Each observation has an associated "Risk Score" that +is computed taking into consideration the severity of the rule that generated the observation and the environment in +which the resource group is running. This allows users to prioritize their remediation efforts. + +### Expanding a single observation + +By expanding a single observation, users can see more details about it - including remediation steps. + +Single Observation + +When available, a command is provided to enable the user to quickly remediate the issue. + +### Risk Score + +Each observation has an associated Risk Score. This score is computed based on the severity of the rule that generated the +observation (Severity) and the environment in which the resource group is running (Impact). + +Risk Scores range from *INFO* to *CRITICAL* (slightly adapted from the [CVSS v3.x Ratings](https://nvd.nist.gov/vuln-metrics/cvss)). +By also analyzing the impact of the observation, the risk score can be used to prioritize remediation efforts: an +observation in an environment containing customer data (e.g: production) will be considered more critical than the same +finding in an environment containing only test data (e.g: dev). + +Risk Score computation -A *Resource* is an entity existing in the cloud platform. A resource can be a VM instance, a service account, a kubernetes clusters, etc. +The details on how we compute and define the Risk Score are available in the [Risk Score documentation](docs/RISK_SCORE.md). -A *Resource group* is the smallest administrative grouping of resources, usually administered by the same individual or group of individuals. On GCP this corresponds to a Project, on Azure to a Resource Group. +### Statistics -A *Rule* is the implementation of a desired state for a given resource or set of resources. A rule applies only to a predefined set of resources, compares the state of the resource with the expected state. If these states differ, the rule generates one or more *observation*. +Via the statistics page, users can view a list of all the rules that have been run against their resource groups, +together with the results. Each rule can be exported to a CSV file for further analysis. -An *Observation* is an instance of a difference between the state of a resource and its expected state at a given timestamp. +Statistics -A *Collection* defines the action of fetching all data from the cloud platforms. This data is then stored in the database, ready to run the scan. +By expanding the "List of the observations", users can see all the matching observations for that rule, regardless of +the resource group they belong to. +This tool can be used by security teams to understand the impact of a rule across the organization, and which +issues to tackle first. -A *Scan* defines the action of running a set of *rules* against a set of *resource groups*. *Observations* resulting of that scan are added to the database. There is no guarantee that all observations of the same scan will have the same timestamp. +### Notifications -*Nagatha* is the notification system associated with Modron. It aggregates notifications going to the same recipient over a given time frame and sends a notification to that user. +A remediation cannot be effective if the right people are not informed. +Modron sends notifications to the owners of the resource groups that have observations via [Nagatha](https://github.com/nianticlabs/nagatha). -A *Notification* is an instance of a message sent to an owner of a *resource group* for a given *observation*. +Users will receive periodically a notification with the list of observations that they need to address. Nagatha +will take care of delivering the notification via Slack and Email: -An *Exception* is one owner of a *resource group* opting out of *notifications* for a specific *rule*. Exceptions *must* have an expiration date and cannot be set forever. This limitation can be bypassed by accessing the nagatha service directly. + + + + + + + + + +
+ Notification + + Notification +

Email Notification

Slack Notification

+ +## Taxonomy + +| Term | Definition | +|:---------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Resource | An entity existing in the cloud platform. A resource can be a VM instance, a service account, a kubernetes clusters, etc. | +| Resource group | The smallest administrative grouping of resources, usually administered by the same individual or group of individuals. On GCP this corresponds to a Project, on Azure to a Resource Group. | +| Rule | The implementation of a desired state for a given resource or set of resources. A rule applies only to a predefined set of resources, compares the state of the resource with the expected state. If these states differ, the rule generates one or more *observation*s. | +| Observation | Instance of a difference between the state of a resource and its expected state at a given timestamp. | +| Collection | The action of fetching all data from the cloud platforms. This data is then stored in the database, ready to run the scan. | +| Scan | The action of running a set of *rules* against a set of *resource groups*. *Observations* resulting of that scan are added to the database. There is no guarantee that all observations of the same scan will have the same timestamp. | +| Nagatha | The notification system associated with Modron. It aggregates notifications going to the same recipient over a given time frame and sends a notification to that user. | +| Notification | An instance of a message sent to an owner of a *resource group* for a given *observation*. | +| Exception | An owner of a *resource group* opting out of *notifications* for a specific *rule*. Exceptions *must* have an expiration date and cannot be set forever. This limitation can be bypassed by accessing the Nagaatha service directly. | ## Process @@ -50,90 +146,178 @@ Modron follows the process of any security scanning engine: ![Scanning process](images/scan_process.png) Except that in most scanning engines, the inventory and remediation parts are left as an exercise for the user. -In Modron, inventory is taken care of by identifying automatically the owners of a resource group based on the people that have the permission to act on it, as the remediation is largely facilitated by running the communication with the different resource group owners. - -* *Collector*: The collector fetches the data from the cloud platforms. This code must be implemented for each supported code platform separately. It takes care of the inventory and data collection parts of the process. -* *Rule engine*: The rule engine runs the rules against all collected resources and generates observations. Notifications are sent to Nagatha for each observation. -* *Nagatha* receives all the notifications for all observations, aggregates, deduplicates and limits the rate of notification. It also applies the exceptions provided by the user. +In Modron, inventory is taken care of by identifying automatically the owners of a resource group based on the people +that have the permission to act on it, as the remediation is largely facilitated by running the communication with the +different resource group owners. + +* *Collector*: The collector fetches the data from the cloud platforms. This code must be implemented for each supported + code platform separately. It takes care of the inventory and data collection parts of the process. +* *Rule engine*: The rule engine runs the rules against all collected resources and generates observations. + Notifications are sent to Nagatha for each observation. +* *Nagatha* receives all the notifications for all observations, aggregates, deduplicates and limits the rate of + notification. It also applies the exceptions provided by the user. + +## Architecture + +### GCP + +

+ +

+ +### Modron + +```mermaid +flowchart LR + User --> ui + ui[Modron UI] + ui --> Modron + subgraph Modron + coll[Collector] + acl[ACL Fetcher] + re[Rule Engine] + re -->|Check rule| re + end + + re -->|Create Observation| psql + coll --->|fetch| gcp + acl --->|fetch| gcp + coll --->|store| psql + gcp[GCP APIs] + psql[(PSQL)] + re --->|Fetch resources| psql + re --->|Create Notification| Nagatha + slack(Slack) + email(Email) + Nagatha --> slack + Nagatha --> email +``` ## Getting started In order to install Modron & Nagatha, you'll need to: -1. Build the modron images: - * in [src/](src): `gcloud builds builds submit . --tag gcr.io/your-project/modron:prod --timeout 900` - * in [src/ui/](src/ui): `gcloud builds builds submit . --tag gcr.io/your-project/modron-ui:prod --timeout 900` - * in [nagatha/](nagatha): `gcloud builds submit . --tag gcr.io/your-project/nagatha:dev --timeout=900` +1. Build the modron images following [Building the images](#building-the-images) below. 1. Create a copy of [main.tf.example](terraform/dev/main.tf.example) and edit it with your own configuration 1. Run `tf plan --out tf.plan` in the [dev folder](terraform/dev/) * This could need multiple occurrences as setting up resources on GCP takes time. -1. Create a copy of [tf.tfvars.json.example](nagatha/terraform/tf.tfvars.json.example) and edit it with your own configuration +1. Create a copy of [tf.tfvars.json.example](nagatha/terraform/tf.tfvars.json.example) and edit it with your own + configuration 1. Run `tf plan --out tf.plan` in the [nagatha folder](nagatha/terraform/) 1. Assign the permissions to the Modron runner as mentioned in [permissions](#permissions) -## Logo +### Building the images -Generated with Dall-E with "logo art of a victorian cubical robot in a tuxedo with a top hat and holding binoculars" +This assumes you have an Artifact registry in your GCP project following this format: +``` +us-central1-docker.pkg.dev/$PROJECT_ID/modron +``` -## Start developing on Modron +#### Modron Backend -### Infrastructure +The Modron backend image can be built using [Cloud Build](https://cloud.google.com/build/docs): +```bash +gcloud \ + --project "$PROJECT_ID" \ + builds submit \ + --config cloudbuild.yaml \ + --substitutions=_TAG_REF_1=dev-$(date +%s),_TAG_REF_2=dev +``` -Here is an overview of Modron's infrastructure: +#### Modron UI -![Modron infrastructure](images/infrastructure.png) +```bash +gcloud \ + --project "$PROJECT_ID" \ + builds submit \ + --config cloudbuild-ui.yaml \ + --substitutions=_TAG_REF_1=dev-$(date +%s),_TAG_REF_2=dev +``` -Both Modron Cloud Run run in the same [modron project](https://console.cloud.google.com/home/dashboard?project=modron). -Nagatha runs in a [separate project](https://console.cloud.google.com/home/dashboard?project=nagatha). -There is a dev container. This project is meant to be opened with [VSCode](https://code.visualstudio.com/). +## Development of Modron + +### Requirements To run this project you'll need: * Docker * Go * The Google SDK -* A protobuf compiler * npm +* terraform -The dev container provides these tools. Upon starting, vscode will ask if you want to reopen the project in the dev container, accept. +### Getting started -If you have problems with your git configuration inside the container, set `remote.containers.copyGitConfig` to true. -https://github.com/microsoft/vscode-remote-release/issues/6124 +#### Generate the protobuf files -## Permissions +We'll use a Docker image that contains [`buf`](https://buf.build/) and some protoc plugins to generate the protobuf +files. +We'll call this image `bufbuild` - it needs to be built only once: -The Modron service is meant to work at the organization level on GCP. In order to access the data it needs to run the analysis, the Modron runner service account will need the following permissions at the organization level: +```bash +docker build -t bufbuild -f docker/Dockerfile.buf . +``` + +Now, this image can be used to generate the protobuf files: +```bash +docker run -v "$PWD:/app" -w "/app" bufbuild generate ``` - "apikeys.keys.list", - "cloudasset.assets.searchAllIamPolicies", - "compute.backendServices.list", - "compute.instances.list", - "compute.regions.list", - "compute.sslCertificates.list", - "compute.sslPolicies.list", - "compute.subnetworks.list", - "compute.targetHttpsProxies.list", - "compute.targetHttpsProxies.list", - "compute.targetSslProxies.list", - "compute.urlMaps.list", - "compute.zones.list", - "container.clusters.list", - "iam.serviceAccounts.list", - "iam.serviceAccountKeys.list", - "monitoring.metricDescriptors.get", - "monitoring.metricDescriptors.list", - "monitoring.timeSeries.list", - "resourcemanager.projects.getIamPolicy", - "serviceusage.services.get", - "storage.buckets.list", - "storage.buckets.getIamPolicy", + + + We don't use the buf plugins because we might encounter some + rate limits + + +### Formatting + +You can format your code using: +- `gofmt -w ./` +- `terraform fmt -recursive` +- `eslint` + +#### IDE + +You can configure your IDE to format Terraform code by following these guides: +- [Use Terraform formatter on IDEA-based IDEs](https://www.jetbrains.com/help/idea/terraform.html#use-terraform-formatter) +- [Terraform extension for VSCode - Formatting](https://marketplace.visualstudio.com/items?itemName=hashicorp.terraform#formatting) + +## Permissions + +The Modron service is meant to work at the organization level on GCP. In order to access the data it needs to run the +analysis, the Modron runner service account will need the following permissions at the organization level: + +```plain +apikeys.keys.list +cloudasset.assets.searchAllIamPolicies +compute.backendServices.list +compute.instances.list +compute.regions.list +compute.sslCertificates.list +compute.sslPolicies.list +compute.subnetworks.list +compute.targetHttpsProxies.list +compute.targetHttpsProxies.list +compute.targetSslProxies.list +compute.urlMaps.list +compute.zones.list +container.clusters.list +iam.serviceAccounts.list +iam.serviceAccountKeys.list +iam.serviceAccounts.getIamPolicy +monitoring.metricDescriptors.get +monitoring.metricDescriptors.list +monitoring.timeSeries.list +resourcemanager.projects.getIamPolicy +serviceusage.services.get +storage.buckets.list +storage.buckets.getIamPolicy ``` It is recommended to create a custom role with these permissions. For that you can use this terraform stanza: -``` +```hcl resource "google_organization_iam_custom_role" "modron_lister" { org_id = var.org_id role_id = "ModronSecurityLister" @@ -169,6 +353,8 @@ resource "google_organization_iam_custom_role" "modron_lister" { ## Debug +### GoSec + Run gosec as run by gitlab: ``` @@ -192,36 +378,82 @@ To run the integration test, you'll need a self signed certificate for the notif ``` openssl req -x509 -newkey rsa:4096 -keyout key.pem -nodes -out cert.pem -sha256 -days 365 -subj '/CN=modron_test' -addext "subjectAltName = DNS:modron_test" -docker-compose up --build --exit-code-from "modron_test" --abort-on-container-exit +docker compose up --build --exit-code-from "modron_test" --abort-on-container-exit ``` ### UI Integration test ``` -docker-compose -f docker-compose.ui.yaml up --build --exit-code-from "modron_test" --abort-on-container-exit +docker compose -f docker-compose.ui.yaml up --build --exit-code-from "modron_test" --abort-on-container-exit ``` ### Running locally +#### Log in to GCP + +In order to use the Google Cloud APIs, you need to log in to GCP as if you were using a service account: + +```bash +gcloud auth application-default login +``` + +Check out the [`gcloud` docs](https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login) for more +information. +If you don't log in using the above command, the collector might fail with an error similar to: + +```plain +"invalid_grant" "reauth related error (invalid_rapt)" "https://support.google.com/a/answer/9368756" +``` + +#### Start the docker-compose stack + Use this docker command to spin up a local deployment via docker-compose (will rebuild on every run): + ``` -docker-compose -f docker-compose.ui.yaml up --build +docker compose -f docker-compose.ui.yaml up --build ``` -In case you want to clean up all the created images, services and volumes (e.g. if you suspect a caching issue or if a service does not properly shut down): + +In case you want to clean up all the created images, services and volumes (e.g. if you suspect a caching issue or if a +service does not properly shut down): + ``` -docker-compose rm -fsv # remove all images, services and volumes if needed +docker compose rm -fsv # remove all images, services and volumes if needed ``` +#### Use Docker by itself -Alternative: Use the docker command to run modron locally (against a dev project): +As an alternative you can use the following docker command to run Modron locally (against a dev project): ``` chmod 644 ~/.config/gcloud/application_default_credentials.json docker build -f Dockerfile.db -t modron-db:latest . -docker run -e POSTGRES_PASSWORD="docker-test-password" -e POSTGRES_USER="modron" -e POSTGRES_DB="modron" -e PG_DATA="tmp_data/" -t modron-db:latest -p 5432 -GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/application_default_credentials.json PORT="8080" GCP_PROJECT_ID=modron-dev OPERATION_TABLE_ID="operations" OBSERVATION_TABLE_ID="observations" RESOURCE_TABLE_ID="resources" RUN_AUTOMATED_SCANS="false" ORG_SUFFIX="@example.com" STORAGE="SQL" DB_MAX_CONNECTIONS="1" SQL_BACKEND_DRIVER="postgres" SQL_CONNECT_STRING="host=localhost port=5432 user=modron password=docker-test-password database=modron sslmode=disable" go run . --logtostderr +docker run -e POSTGRES_PASSWORD="docker-test-password" -e POSTGRES_USER="modron" -e POSTGRES_DB="modron" -e PG_DATA="tmp_data/" -t postgres:latest -p 5432 +GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/application_default_credentials.json PORT="8080" RUN_AUTOMATED_SCANS="false" ORG_SUFFIX="@nianticlabs.com" STORAGE="SQL" DB_MAX_CONNECTIONS="1" SQL_BACKEND_DRIVER="postgres" SQL_CONNECT_STRING="host=localhost port=5432 user=modron password=docker-test-password database=modron sslmode=disable" go run . ``` +## Telemetry + +Modron supports [OpenTelemetry](https://opentelemetry.io/docs/) and expects a GRPC OTEL collector to be running +alongside the deployment. We currently export traces and metrics through this collector. + +The collector (`otel-collector`) can be configured to forward the telemetry data to other exporters - by default +these are Google Cloud Monitoring for the production environment and Prometheus / Jaeger for the local deployment. + +### Checking the telemetry data locally + +When running Modron locally, we suggest to start the auxiliary services by running: + +```bash +docker-compose -f docker-compose.dev.yaml up -d +``` + +This will start everything you need to get started to develop locally for Modron: + +- [Jaeger](http://127.0.0.1:16686/) +- [Prometheus](http://127.0.0.1:9090/) +- `otel-collector` running on `127.0.0.1:4317` (GRPC) +- PostgreSQL running on `127.0.0.1:5432` + ## Future developments * Provide an historical view of the reported issues. @@ -231,4 +463,4 @@ GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/application_default_credentials. ## Security -Report any security issue to [security@example.com](mailto:security@example.com). +Report any security issue to [security@nianticlabs.com](mailto:security@nianticlabs.com). diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..7f7b406 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,30 @@ +version: v2 +managed: + enabled: true + +plugins: + - local: protoc-gen-go + out: src/proto/generated + - local: protoc-gen-go-grpc + out: src/proto/generated + - local: protoc-gen-js + out: src/ui/client/src/proto/ + opt: import_style=commonjs,binary + - local: protoc-gen-grpc-web + out: src/ui/client/src/proto/ + opt: + - import_style=typescript + - mode=grpcweb + +inputs: + - directory: ./src/proto + - directory: ./src/nagatha/proto + - module: buf.build/googleapis/googleapis:8bc2c51e08c447cd8886cdea48a73e14 + paths: + - google/api + - google/rpc + - google/longrunning + - module: buf.build/k8s/api:8f68e41b943c4de8a5e9c9a921c889a7 + paths: + - k8s.io/api/core + - k8s.io/apimachinery/ \ No newline at end of file diff --git a/buf.lock b/buf.lock new file mode 100644 index 0000000..5373d39 --- /dev/null +++ b/buf.lock @@ -0,0 +1,9 @@ +# Generated by buf. DO NOT EDIT. +version: v2 +deps: + - name: buf.build/googleapis/googleapis + commit: 8bc2c51e08c447cd8886cdea48a73e14 + digest: b5:b7e0ac9d192bd0eae88160101269550281448c51f25121cd0d51957661a350aab07001bc145fe9029a8da10b99ff000ae5b284ecaca9c75f2a99604a04d9b4ab + - name: buf.build/k8s/api + commit: 8f68e41b943c4de8a5e9c9a921c889a7 + digest: b5:0c188e351df7b094d6a5412f4cd5f097fbf1a32d4a2d4c42b83774e168961447e6e706e4ebf241a13b94493aa6cbe08dc8abd03e2a1f8207ac7620bf186030c8 diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..f0a19ec --- /dev/null +++ b/buf.yaml @@ -0,0 +1,10 @@ +version: v2 +modules: + - path: src/proto + - path: src/nagatha/proto +deps: + - buf.build/googleapis/googleapis + - buf.build/k8s/api +lint: + use: + - DEFAULT \ No newline at end of file diff --git a/cloudbuild-ui.yaml b/cloudbuild-ui.yaml new file mode 100644 index 0000000..3683f49 --- /dev/null +++ b/cloudbuild-ui.yaml @@ -0,0 +1,12 @@ +steps: + - name: 'gcr.io/cloud-builders/docker' + args: + - build + - --tag=us-central1-docker.pkg.dev/$PROJECT_ID/modron/modron-ui:$_TAG_REF_1 + - --tag=us-central1-docker.pkg.dev/$PROJECT_ID/modron/modron-ui:$_TAG_REF_2 + - -f + - ./src/ui/Dockerfile + - . +images: + - "us-central1-docker.pkg.dev/$PROJECT_ID/modron/modron-ui:$_TAG_REF_1" + - "us-central1-docker.pkg.dev/$PROJECT_ID/modron/modron-ui:$_TAG_REF_2" diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 0000000..e30b208 --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,12 @@ +steps: + - name: 'gcr.io/cloud-builders/docker' + args: + - build + - --tag=us-central1-docker.pkg.dev/$PROJECT_ID/modron/modron:$_TAG_REF_1 + - --tag=us-central1-docker.pkg.dev/$PROJECT_ID/modron/modron:$_TAG_REF_2 + - -f + - ./src/Dockerfile + - . +images: + - "us-central1-docker.pkg.dev/$PROJECT_ID/modron/modron:$_TAG_REF_1" + - "us-central1-docker.pkg.dev/$PROJECT_ID/modron/modron:$_TAG_REF_2" \ No newline at end of file diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 0000000..8a2aa9e --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,53 @@ +version: '3' + +services: + postgres_db: + container_name: postgres_db + image: postgres:16 + restart: always + environment: + POSTGRES_USER: "modron" + POSTGRES_PASSWORD: "modron" + POSTGRES_DB: "modron" + PGDATA: "/tmp/" + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U modron"] + interval: 1s + timeout: 2s + retries: 5 + tmpfs: + - /tmp + + jaeger: + image: jaegertracing/all-in-one:1.59 + ports: + - "16686:16686" + environment: + COLLECTOR_OTLP_ENABLED: true + COLLECTOR_OTLP_GRPC_HOST_PORT: 0.0.0.0:4317 + networks: + - otel + + prometheus: + image: prom/prometheus:latest + command: + - --web.enable-remote-write-receiver + ports: + - "9090:9090" + networks: + - otel + + otel-collector: + image: otel/opentelemetry-collector:0.108.0 + command: + - --config=/etc/otel/config.yaml + ports: + - "4317:4317" + volumes: + - ./otel/config:/etc/otel + networks: + - otel +networks: + otel: {} diff --git a/docker-compose.ui.yaml b/docker-compose.ui.yaml index 2149ee9..aa5b14f 100644 --- a/docker-compose.ui.yaml +++ b/docker-compose.ui.yaml @@ -1,5 +1,3 @@ -version: '3' - services: modron_proxy: container_name: modron_proxy @@ -16,30 +14,35 @@ services: modron_fake: container_name: modron_fake - build: src/ + build: + context: . + dockerfile: src/Dockerfile environment: RUN_AUTOMATED_SCANS: "false" COLLECTOR: "FAKE" DB_MAX_CONNECTIONS: "1" GRPC_TRACE: "all" GRPC_VERBOSITY: "DEBUG" - OBSERVATION_TABLE_ID: "observations" - OPERATION_TABLE_ID: "operations" - ORG_ID: "0123456789" + LISTEN_ADDR: "0.0.0.0" + ORG_ID: "111111111111" ORG_SUFFIX: "@example.com" PORT: 8080 - RESOURCE_TABLE_ID: "resources" SQL_BACKEND_DRIVER: "postgres" SQL_CONNECT_STRING: "host=postgres_db port=5432 user=modron password=docker-test-password database=modron sslmode=disable" STORAGE: "SQL" + TAG_CUSTOMER_DATA: 111111111111/customer_data + TAG_EMPLOYEE_DATA: 111111111111/employee_data + TAG_ENVIRONMENT: 111111111111/environment networks: - modron depends_on: - - postgres_db + postgres_db: + condition: service_healthy modron_ui: container_name: modron_ui - build: ./src/ui + build: + dockerfile: src/ui/Dockerfile environment: ENVIRONMENT: "E2E_TESTING" DIST_PATH: "./ui" @@ -50,8 +53,8 @@ services: modron_test: container_name: modron_test build: - context: ./src/ui/client - dockerfile: Dockerfile.e2e + context: . + dockerfile: ./src/ui/client/Dockerfile.e2e depends_on: - modron_proxy environment: @@ -68,15 +71,18 @@ services: postgres_db: container_name: postgres_db - build: - context: src/ - dockerfile: Dockerfile.db + image: postgres:14-bookworm restart: always environment: POSTGRES_USER: "modron" POSTGRES_PASSWORD: "docker-test-password" POSTGRES_DB: "modron" PGDATA: "/tmp/" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U modron"] + interval: 1s + timeout: 2s + retries: 5 tmpfs: - /tmp networks: diff --git a/docker-compose.yaml b/docker-compose.yaml index 594ddb0..c4f7258 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,17 +1,20 @@ -version: '3' - services: postgres_db: container_name: postgres_db - build: - context: src/ - dockerfile: Dockerfile.db + image: postgres:14-bookworm restart: always + ports: + - "5432:5432" environment: POSTGRES_USER: "modron" POSTGRES_PASSWORD: "docker-test-password" POSTGRES_DB: "modron" PGDATA: "/tmp/" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U modron"] + interval: 1s + timeout: 2s + retries: 5 tmpfs: - /tmp networks: @@ -19,36 +22,39 @@ services: modron_fake: container_name: modron_fake - build: src/ + build: + context: . + dockerfile: src/Dockerfile environment: COLLECTOR: "FAKE" DB_BATCH_SIZE: "1" DB_MAX_CONNECTIONS: "1" - ENVIRONMENT: "E2E_GRPC_TESTING" - GLOG_v: "10" + IS_E2E_GRPC_TEST: "true" + LISTEN_ADDR: "0.0.0.0" NOTIFICATION_SERVICE: "modron_test:8082" - OBSERVATION_TABLE_ID: "observations" - OPERATION_TABLE_ID: "operations" - ORG_ID: "0123456789" + ORG_ID: "111111111111" ORG_SUFFIX: "@example.com" PORT: 8081 - RESOURCE_TABLE_ID: "resources" RUN_AUTOMATED_SCANS: "false" SQL_BACKEND_DRIVER: "postgres" SQL_CONNECT_STRING: "host=postgres_db port=5432 user=modron password=docker-test-password database=modron sslmode=disable" STORAGE: "SQL" + TAG_CUSTOMER_DATA: 111111111111/customer_data + TAG_EMPLOYEE_DATA: 111111111111/employee_data + TAG_ENVIRONMENT: 111111111111/environment ports: - "8081:8081" networks: - modron depends_on: - - postgres_db + postgres_db: + condition: service_healthy modron_test: container_name: e2e_test build: - context: src/ - dockerfile: Dockerfile.e2e + context: . + dockerfile: src/Dockerfile.e2e environment: BACKEND_ADDRESS: "modron:8080" FAKE_BACKEND_ADDRESS: "modron_fake:8081" diff --git a/docker/Dockerfile.buf b/docker/Dockerfile.buf new file mode 100644 index 0000000..626f41f --- /dev/null +++ b/docker/Dockerfile.buf @@ -0,0 +1,51 @@ +FROM ubuntu:24.04 +ARG BUF_VERSION="1.46.0" +ARG BUF_MINISIG_PUBKEY="RWQ/i9xseZwBVE7pEniCNjlNOeeyp4BQgdZDLQcAohxEAH5Uj5DEKjv6" +ARG PROTOBUF_JS_VERSION="3.21.4" +ARG GRPC_WEB_VERSION="1.5.0" +ARG GRPC_GATEWAY_VERSION="2.23.0" + +RUN apt-get update && \ + apt-get install -y \ + protoc-gen-go \ + protoc-gen-go-grpc \ + curl \ + wget \ + minisign \ + perl +WORKDIR /build + +# buf +RUN wget -q "https://github.com/bufbuild/buf/releases/download/v${BUF_VERSION}/buf-$(uname -s)-$(uname -m)" && \ + wget -q "https://github.com/bufbuild/buf/releases/download/v${BUF_VERSION}/sha256.txt" && \ + wget -q "https://github.com/bufbuild/buf/releases/download/v${BUF_VERSION}/sha256.txt.minisig" && \ + minisign -Vm sha256.txt -P "$BUF_MINISIG_PUBKEY" && \ + shasum -a 256 -c sha256.txt --ignore-missing && \ + mv "buf-$(uname -s)-$(uname -m)" /usr/local/bin/buf && \ + chmod +x /usr/local/bin/buf && \ + rm * + +RUN bash -c "ARCH=$(dpkg --print-architecture); if [ \"\$ARCH\" = \"arm64\" ]; then ARCH=\"aarch_64\"; fi; echo -n \$ARCH > /tmp/arch" + +# protobuf-javascript +RUN wget -q -O /tmp/protobuf-javascript.tar.gz "https://github.com/protocolbuffers/protobuf-javascript/releases/download/v${PROTOBUF_JS_VERSION}/protobuf-javascript-${PROTOBUF_JS_VERSION}-$(uname -s | tr "[:upper:]" "[:lower:]")-$(cat /tmp/arch).tar.gz" && \ + mkdir /tmp/protobuf-javascript && \ + tar -xzvf /tmp/protobuf-javascript.tar.gz -C /tmp/protobuf-javascript && \ + mv /tmp/protobuf-javascript/bin/protoc-gen-js /usr/local/bin/protoc-gen-js && \ + rm -rf /tmp/protobuf-javascript + +# protoc-gen-grpc-web +RUN wget -q -O /usr/local/bin/protoc-gen-grpc-web "https://github.com/grpc/grpc-web/releases/download/${GRPC_WEB_VERSION}/protoc-gen-grpc-web-${GRPC_WEB_VERSION}-$(uname -s | tr "[:upper:]" "[:lower:]")-$(uname -m)" && \ + chmod +x /usr/local/bin/protoc-gen-grpc-web + +# protoc-gen-grpc-gateway +RUN wget -q -O /usr/local/bin/protoc-gen-grpc-gateway \ + "https://github.com/grpc-ecosystem/grpc-gateway/releases/download/v${GRPC_GATEWAY_VERSION}/protoc-gen-grpc-gateway-v${GRPC_GATEWAY_VERSION}-$(uname -s | tr "[:upper:]" "[:lower:]")-$(dpkg --print-architecture)" && \ + chmod a+x /usr/local/bin/protoc-gen-grpc-gateway + +# protoc-gen-openapiv2 +RUN wget -q -O /usr/local/bin/protoc-gen-openapiv2 \ + "https://github.com/grpc-ecosystem/grpc-gateway/releases/download/v${GRPC_GATEWAY_VERSION}/protoc-gen-openapiv2-v${GRPC_GATEWAY_VERSION}-$(uname -s | tr "[:upper:]" "[:lower:]")-$(dpkg --print-architecture)" && \ + chmod a+x /usr/local/bin/protoc-gen-openapiv2 + +ENTRYPOINT [ "buf" ] diff --git a/docs/FINDINGS.md b/docs/FINDINGS.md new file mode 100644 index 0000000..178b55d --- /dev/null +++ b/docs/FINDINGS.md @@ -0,0 +1,165 @@ +# Findings + +## API_KEY_WITH_OVERBROAD_SCOPE +The API key is granting access to too many different scopes, or is not limited at all in what actions it allows. +A malicious actor in possession of this key would be able to make a lot of damages to your infrastructure. + +### Recommendation +Limit the scope of the API key to the smallest set of actions required by the user of this key to run properly. +A list of scope is available in the [Google documentation](https://developers.google.com/identity/protocols/oauth2/scopes). + +## BUCKET_IS_PUBLIC +A public bucket means that the content of this bucket is accessible to anybody on the internet. +Make sure that the content of this bucket is actually intended to be public. + +> [!WARNING] +> Do not assume that files with a cryptic name will never be found. These files will eventually be found. If files should stay private, then they should be hosted in a private bucket. + +### Recommendation +Make this bucket private + +OR + +Make sure that the content of this bucket is intended to be public + +## CLUSTER_NODES_HAVE_PUBLIC_IPS +On GCP, this means that you have a public cluster. There is no reason to have a public cluster today. Services that should be publicly accessible should be exposed using a load balancer. + +For Airflow and Dataflow clusters, there is an option to set when starting the flows to use private cluster. + +### Recommendation + +> [!NOTE] +> There is no way to transform a public cluster into a private one. + +1. Create a new private cluster matching the specifications of the existing one +2. Migrate your workloads to the new cluster +3. Delete the old public cluster. + +## CROSS_PROJECT_PERMISSIONS +The resource is controlled by an account defined in another project. +This circumvents the isolation provided by a project. + +### Recommendation +Use only accounts defined in the project to grant write and admin access to a resource. + +## DATABASE_ALLOWS_UNENCRYPTED_CONNECTIONS +All connections to a database should use an encrypted connection. No clear text communication between a workload and a database should be allowed. + +### Recommendation +Configure your database to allow only encrypted connections + +## DATABASE_AUTHORIZED_NETWORKS_NOT_SET +Anyone can connect to this database without limitations. + +### Recommendation +Add a list of IP or IP networks from which you expect connections and allow only connections from these networks. +This is also valid if your database is only available to internal IPs. + +## EXPORTED_KEY_EXPIRY_TOO_LONG +An exported key has been around for too long. + +Exported keys are immutable credentials that grant whoever has access to them a time unbounded access to our infrastructure. As people come and go, it is recommended to regularly rotate credentials to reduce the risk associated with leaks and malicious activity. + +### Recommendation + +- Rotate these credentials by deleting the existing one and creating a new one +- Create a process, possibly automated, to rotate credentials in the future and run this process regularly (every 3-6 months) + +## EXPORTED_KEY_WITH_ADMIN_PRIVILEGES +An exported key in that resource group grants administrative privileges. Read EXPORTED_KEY_EXPIRY_TOO_LONG and add to the risk the fact that these credentials grant access to deployments, databases and possibly user-data. + +### Recommendation + +1. Remove the admin privileges of that service account or create a new service account with limited privileges +2. Rotate the key after this has been done + +## HUMAN_WITH_OVERPRIVILEGED_BASIC_ROLE +A human user or a group has one of the following permissions at the project level: + +- Owner +- Editor +- Viewer +- Security Admin + +### Recommendation + +Use less privileged roles for humans. The principle of least privilege should be applied to all users. + +## LOAD_BALANCER_MIN_TLS_VERSION_TOO_OLD +The load balancer supports a deprecated TLS version. +The TLS version was deprecated because it supports broken cryptographic primitives. + +### Recommendation +Define an SSL Policy at the project level or for the load balancer specifically that with a minimum TLS version +of 1.2 and a MODERN or RESTRICTED profile. +See [defining an SSL policy](https://cloud.google.com/load-balancing/docs/ssl-policies-concepts#defining_an_ssl_policy) for more information. + +## LOAD_BALANCER_USER_MANAGED_CERTIFICATE +A load balancer has been found with a user generated certificate. + +User generated certificates have multiple risks: +- The cryptographic material associated with that load balancer must be manually managed, leaving the door open to: + - Certificate expiry if the certificates are not renewed in time + - Credentials leakage if anybody that has access to the cryptographic material is compromised or bad intended + +- The generation of the private key has usually been done on a private machine where + - the Pseudo Random Number Generator has not been verified + - the entropy might have been too low + +This weakens the encryption of the communication between the clients and the load balancer + +### Recommendation +Migrate to a Google Managed certificate. + +## KUBERNETES_VULNERABILITY_SCANNING_DISABLED + +The GKE cluster is not using the [workload vulnerability scanning feature](https://cloud.google.com/kubernetes-engine/docs/concepts/about-workload-vulnerability-scanning). +This means that container image vulnerabilities aren’t surfaced, or are only partially surfaced. + +Follow the steps in [Automatically scan workloads for known vulnerabilities](https://cloud.google.com/kubernetes-engine/docs/how-to/security-posture-vulnerability-scanning) +to enable it. Modron also suggests a command to run based on the name of your cluster and the project it is in. + +## MASTER_AUTHORIZED_NETWORKS_NOT_SET +The administration interface of your Kubernetes cluster is available anyone who has access to a Google IP, or anybody on the internet as anybody can create a VM on GCP. + +### Recommendation + +Restrict the access to the Kubernetes API to a list of IP or IP networks from which you expect connections. + +## OUTDATED_KUBERNETES_VERSION + +The version of Kubernetes running in that cluster is not supported anymore. +Running outdated software is the first source of compromise. +Running up-to-date software is the first barrier of defence against known vulnerabilities. + +### Recommendation + +- Update your Kubernetes cluster to a supported version +- Onboard into release channel to benefit from automated updates in the future. + +## PRIVATE_GOOGLE_ACCESS_DISABLED +The mentioned network contains some subnets that can have a preferred routing to the Google APIs without going through the Internet. It is recommended to use this routing pattern for security and latency reasons. + +### Recommendation +Enable Private Google Access on all your subnetworks. + +## SERVICE_ACCOUNT_TOO_HIGH_PRIVILEGES +This service account has too high privileges. In general we try to avoid granting too high privileges to service accounts. +Often time, permissions are granted at the project level where they should be granted at a more granular level. +For instance, Service Account Token Creator at the project level allows for privilege escalation as it allows the +service account that has this permission to get a token for any other service account in that project. + +Granting this permissions at the service account level only allows that service account to get a token +for another specific service account. + +### Recommendation +Limit the permissions of that service account to the strict minimum set of permissions required for this service +account to run the tasks it is running. + +## UNUSED_EXPORTED_CREDENTIALS +An exported credential is still valid but has not been used in a while. + +### Recommendation +It is recommended to delete unused credentials and regenerate a new set when they are needed to prevent leaking +of credentials and unauthorised access to our infrastructure. diff --git a/docs/RISK_SCORE.md b/docs/RISK_SCORE.md new file mode 100644 index 0000000..b9fda96 --- /dev/null +++ b/docs/RISK_SCORE.md @@ -0,0 +1,120 @@ +# Risk Score + +## Definition + +The Risk Score is an indicator that is used to calculate the priority to assign to observations. +We compute this score as an indicative measure of the risk given the conditions in which the observation was created. + +The Risk Score ranges from INFO to CRITICAL. + +## Severity + +### CVSS v3.x + +CVSS v3.x defines a qualitative measure of severity of a vulnerability. +[As they define in their own documentation](https://nvd.nist.gov/vuln-metrics/cvss), **CVSS is not a measure of risk**. + +CVSS v3.x defines the severity scoring as follows: + +| Severity | Score Range | +|----------|:-----------:| +| NONE | 0.0 | +| LOW | 0.1 - 3.9 | +| MEDIUM | 4.0 - 6.9 | +| HIGH | 7.0 - 8.9 | +| CRITICAL | 9.0 - 10.0 | + +### Modron + +In Modron, the Severity matches the CVSS v3.x severity levels - with the exception of the "NONE" severity that +is called "INFO". Rules can define the severity of the observations they create: +```go +ob := &pb.Observation{ + Name: "BUCKET_IS_PUBLIC", + ExpectedValue: "private", + ObservedValue: "public", + Remediation: &pb.Remediation{ + Description: "Bucket foo is publicly accessible", + Recommendation: "Unless strictly needed, restrict the IAM policy of the bucket", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, // <-- We define the severity here +} +``` + +| Severity | +|----------| +| INFO | +| LOW | +| MEDIUM | +| HIGH | +| CRITICAL | + +#### UI representation + +In the UI we represents the severities (together with the risk score and the impact) using the following icons: + +

+Severities +

+ +The screenshot includes a "?" severity - this is a very special case that is used when the severity is not known and +indicates a bug in the code if it is displayed in the UI. + + +#### Re-using the CVSS v3.x score + +When available, we use the CVSS v3.x score to map it to the Modron severity (external sources) - internally in Modron we +use the scale directly (e.g: a rule defines its observations to be LOW severity) + +## Impact + +Since the CVSS score (and the Modron Severity) do not measure risk, we use "facts" to calculate the _Impact_ of +an observation. The impact, when combined with the severity, is used to determine the Risk Score of an observation. + +### Definition + +An _Impact_ can only assume three values: LOW, MEDIUM or HIGH. + +To define the impact, we collect context about the environment in which the observation was generated that we call "facts". +We always take the highest impact defined by the list of facts to determine the final impact. + +### Facts + +An example of a fact is "workload is from the production environment". This is a fact that can make a misconfiguration more impactful. +By leveraging these informations we can increase or decrease the risk score. + +#### Real-world example + +| Kind | Example | Decision | +|-------------|--------------------------------------------------------------|----------------| +| Observation | A misconfigured SQL database is accessible from the internet | Severity: HIGH | +| Fact | The database contains sensitive information | Impact: HIGH | +| Fact | The database is not used | Impact: LOW | +| Fact | The database is not used in production | Impact: LOW | + +The rule "SQL database is accessible from the internet" defines the severity of the observations as HIGH. +One of the facts sets the impact to be HIGH, while the other two facts set the impact to be LOW: we always take +the worst case scenario when defining the impact, so the final impact is HIGH. + +We now have the Severity (HIGH) and the Impact (HIGH). We can now calculate the Risk Score. + +## Risk Score + +The Risk Score calculation is straight-forward: + +- If the impact is **MEDIUM**, the risk score is equal to the severity, +- If the impact is **HIGH**, the risk score is one category higher than the severity (e.g: **MEDIUM** -> **HIGH**) +- If the impact is **LOW**, the risk score is one category lower than the severity (e.g: **MEDIUM** -> **LOW**) + +We can therefore define a "Risk Score matrix" as follows: + +

+Risk Score Matrix +

+ +### Examples + +- A HIGH severity observation with a HIGH impact will have a CRITICAL risk score +- A HIGH severity observation with a MEDIUM impact will have a HIGH risk score +- A HIGH severity observation with a LOW impact will have a MEDIUM risk score +- An INFO severity observation with a LOW impact will have an INFO risk score (can't get lower than INFO) diff --git a/docs/pics/architecture.svg b/docs/pics/architecture.svg new file mode 100644 index 0000000..c1366d1 --- /dev/null +++ b/docs/pics/architecture.svg @@ -0,0 +1,534 @@ + + + + + + + + + + + + + + + + + +
+
+
+ modron +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ modron-grpc-web +
Cloud Run +
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+
+ modron-ui +
Cloud Run +
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ modron +
Cloud Load Balancing +
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + +
+
+
+ /api +
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + +
+
+
+ /* +
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
+ IAP +
Identity-Aware Proxy +
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
+ modron +
Cloud SQL +
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + +
+
+
+ https://modron.example.com +
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GCP API + + + + + + + + + + + + + +
+
+
+ Collect +
+
+
+
+ +
+
+
+
+
+ + + + + + + Nagatha + + + + + + + + + + + + + +
+
+
+ Notify +
+
+
+
+ +
+
+
+
+
+
+
+
+
diff --git a/docs/pics/modron.svg b/docs/pics/modron.svg new file mode 100644 index 0000000..04eb461 --- /dev/null +++ b/docs/pics/modron.svg @@ -0,0 +1,75 @@ + + +image/svg+xml diff --git a/docs/pics/risk_score_matrix.png b/docs/pics/risk_score_matrix.png new file mode 100644 index 0000000000000000000000000000000000000000..9959320f318a162ee4736b66dc8b6a5fa56dc7b6 GIT binary patch literal 51408 zcmeFZ^m5@knR}L%>YR$l^)8Z8>G8q zjC|(%^L?Jb;Q8tM`s}AWw)?uT^E|KPtm9gQhMK}7B3dE>0)j_Min3Y+1b0FS2yVGO zxR1Y5ve(H$K=6@3N%pOdkLh;Sz09Y&&%**k3YwdrYwBpkQfU~{65r~&{Z`}EhkVNC z+nogVvYaFz$R{i|jHxELC!)pnyyy7559gX;Cq@xjH%ESZkVz4ct<7%b(RPW&UHlOL zKHmo^zcT#KApyZ(&X@QX`0w-A<$n+V3F7}ZIehHs{q-w9IQT9ajjmz6ML^)fMlORN z;QOuR)zu+tGx*z%&_{WvJJZ@ntG`QR!bnHT^mu#^`&jMQ2C}}f6&4i@?=;MF51$`z zED88xcU1$fPN-Y2PNzrm6v%K_e{y7if`VgSHc8vtAJTcPiB7gBwZ|@cnPPgErW+vG z;%R@RW}dvJynN^{1_}xpp;?avg|@dr_Xlh3hu8CBBy;Z&lBTLx91VeaG_kw0)>%vn z(fPHt{7|z-x`uw!B8gf@`1wZBN;{6mOl)2c!fEnNr_f!pGJ6k1Kww<@h6F#QkBp>P z@;T3O6PP_|~F%Cqy86f>)9_q74dtMe0y!wF0LBx`Mc)Am=_IgU>aRMvR!nX+r< z1|uPJEzrZW&CxQ6%{qnAQeB{cfbMEcc=(saE(C13dd;lr{jHUim9&gE39Ra4M>-{1 zAzE5m`7ZU2;{$o5W{=6q-|V$w=|_t-Wu(UHos6KEUvwgF!R)%F`D+^!)l6AZ0dw($ z1O(j7|Jn7M$6<2a+``uV$#wAgTsdN(G6`61xKLFBJz}^3v5GPVOWa{Me*6>h zz`(#-$8m1x40rNeuGh{~jdzTQ=VoG}K8zl6(8rpcn(+oZ2SXL8sgg9UjhG6y|E=dS zsOfZzf~oyh$r9C7w**`t_VaLU;MHDo3-oXCMZS*A?On)A&)2CmHXs_lzPi}hYriR6 z=}+ljh(@)Ymzs8ShjxUKrF6*OoGp<_TwflkE32s`EOD-0|7_81?q^{Xu*FMoU2`s9|@EOPAh?5^d zgj}s5csn^>Z@o+K5&S3|fS-s)a9>|v&U5F+w<(Ejeiu_;6`{*;S}zcM{|BLJlY~Ko za~3jXWp@kJUY~zdg?3v(Z$PQI4D*Z9x?;oZug|woh4ILVE|r<>&8uok6c?$#*7XdapDEaNxAd>7r);c& zT}LkDD<$BkdB-6c%m>f@)uDlETqHWb!p>Wx%Dtjs+P4V)x)E4Bz|WV`)zlwnZK5OG zNbPueb|9(OSBn4;V}7OaZQuo$46^JdX^JmtV*TCHsM?>*bs`cdu5+}S0T~7*%~dsHdXM z5Of+ER!-(DLAlUAdj`}a7#%_eTtiz)mN1KaQ)5(7CM3`K?Ov{N);rHm)ffW%_ZsH? z9Yu!&m_l0?Ir=MGf76{JwFw9s69Z{(r%Jx`6cdX0HR(^c~{ z>|MJ_BVMRl_x}^B@B?IrcRyD{uc^e>-hCHMQ~QK!1OTFVgBPiC-|*rQMyi42q4jRt zg>$P)ky_TW#&ES|&t{L0T~TY49~%%$ddPAz1)cN5noPCA8j$l0Amc`toDGuuH%T;y zE~9v$GnjgB`u-mR5dLQ>AMY$cVR#*U?InTeV+9^W< z&HjE_g~+KtBSEBkaYqm4DZsX0oS8)eux<|4jeac(ukb*t_J4qugpBM3uhoNp4xonm zH)kY=US(}K09Dn8U)n-(Sn6cO3gh?hzqGw1!LFwXWsFzhrTIFW!3)F>(F55I&|L`= zk_8AH;h&xj=D34LB;)A+^JEn`yu~;tAtXcy-o9B{X7?)<Sbv6GyD0RHdY|Doy3upso%%3-89F3iQX7BY`C4Y=~` zhl^8nu&Ac{EZ6nmu}#&MH&xWLl$im027NA%E;nf&%QJojiGHj@dDDCO=3c!ssFThFXWL|;6kZQDEw z3==$T1-USxiqb@k7H$ zJZq5I>(%DWYU4bHar=S2leUSW)(|P&%|l#LKaXnBNB5hh`pKgU?bSoM$2aaxOY^4o zm5aHlEk_=&SwnM=1~jANZ85o)l4kytiDBBc;<%MN_i_&jt+T|fc^E~o!#uoZJ{eStW#NKcz?F=> z?MaWtIbS`~=M5XZzdP7?Rx%~PaWWzv;l_M6v5r$RjD>MEjtvqBS=_O<+w2WNQ=uBO z^@bK>LMq36fO;itPkYPfSTi?YR$GZ;<5~XYsTdHP9KUr-b6f%&>$oVZd1~sWnyrR@ z4vDR?B-BhkP?!pwV+0t}=pzyJugxTXB&@6^^ zrCDqi^cHT1o%*`Lw!jcue}Ac+M!d#)rnh=(v+XPi7R$5 z=bFE(W?ziegfhbk6KhTlUgwYf?>oymoo2mYe#XiR&O%*gqxx^h9`YY)3x* zq4i!fqe<<;^t95XWELCd*g(8RK6!MSWlEXV@8*|wC)w_)-8jbp%%x63FAmU^q~y)g zrf{{0ENZI;+j$-O!jj=Qt}Ql+%$9WLC_m)4E^!4t1u5qC(+Cq~GUfp8p&0$cwFiN4 zVf!RSGsc02#6nh7O+H^lr3Y=we*Ijb2oULH@vxQo+H;cud;28XM1?~upx*YDV06#& z&@7~DII&@Aqw=Zi@pz7k1Q$>shW>(IQ$73GM!dCUg|4YZ*g!>@UiId6;O=AS9u zQ_z2I;6WVh@9bTJuyJzhT^K7eS{`-{5E2E(j9sS54@)Y3dC0M7zujNWzng@Yf{*W6 zp6pbOSeDk1@u|i4e<=J>wy&>%PhDK*?FxFR(xS`G`=L>|WdK5WWKUizcmh|LEtc6WuC=;w*@X7w{!x)C>pK4KdG1CXX{X?Iadd(IzgEEB32_zJtGR9ImGuDo?; zM6^4CdaF@bTS=%VIu4(qIY3?M$M+W;T%PT0eR;e5O?Esa~D)-Et) zIk+jx22BaM7s?qUh|qiPiUy-0i2}2u`kq{xXBVd**qfe}%K8tECblDdNR6Vz4S@L{ zXkQ(A@Zq1YYkLrCpQw4-qKIA__gyt|4|<@9+1yf^^x@Oo-^%sp3D6g*QTpA1>w*G@ zUX@n=So9$iP|XaAb`l&?VG~0pjdP=R${sILhirVMIS>8tb*_@9lIh&Ku%z}OfD6qK zgE&n+JUR*GY*^#uef8ks7E5QT(uj(Khu8hjsy;o)E{+XlQVG9Zumin725oj0uuW2$ zE0=yWujzs0oDN;U?hF-LXCzzdG%74!D$m!pP@5udj>{&{PA0uz_iv{5h)Uv;Xj3#j z2(g~oA4{`}+O`}y-sVgzsx00@cx(k^e`Mu!5#kQdxkYSQJU%>F%f&CFju1;xXdrsH zh0RQ#%WjW6JBnRX%99J#NiR(wRTH)E!lYCnx7>a}t_T)OpLRgAsiR z))%;`GukG`2H>Kjr=9WzpelkSZscb!{F zI9f3o5*g`U0SC$^`KZ`W#g{F!NV(jWDwPMb8oEO^zp5e^&QD55TwgTIt^z}QV!#Ut zLxOJk0AI_V_$^Xb__5tdYAo2giwLY~tsZL_xgh9ull~lu;Ko??rqSa{SQVjcv)On*FG39?FdUhJgE+iLY|iCBwu&WsT`FPPf(Y_*aUy=so5dcIhRN)Zn;Q2fm%Mjh!MAz1$O zNt0)-E=k9nR*0q}Lxc4Zd2X$p_9JKBMY$=BtrJAetL2*FpB58*UL}c5Hqy1usy-OJ z6%r5l$vqm7RH+E2sgLwkQ8t_|ZW;lcKXiB@pfGY0es-R!0(lwie$a$Eso@y*>h(}_ z1T0b;p%(ks0?3Di!94R?@GFU}VrIa$tfvc! zsm0v@%*H~Td>WIcNR6Zm#rd7sLaD6IP5OP=h z60L4;Ruo;TnDmaB&vo{GYbFoq7Z{v(9IK&BMw)Z{M?>`Hf`QVoy|COirFdz*cp>pI zJ@;Yw@t5~#agS6V{W1O)FUL_8N9+SOuSzIy01@lpEui1vy`CeL*OW@nO_yyNB#$=4diG$Bl^G}TX_wj$23 zMYy|@{D6UB0Lg59;UyxgD*~Tj1Cz_}(OdNL*5_oOnOC>j^|$;$5jF8X)Ou@i7DE1g zevKjv@lsjD{!zlrhDGs1J>%3~CjWNX9m?#2mBUm@L8v6a`z>n2nwi>=q;5;FuVNqq zGV26d3!#~sOz=}yehGdC2K3AC@St&=2}~4%n1^mv1poa{FA)n(^-*4 z@1Xj!5jLexVcZO~%__Th#h?x3m5gCrbmNLk%0t_DRZ6RutK~kYZYM5rel?**qdyXuhF`QAKbt%@)xdegcKvpJ79R zPlNAMQRg=H=}dheXB1;)QTYR-R9E?iU|bs*ZkG~uxC_mAOa|hFgLJO1jgsD06z$K! zltlat)p;I-=LCk)I2?Me6FYX#sNiFmj#%W0&^aKKac8eUKAnuyf(@-T0Cl_%h(s6u zkjE?(2FjKc&Sen;L(e^lCojNR6i!w;int@7_nhQd3%Lw&O!luUQ+~McO^( zyxnSqHT|wZX4bZd^j)7t6b-3K>FH+mjH~(!GSFu=LErVur!vW{zXPV_A4~{SC2+u= z5OQmUMa>=f(siW{6g;MiQ|&OTS_P|gG2DUUYlhz!8i$7LA^nA$`|6JucBao;H6l$& zes{1BOaENaP&FaRHpnl+afCwGv@3pk?^(g9l>9AHJ$XM#JLu%j&UCOY@4MWzX;ED< zUwq&X8ACDOi`i1wZw(!|xNesf<+QSk*-&t;vT)d}GISi)2j9T^?aNON)j@Vs@ZzZDY z$m(YJ1YZDz8X%>ZlzWL}2K4uTx!>bSq;Z?_P8fl8-puj-6kP-H4f^dc?tl-PF0Y+W z{NIvr<+D%n^Iz+VP$t4|i^)kT^|-Z?bxEaCsP5#gfqyu1AI^W+oP_a1KgXiO3g`}{ z1vVnje_j8QWC=^DA@_>%kDQ7)Dq|2raW5}{p{BLCev>4BRK)F$6)_gBY+lNRx94=iXe;O>2V zyhley)aS#B z=)3Dj5Avv<&Lo(#TqmfH;^H|21xhu%mY)F!%s)Rbu3;OpFb$YBC9&u|=w-_1gvBMU znZ35yI9mSZ{we)hy)(9fC{0Xn2ef?8nI!@vU3ww+eMPIGL=pV;KEo1!g>wc?%`uQC zb&54umpjV+;)VQ0%;a?YrQnpHlMO)v8EI4%+bHHmx>GL;+#$ls5+GcQD zonnQNe+De1(>o`L{e-X3=719UQ`EXjesm~8!;8B)q=f}0m(25ju)_Q2LSNXQi_0eW zLOy*yy~FWOQEHn5vy?g|nR8#gS0jpc4B!@~aF$|Yf*q&H;M00%y) z%Jf0r0r)~X()_1w*~+CXuvmKN$-4?r5+}jIfSX6BqVB#d-81oUN-XURYjEnmYvskC zkN)(nvC-5rPK~T6T68O9^>xt&N9ZEoQo0+eZL@?opzT`#AZ1}x1KLa!4b&d_8z8k$ z0Ay98@yzJ@Gk^LN3ZfjU4PXWaNaQKUQnsN+RG?}3aIv>ZnV;|IPM#07epmLlV1v=X zlr@KfR=|k`;!D_%W)`WBi%v(X{~m~Vi`(X#yarK@-%J}HT#4<%UTqTlc4a9Q|BkjD z5lQ8Y5+5hGSWgOXkp;N9Tl2gmHu5h^J%?SBjTnJ|&RI-y&|3Ht#;C@H& z-NSv=0S;#M@QE2rJe&GuLjsl>UnOheM45`N7Urlg=iths<{x7!^kBCHaLa<{ zpY#+*J7fFd*BN_raz*%<_4`8pbyLEp8TVTT{<&tS+gFDne!5yt9E47fGbF6c*Kq0Io$i-2WfAOB>Uv=+&a{g6NSTu?dTYiy9#mTP zbKMw|Xk%zCdL`V%sA%k|wMZHe7<+i*g};X$)^)kXpl@?ubKq!Had6Hq&-Sr}9e`0N za7zC7;M(t}7gdj5ochZYo`TX-FcMtWvZLy^f&8{`t% zq^sS3SU1F0#APMNQ_qApS~$Symt?9dzH1k$^AE%i6iA?I0BrY_pU%}Yvlf7X8BJ)x zhl@6qCdT!3>MIJF%9wup!HYCD-}N9_iFQHp#4eYKAsSJSG3?&PUy8{ZJxz*^q-m$4 zv_MjPVKpqFdXM1VcrmASwA||Pw#X@`WObyuD^aCB(uYABXwE6CN~@(7b*^X1ho5pn z`?YDUf)hniPrz|RiPd#y8!mZny4SV%Mm6^lwojPXD)pNLA-+8DtZrhWJIJ+#rsjRb zMbYI!SZ2R1>3H7PM}bFrBwxSwo$Pn`igeYse=$z9Ii_*@{EWjQ3J`z~``wnqH5>fc zHl`tn(<^7Ec~q_+U(}JXGzr#^ZLZ!@Y0@QuUC|AY>d`=Bf?5@+QJclM4FA4|03e2A zv{%T%VA*TwRUE=U!u*(9m)|-j$4+5=T7K#tg^X)-c2NW@-sbpALaAidEixOSRq5sRZ0}4<>O_e+ize zI@8-IDi7ME4_~~vcI~$!Tg=I%X}Y?%hrq}FkFSzEjTQ5J{dWas;-5K)GWC+RcWLDB zi04Kw^y$0rz2~3p&lpx%S%)4`$dK-GWgg3ms~YFr=b`e|An_2YQ-`MDC@mLfGx0Tdu8^KqLOHx>+ zKqJ0l1vI7#9zI!dHqxzRyZ3AkDLS$XOM4oBbH-1R&zOKm>F26!O2(#QV;_4E2gP zc{ajxde3j}?+J*QO%!t>Z951BdZ^Y4G8|}r)68TAg9KUUV;^%e$8K%BXinV)pR24c z<>Y^jAKkbOR4$x}{=}R}otu_YqZGa&Zm>2AQXd3h|LADEzL6(MBMVSY>KTE41RW10 zyL(`a4r(edJ7ZUzMj`ed+_MD=k>SUjmg{@A?FCu^-Z8JTm-A57K6Y1$}Fe^_l7WtYEHqAWhcIeK|ki5)59C(=cm5Dw+ z_fgGjtuX542ikL`D?j57Ki-^$Yz~(tl-`WyJ!$KRd!CC4P zZf*|I`(Zy-nlGMtycBc7FM(kQIO7SOsFY5sXx$`keR3UW6de4@sab(2hcc6aA+eox zxk@RZtLpVTW>ryXw2f0Uk?X)`p%NIA!!m2=np#uC=XK_lw=r8WAy+l0Y}Pt&v`!`!@tJ2$F42aa^s`4=eutyPY!6WQ$27kvu3%=|9^An0m4sb_cFg?=KeY)&4M`MC_{&1*DH^gAbE_ODJJ!iH|G zY+IyKR9xQ^6avKkMA@M*A|#M;d38(fd>Y;&8wm$vGgnht8#ZftiOM35CTJpbnTEOi zQpec`BP(&!VrrwFw&>w?@I=|T8Tq7B;;*7{b98P>tMuEa%3B;rB$7`ZHOw6=~LmXhGaWNS?3HkTNatVoVbJj6{rXq zBKzHZTPB;tylpP`E@)(+Xeay3Bhr4FLgVGlJCZbrOq6-%)N9QOLJ(eWJ8XwcOyHc7 zJ=WfPkdgD;m6qaX5a=#ec+Hn_{{_rSy9Z_qRQb`U~9OUxZGrC z)^q9r$?a|4D~(P}DZD1srjYUM8jG)Ou(l+E%#MV=Q?*BhFYnb)vbVxQ06LUSCOo-_oS8Rms3};3CkgO5obz=VnTJ&0sY4*~Q;rJD@ zC%X8>@kvyBY@epFhx$;D+lq$n7=03q>@?a$`fABE(F_MuT@;>A?2G}1iDQv|;?_ZP z7rj4ZbRf~NzM7%+!5Mvr=LMDTOXZjV2}XQZ&!67Wxn={ z&68Q7O=Wf;E{hrv^t90_U|SR3XwOBbxYG#$mrrBJ=NTf{ecLg_6nMJGo1YyD9U(96 zTa9^x-yD!@SFG(ps5;c8#{1VT*bYO-N-Q7CTtkjr4=hj-OppiJwQ5-muez!X?iknx zT1@04k$cODO89yMFmx3z2(pTAzg9z+jhRCI;7(H~2bXUj+cyVwwmvBMau|5t(n*of zN(`hBSp!|sLgO;rrJPcFJV7300;9V*^t-sohh^i2o6VVV7R42(eZ-x3^po*X&_O8?-hZs_e;zWKr6uKZIO zss_{Dqo$)iU6tm3Q*n{g5twH?RK^Wk0 zunzs{rHIZ?AAhEXh5HRO-(YQOaHl6j^bU|BmI^JBJtijvDUpe}j(+ER&^ACedhOH| zm6b{6bNU#(zX;Nkmv|fI_Icr==t#>>_9v0&^n*ABXA7QuHDV|2i~G_BfITC+Am% zKB}-_`@4{Cu1ebn4TS~MgnHY7n_Lg_!>OHi1*WyX<#Prc((nCbT_dbtt}n(h(x+Nr z7+tMx^p8}Vrjc?#J1o^zQrYfS*SD}Z9nf5R+gXkMZwJ#wdZlp5 zMQo`On@%WBo-*CjWn+Tf!_5>GmdOmLNlBWu3a$9bic~hgVlJ<1>op!=Glz}lAoCQ; zjO0GtbRp#!sk6;Ro&#Swjd7OIIL{O-zZiF2%R*Z8HX7%DxJozv%AkFe*6`&oeZT8c z{vTk=rkzEusY)aV$`EZWyU(6aUqy4c$d~?pAWfUUYahd^v#}_r{Zs1olUy&fc}fkT zdT)$mRAStrknCxtQkv*N1@N~-XFN50VWD{L6BZ|C|ACr`IkBPl_1fgifmUdE=3dcd ztN!K7htToPk!W)5)`mE}hZ0v|5@DjXN9!#Ax_=dB31fAw){x>Fzi(%9tCn%E0KZCQ ziEdru05T-QOCxidl04%DkC?2_;4R@XMp5>A&GLqinc}DuB8*sI3sZ8xPiV!9P~hXQ z=k&p}EuNiaB~n`16Vz7pPI1q!(ev1+J!TXeC6Pr|cCxE1nnmkHozT@@_XBd>WI3@T zt+6uVKe8n-w&-v(XUuJ)zx$JPKr@+?QW?R38OLw{z!XS*GKuHeW- z{>q>r=IDmFKf=#aIMx_Q7u-3c3V-7`SuAa_)tpT|0PF*_uJWvOuhZ(NDz%eh@;Cv} zKgQAiOIAtbP{+@WOH}({Q+9*)6!N9tZdO{$vK1hK6_~-qJ7k&Q9ZKyR2W3>#8av24 zm&Z;PF|D{p&-gF|wCYkiw%wIF!j)oF)pYm^LV|}x0NiW-INfV8R`eM{Fl8JCuFa4e zqZU_xeK7(|OX*+kEInCfVSeU$_CU?M;AxKaSDa*%9$`}4#uDl)5V#qc3rFeC{GyQb zY5Ssr&@K4WZ>t=Jp?)fdZoA6gCYPpQF(vAfNLrc6<|Ce*yslG+S6V+8ju+QMb3WOZ z=CRjwu*x_+w!kS-k7rR;6t1N0#vxa^Qyg39O6{w6`MI~aK3Mx8vn2w2Yo{)fcY5xR z7^f6CGhh-H&b2UTC(iX_$l~OE#2)4>SuM2RZah8hy)t?Bc$6^H=QX+XJ{A4~oa>7e zV2ckib~S4NoX-bUG7TCYSY_6Zj=9KOMCacT(L9z}y=9p4_*FaouZZLx;!j~w@+$@F zYf@Z{!?kWD@{#2FOzMnX&5^r&I8<*R1x8dvoqPESS6ezYbO8rHSTo_x8m@6uX~(Eq zMJ{Cyc=nRtteO|nHZ9Us?=EFxT5gv}xrjBI&ebob_@%j}Zd~`$0V=!c^fX@oFAL`X zW5IB{lhTVTa=a`5vhqOTo|L<|_u4&V^O1DGYX4)k2Rl&I-vFk}$BR$}dHE@`J#Her z=(yeg@#fgymDAh~eDjk0!QDmPC_E2p?Aq5`6W`x)Njn-;VCXYrXGmt3m%1Bi#vRVc z&>r5{kblmQFuK?ju*Xc6yM9j!`}E0suc0?T%#D{pC=P=%aba+f1UeguzYx}85`QLiVLYg68Ow}5W6}MyG z_H&Qdbm~1B>{-Ate6^pa;-l}i>h-6^cr7PD zp>Z(YrWJCxXOt?ZA*|!eeaq>>aqAPB`1YaW0;8L1>YGZj-B&9sDO~nw1&02m%I00h zam$#;Sz-{S@rVe3b%(r`4Tfbri}A*(A#Q8TXtYMVF!m75fu~?)utS{_rLM_wGdmgl z5$nqjwL|V#+EUZ(z)l2UhjMuuT^b|R;E|^~m6Ra@#j1qOUWGD#z8d;VzLdsww|7W2 zhU+fhKD`6X_mup#81!S6)hqvAl$7eehY^Cxxsw#zqI| zly+g`-ubpB+RyJPczBNG(Yi)VrH6*B{*@u6aX}_Kap4x9f<{w3rD0h*^I57yHnRPb zE96s|u1mu(-+X(7G_}l5QwkYA{Po=U`)GVH$8@%X0! z=jp)o`LzAWr8h8ZB8WLZMEosXIjq3YYu%U2_{5Qv)B2lsFb63V?%0Bq-Wns%PIBY@ zoZZ@*`N0UMg~YUe_^2THEN~3gfz7MRz9?V`g)(a4HzK&2)&jSZjl>UKmZN9@!y>{w zDSSotD>#k-2oDC(<@AdN2Wh+3zC)2(L!f>%} zC({Uvy(Hcgz7bZ+5PE?pQpai#XguEkC(&bw3D* z#aXZZ0sOPca}T#i&!j8g1}Q>M4Z!I&@FwwHT2UP$dgdbiA!}s&^%DyrgWs$JOc>Vd zJCKLYJ;%=+P}%Qxi?X9mBg8#M!Wz}G9BLs8(*F#wsx#IM8HB$3r%#E3O}E$0)QuNt zV35DOtYxZ9v?42~`7?}R2;`c5CWkb;%ro;rx=}n~6Ox<@Z`4PNxQ7mT?S3f^A-202 zeZi&m1Fs)lc8%`2RtJN}2Mqpi0ua`~HqzLjl%FIYKuJ3N81`BFd>ZfRyQ$e*%unw{yx zil=BAGPu*l_lM6x_Mc-Ab3~$8^&_pgA z+Uf`K+FY*lLrgb}Pmh-z-|3+uwl?Og*{v&16ICQ3Lp`%4 z3e)IwiN9VZ^QBLi;T}Qb7n|zFXndbs`=~2}?Thn0qGG)`<@(aMp99+Qh;L*)VB|8% z5-z!=RPN&Rk(+i<%26r(o?wcv<6a z)88B@oLZ3`4rCWmc&olPoiHE?`LdGvdFbd>gzaHNW9X816kif2O>*xa-%((r8x$Q8 zJs87UVP^YSCOK&Tohon#Bc)o<>VDnB5JHf|`zo+KQn+CU274!Z)9>YI%GKVOt>oDD z=LndVMmbhIz|0!T8It4asEHPuPpw3YJzH4N>ehFQ7yMAg3j2QB^TRP)>zYPnj73sd$ocpMNkT6Xd#_BrC zKG^-F`)XlLzml?GXVMA7?yoi$dGUVh;qs+)Cg$ci4;j6_ueI|?OJ;fzn21}7#gloJ(cLvm?%Mx1Kn1nR3U{Uhx|!rUD%1J`HdA zNy`cg$<@9Gz2s>SY@BFAh4)M&!ia8K3nw%GXG{q5(F0l^u}`4|io4$J6nmC1NNU zAc@T3IqZ_mK>HJR*yu7^(Hr~nnGD3z6#n)FJ6rxI*!i!7v>^>nKzz~F z=R69Dto3iD47Oa7CyGG3DHJN0D4QaEXR=r?mX!d6qdFjd#Ojm)o>LO*qHexv7Lewk z)kl#{HtF7bq%tcj?vq_06h2KiS6M)5EH=~Tr&q6y>W52()G}9|8)Md#!Oh2=uVcep zl5-L(%+e}*?NIPtk*4$Nuj JUsxhlF?K9%Cd-@J?cZzOAbJWHcu;orZ;ZYvZBx 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 literal 0 HcmV?d00001 diff --git a/docs/pics/severities.png b/docs/pics/severities.png new file mode 100644 index 0000000000000000000000000000000000000000..8bc51daedfa162e5680ebdec295e16aba52d9319 GIT binary patch literal 20307 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 T2~C7umUqGgW^P!a@A~|Iq`o$& literal 0 HcmV?d00001 diff --git a/docs/screenshots/home.jpg b/docs/screenshots/home.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f7b11e715c896941569c1598ccaf906a7990729d GIT binary patch literal 257739 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%0AI- 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≻TCK92-p_oRt$H6f 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 F{|8YrHLm~w literal 0 HcmV?d00001 diff --git a/docs/screenshots/nagatha-1.jpg b/docs/screenshots/nagatha-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..886c6bb122a8c189b25f718ff94ea4a7b8191b6d GIT binary patch literal 75675 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 In?Je#1)XqvZU6uP literal 0 HcmV?d00001 diff --git a/docs/screenshots/nagatha-2.jpg b/docs/screenshots/nagatha-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..744ef4d8db65efc6d3a3523ba0a170af93d6f9ff GIT binary patch literal 53488 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ⅆ1C4)}aA+WSg1Z5_{vYSGFOj&OK`ORiw&g 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 FKLCNwnJ)kU literal 0 HcmV?d00001 diff --git a/docs/screenshots/observations.jpg b/docs/screenshots/observations.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c78625b195eeb9a97c24298f21be1de05ecbd32e GIT binary patch literal 311413 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�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%0D0lKl(B6E9s`kwc zulZ}+pd}^XNB)GUk=l3dP&*qLa0jLBrhY<9rAFTF=jm9vQSOMT1aN3(Vfwtvp_8$Lqg$f zy2fMO;kcHl207)Ed~UOYXlab@+%{wqqFxVNjT@!vJ5B%g3}k`gs|{vknZjx~iS5hv z52{Fdf00bJdf}Cio#MwjF4qnl@k&9^ zLVxLgBBMcN8Bcvs%^rrFjYPO$Uw3u3L3Hv)lhw^=nPFiMx;DHn1r>6Vr%~s@e-zX( zAy>NQCftiENg~NT633amO!<~+&F4{dQU%fZj%-tu_CLOXY;mcylrBjM-tK?U_MTBq zrE$L~>Zqew1~H&i2N0wWCG_GbMTCF|A#{{b69`D?&5qPCfV707p@oDZB}5X+s1)f! zfCwQV9Ri_ugt>d(^S*1H_uO^Yy6c{E_m_Og-dTG+JNy6q%ddEW9+g-_*X96hquvs? z^@Wfx_dRNi0}kDLy3I0@wWvi$7<0Raw7dpj67BEQmiFTu%Z-Y8#gQKWn(A|=ThAp_>_v)eyhlZuGtVSE9O>RXd8^fxuo@1FnB z1$vh=Zt^N7egfrMmS$4j1JkB<=u{lh#~WeGan*oCyVz+zXwq6^yNA;0omKZ}=${FW zvhpc%1H%q;c%pTjCxV=u#3ZWSWPL5AmyDfXdL1$RXVGYDnWM^g(votbqPes*>e^U9 zJ*KoU$iPH}pzV}~TT1EiQ#+JHPEzr|2pTGz-voHc;2ttuXY#CE2Rd@JW|O?`RQh{- z3`;=l&%39by366g5XMtdI&K@q7W%11KC5Y;L%-rAMlH(N%ynAfzwmP2by*&&uXyEW zVG5^vcco1|yHQ^w|Hbp>NB@a1```OerA=iLuR#;u9OV8{(a^pRNrPJU-Stamr+tWd zU#;g^vT}V())Kar+pj*-`Fw`!7l3`j?|xfX?#JCkN6!b9A1koP4Z1}(JVEZ7hm{Sd zC|felHA-l}ui0x(p$8V2=26hBF--b0aGQ&(T%|Gk?^=enlCWZrY(!m&+KczY#KoLA zb9QXL;KN*Y^Sk+-xUZyssAjkf#lMe@AxvtizZwfV^*~XH>B~2a+O4#cOnG|eR`%4s zP$N(2KU}V2#Ix88oOxB`gP=IS%yt{NfN`QV=~|n@^7Dl1GD~8K9643p9lc}IHe`+q zAWtr~oqORL?j6VUj}+;!@U9+xe9znav3ZgO2%R2Y-wTClb`mR~!0|xk$N##pr(U13 z3spk9d>JZTb{buNDQ)f2{twq>Mz&gs>a4J3+-Hu&ga39-iv3gg)G=)HZEOAN{reHx zuj8NnkMC9ar(BAMcvwogcK8yQRnN&0+3~@J4uN^ola)W+0!y3slJ35oz9`gcjabCw zEgvf#OxQjxNanGGCfR%njw0kc7`I7^VUHK%J(uJKHQo$z2`aixsoVz(o~rpF1bM6; zQTBbtx43r3M)TU}-0h@U03SJH>=VmkgoxJ}+H6XxP#q$gGGVT{db0-I^Z}WYCOyrg za3Y&Hi>uPBK=h(5g&@0lWi_^ed%6aVkBK1SOp`*v=dw?nti9RZ>NBSUfu-^vp)2s_H1-v=~AH*Rn@L zADP}fHI^$Xo-^VJ8hgd@)m$% zJrWS2rW;SFSReh~Y#h3;^PkV>z?l)s2WUc(Nswt1hVPwm&EO^MdN3LcO#6j*RSU-H1hBUc*y}h^qn0=fb9+ z&NY?qJ9N^>G#Q`MDOW8&3H~0V)-L30RFwotA>{J#=zTr?#OYmVlyif_UhIqcu=gEa zWvf+v7N)8Yd}T2-yrf$R4g+>8u%-PD07XiU>Q{R}+PHMmU$8giE&F1nEg5~iq8pj( zPo;lXzUEb098O9XDEHM@!RD+O7;ARsbTNp}b^D$9R3rXel2y#|#Z~|+DwNdfqfkR1%>w=?c!+JRBwo0toGG;&*1VSRO4jV2qnL68CElq zpO11G+c?@iwfW4X{ca7nE8?}@8+ASotRbU?;+Ma?4@0%rIrrgknOjufk337eCqm_! zI-TH4ePyGK3622=yN)RT>H4dxf0)RS(s|Qs*|W$nR#*4D3|i5d_)E>b&^rUZB3x>Z!VAU_JzXi&z7pEFku4+7h}R^@ z{_b+__w@L7%HHVgZRzZfI`qywA>P*q-6RDm@5^Ti!g4$V1MB)Ns(*ZL!DvkP*bY|_aN?F%^4$!~aVJz7{L^2%ec#E2 zUq=Pyx?K$i#}N(#|qrCA^&bV=_CI$P7niOTUA0URX7HWibhr_QxloY!(_8MFcSWpE4~lFb(hOB@wLkg<5( z-0LNY0B}FCz=@);P`*$~qi`EbwJ9yrMJrnaXu9&l{pi<&+krur1=CpYp^W0XjAFcu zSTO**w~y5QAP}ypL^3BY3D;c8%oa^}ID4NN0@$jwB-#!|fLWH7Q0UXoV;7%)u4Qu5 zb3`)D{Uao7A})fo7k?B#hlW`ml5#47SI|R_M8J=OSh4g`U=b6kxn`#Kohf;1@K|ls z!?2QtJ~IKU)s;@)E*(ExkQDur@#~T9X9ZaN5_)OwV;RK`pAkJ^jnGt&*I;&SNp^HK z1B>)Nb!|I|fz(y!(U={WNAwWnHb!Z;Jd-k(aG;8NOb3Cs`9=bTn>eGVg>@Ps>ma=-3k%hci#m?lQnqZ>v}-&V_K z+i%zMo|$a77%8#kUg3Rk({YGyJ?4hFkBuv|W({bmDUoXI+a!A|ijvlyhVE{@8|8kp zwjMX;zbrEwT8^)0u@xV71Y)Ic`fRGH)tB2&C-KClsJmH1*+8Ckk?WhI{u`kas`J0r zJIj`rI)I-dgRs(`90{XMDOJn1ahD>sgOGLhS%FjUvYuYDmBU88a;yXmFDmdlW;m4f zo2nyaz(TlIA>Y{pUboqacH;9_!@XAnDb6ZFrn+4=OLrr`5aD^43Wlns*89Zt>gwx< z_a^!Tbcuwx&eGnabj`Pyng}Ls3Fz~0`(stzzf(N|hv~S3s!)dd@D@y4XkZ#C++xaSvAuDcbXz}B9N7<{CM_Ima&elS#z-P35ipUQ; z9?nrczx)D;!-SIsh62d3L_#cXwOZ=g)s-s$C$Eqq#h*w)*knRh#a#%}{e7D2ZmieS zs?ptMl>75lmW|;Ad&H9Wov@tgOKz0-+fn7Ly<`TGR1W$uTo<#Lr53K0p0u#i8G883 zd=k@VZUu)YqJDV^(=Z$0IClOR)MRhySSXjgnPsL-QH&i(ECImNPBJ!L~N-GDBXQ=ADPWutIjqY6*PXibpyiyOh0Me(`; z%!KOa0kihv&u2!}?cV=k*#mEtDKGVQQ!7!{lwPF!rh+4!b68rWluja%M6LMY`G*-_ zSIOSR1ao-{0*P!Z?{9x22vKd~{^Hhv+75A;ToFKS3`U4;FXV9{w;7Y$#ZOo(pwTC# z*z6L%!-fiMQ&0SisPvz*xd)C_B;l@Ra~#Q%heseb_L-x-?0YTbyKAH>UA1a?3Er+v zP`cIE8mLWA9jkDl5~lbgR{#f~Jf07?2l8HS3+j?(9B`*knZ5agLU%xx7GrPXF}Liv z1*(+9jSJFmc(CW|)nS3=xU+tafp#jT#>v?K!62hP?=!z2Pa1)Ih zw78ho@@u~Dy5v7xGtNT#eSms7`+1ed#ze*^AIH*LS9Ul2cT*pre^&McNFl#(9svwq zbwzb&Ckp!)!8@vgzfCUjQ(Z%yI@cYHLmI7Jq@o7A3R7yjzkZ)1!!fExVg{@fOBiw6 z<%6k8q$uxTqRsTHc|I*HM;Y!uR*T;jI}ZND`Ac(hR5S?2@L07@>RDF zY`-}y@04}{nbHxj4cy?T2HNUKKybv)Q(wy5KU~)?W_?-v3Ab2J2o{X7^Db9&Fq5`0 zHa2Hv^QyC++7p3ioel-G8G+-S)hOjtMK&AbMa3bTXG8fsCA_chb{oxafItwynH9Ha z9kAs$m{S3W1_FisjgVT4S4n^L>%`Y+7F2vyZfI-LYl}>ls@!L9C0V({g*yjbWyHjY zQpzQU0VTJ!uy3S3SxKjGsy2MtK(^_8pS}*A7GLIRmIcz>RAh$sc_X)pjtdn6)8Pqu2dD$+fs~mZ6`Y zmqKbfiVwhp!)a`1Pbrl;Yu4?zPq@Dn3%6vbd$rZbt{8g9cLchRrxv$EuYxWT-2C9M zynBTP1}30zF@=SMkD!wOwJeatHh<3GFSm8R9^-CTfrRW|*psBN2nv?U)2izR-6vsf-A37K(k#?ZB};$KZf19rZD_3es4+MOz4iO!mCn&3wk z8wtL&Mj;i*7RRmM)uI~CKEEyqEgJ`X?;FFVTv{}$7tF<78FDXU&zmN(_Ai-u$MnD; z68-YLzb9AE2KwCVU-*Zsn-T;g206#kAH^Xmf3<&FYS+n5$4PhpA=G>CN?!49oKv(> z`1gK+B%g)y09_t|K^70_Uu8jo*ym#X(Lx`Df6L>9RN1)rjez#;#J&$Ou0=zz~Zp11G()88KPFqmPMx>Jvy@r`Cx-|6E%d5DbGQI)tVUMbNs! zg5*DY75U0lD<#_8DNBk^DS*3*={h!B#BZOhA^kXO3U79jeIozoV2<9WC_)f~&fi`Q zDj-93nP{<=M3}g6HJ&Wo&+oB;7dzp;pnnM)^(B{bqsDtM(}=kt*pT^#ZqwUgcW)Og z@Ym;ZxMK>q+`u5bk)>6CJA3}SZ>6PwLR;|m!!yYD+ z7>kC`2a07X_i8s!(rp711lvmSt*!+b&$q7NzmCQh83(h7G}5qn2(W}-OY8w(>gRRV1QAzp^h>16bz1s82?;xhO4$QiSr&cD8Yo?dEco+)yV zm|7Iq_kG7nXqa*cTPPmaf53Md0ATsli)1_27E_7j3|vu+Pq;Te;!08NfK}Ngv}3tV zVGai1J`w$PnD4IT=o_p#8XMo*lHaNsU$=KaaxCK6b0#+-+CyU0{AwPhp)~YnA<0JI|C^e zn-D$R{S7&rn3AhljF!j}QdKy*ucH-7npPewxS*v$ZmWkMqd$@LI3)}u*|3v&1o8XL zxomKl^5p`zoUA~#^tA<6gUJY3)3(fs0?1kLJpy5BmNK^mqTc5ZMp)J4?V2zmRYX@+ z3zzHl*OPlyYUVk5$-?`hlU0NM6WsrBb!yP`ed&w)O8XIcl{n4aaPudaCc9)+{>t#2 z2o=qZM50+j;(armX#I51PqB4vVel{paXtUi$vAO1P>st%J*^E#0KLr8<`MHR#tSQr zeE9L=@%49D0#PQBt>q!5ln<{yPmb(Y?V(bC{+6pUYAcsA zOOF#R?DCo~#g?=1tY#?RQr6xVo)WE>CD^b+nC^Mp>r3*=Wim50Wcm6_JXaS@e<9J{ zy8SwoHtMP<#a-Q&SYT_NN0tLrpQU63k#loAccdUK$bO4er6gp#h>YocN>+MY8((g!2@KUoRjBfa%*6wf#9k!;l8&2B42Az1cB@@czSl+Z^pQ)8Ue4Io(5Sw{-z7ZC zk4b*+l~nywcAwvImgh%9=dP)xpgr$KUJi#{S`o@tWAOEScdi62)NMrkMnVE`p@(3& zn{lvrwNEUev~FP<05QIzXd!c<DbhwWD-dR}YaL$4byl>)<{Aw~|o zy5qR%^6@JXNqzR&f4Dp~^Y?)XT1BAfUh*DqRLxPY3;JUA6PHBxC3*Dc8in&8Q>k`- zS67P43vm{V3|q!_(H)>z5GzOlykG%~I36<=?HSp@sA8OE*yQC!GnmUxGWPz5tA4j- zD{E1gk3lS+Re{*BTYd@*nY>thXn(V%GF-YnYW0ebvf-aejFGVN+DmAtpoBkpVJHw~ z9id4*6C$vr8!-_J4&TS0fB*hrNDFl!B>7BtZe`1!}JeCu7RV_6&R zhLbVKyo+zVX8B$#o)xkHLY6j?qZq?^R&6KaJ<-#hO1cS$!m`83<%hnE($6SI#mcb| z;_m8Xc@yh^7{6(RcNHVFDPcV#bv*1;or@EqNr3A8#={R&_I?m!NqN&kp@=_UcFx!P zFL2C#ORvq#P!-JS`PEL0U=qaz6i_+_{fG);2vE0iI_v_`cF2<@6%ozZcJ=ma8!_rP zug%WZcg_BwszBTaA`kB~@%xug>KM^@Ui|v5@LkE@e4=Uh(l#w#_YjPgIER}273>76 zre$a@<&An&N${~C-S0rO)~vh7iqNO79!iu-~n@86`}h~i2y5CMweEX&q)5e_Nr8HFETs;uSpa03E5r> z5kxy3U8?b{B2-99a;WR*%CNYsthjukmf5&2gDoO1O~h&>jP3H9?U{0KmPf{14$r!j zC1&`pkTm4diSOn59)v0_5YXDfp+Tix6my&pftc`Vx#8x=Wx;gqiZ@Z;TG<|+DuiD2 zxJ-{Q;GVF#I+Px8#P2mj1a1|)P7Nx|6qNWl=Zn_~cCY(hns4e|)L@%x>vLG#lYx~8 zH%bMAzQ)hh=RUo?WiqawpgwK&p5e=f%#N$@s*kH*t9X)Ux84FcN_fYyIo+;Alv${U zwjvGH_*ivR-S9mu?^5WyuSy7QIW>O6z`wDJo`vzMaGn*6hmMJi^2P1j@IFZf zCOZZOi&eAW16AUzl~cs<3y+At(WgH=qiRHtwVGxlDyc)U>(@)ri+R4EGeKKw)Odmn z3rsp4FY_wWd=4RAlxoCjPNpBj3ntA4hW&$Q%@^OZcG#rYtk&?jtd_03ezW61^;IiE zK`JR|dgjB_^ph&1uei_ryfTRhu=?Lx%lSt-bL=hm9u<{_1^so=HOWKIL#?J6b-Rym z+Jm~xR>NT#{<*Eto5erB!b`EN7skSOuV`k=h6zg~Bcd$H{K1umE)(jJnx6&o_*wF( zQQZj?akgPfX3&g2oLy38sH9uct5wh#t!SynrZ`LeSTSi~r8+4_r)j@xlBT`iIo)m( z`1i=BzFh6m8+CPL*Jwb{L?+PcM^o+ygi0Q1m!W{I^SEX>ygLdX7FJ(hg>jtL7n*K$ zscn}cZ3j(XTV^lJ^VsghE|#a6Dw*?sEG5-`7fY4#uDB*HK?v7_bvCSEY``$Ap}|Ng z-EZRNl)~k~b+!4B;GvbnAaw;ZvsSIR_;3T@X?a8#=ahO{@_EHeQ=G+rr1+;j9pK2( z%Q!baZzC<9FoReD+UZ}ceg9xSG(qyFESm4_Ag4XMYO+DhXe?8TKs&Q z0&L9T*Y<}a$UKI@qKx0J(Zdqd!M)th>id7G%#M!dunME13-A*Q3?H4}DgMq>j@l|) z0SyBU@jysuml{yF=hYsoJ{86_Qpd##2WtHG)izi0)%-UW)L6eVCkgN-F2^m#Rc%OAradVQhNQxE6iw3ZZWdh-dJN<(8XC%_uT zAb(|mJBC0!km8Oo&qaQ(oR*`4MJ1(ku_w=**3i>@)m zQ*S0!gS+?*MhG+3ON^kg6%=w;iv;91sDJYt+#utphlf&5kBZPZ8q;KEi%$;ruK$;^ z-UhV+6S3}Ja&@SF(XC2{ZSTL03MWo*SR;0(MvSKH$EjyDBP6M6s9sp~%dFD6Bh3k8 zwQX&_#l|54tfEr^px{u)a<0Y_0hu^_X)?mqbF@36Ur7@}!lL^9ip4x@w_LYHO=`Q& z4r#3COju}q8d^FdZh0D7J@(c#ye)ktt{VJ%cs(Y)sxVFfPxL863spk;1L}acEpTin zd&nzD+asQ{mqo1bgFfX^;qAGWRg7p?wRqcV8Rrl%bQRWR4YM{L=Dip^?4uv6W6y}4 zi}5v7pd? zX#M=4S(^+%@oceTl$d|qlV}ddZMA|ODhuap#LL3hOQ`Fw`<}CnPYapBn1w0i-4HDfNoSH)tUr?VP9wXPM?8Yw|MX|yfvem!Ol4-loAnLv%0wr|@p}!Tnbkfd zdO?Wc$faf{=gFtWA;#|*94!}zYxPiEPWq6DQl4FlMRIah$-3%=*%;ce_L_^x-vT5ET{OaHHs5nCNiiT|tIiiOWK$)zoLRHwtxh z`z7z9Pk8%VQ?IMQ3A{WDTLFdgEXQ3b=t8bqChSXi59&+Fv8oNlnw0N175HuHkNs-zU>!!7s^yIX|=X|~kU#pPTJMmgxAG}iI@41^; z_NK&dhc{|_`Ve+X3>F$gyhHu$3cvfUaRlpFY(F5jsYHiOdC#^$HRFH}S+a7zHVAkRj7|DvLgOu#OX^e$b}8nQ1))vSPwCe$s&XRbAUsb5PPUHazd z5ZND~}FXelKJNkZ!BH1&RoGq8-{{%*mA?He+BO+I-RRbYhBRj# znLNAfC6>yPRsz8}Cb|55qe|F`lnLhs}j zbIcoynb9)?&@bVgAEPx;A-=T1);@>)S_^$OrNrrL|8QN3mevi@qcFm$n~A|H<(@6z zQ&4}1O9N0$y;9HB4RR>|>*0@nHLPEjEBJ`!xU0()lN_t`TN?9?R4*G1&h_Yl1B*Of zl9}gesng%3iLr}~O~$z1U$qLLq9D{wi%+kn&OZ2~_Xb<3s|VnwDn3bkb0yD?!`~fp z@xFa$G(9u7p-Ywuc4QQ|hfK7IX3L%(O;P>jo{WM|~OABcAZ?X8@nCLG$>h=D4#2|;@UCNshA|La@VpV03=ZjD@HP~0LBi|ITZr<@^cU`WC5 zzNQg{dk*Y0lb!(sep;995CozMVUhkh3aCX($^tVWJ!VdLFCxn}|K#XUm zky4gi0&s>XA`L|(l}@2V$3lrTblG??+n1SWiAwlcw~)L{@6hN=?C##d^q-Ss;ae=^ zshhfUaANm=Zx1~EU)uu#{ACRWaO!k%W}|GEhWS)+Dm@x=i|s*ql+ym71>-|)CZ15g=RjS*u#ZtBV?bab=p71ydl)A2e z=IPa`orKG+%X{fHuTWC61=SC%n57eXCow7gUj*TZMh9iFdVrLP7xUY>>r%ND;XFyC zmC+h}*P)$6;u8WuK=pWn^(=ADb&t{_;IoS5hv+|sDN|W;*JF%xYy(%PE+#uE6GvoM zl56aLFEp8n7~4Xo$92zzFRw$lHvF2sVID})d})f7@oCKxZ8TE4u!z7>}AO@Ij7}&jk$L0NKQAzk&i{Q+wlf3bVcl85o{CBcn4*@qRfaMNWQ39-Eo9%fUqq*N!y+=Ydq3F3gTtrN zg=**v%VPh(NX5%*aduS~g}$P@Gp|irB%CLf-)%Pii*?=QrNQ20Y07E@?xfPPO?v}o zM5xj^Zg7!uEI9mx4`*C7gklC-6;b#WW}B5%%nXbW-R7PpG2%aBhS)wmP%Bh30zCqt zZ(V>6)}70|qT8ncyMdg(pWpxCVrX6dY5enC)5vjeW&MXN^6o~#_1c-np1|4=@&!=i zL{Q0o_^5e*>~UKt#ktpJwR3KTrU_DaO4ed{M}HyK7tI$p-7n5VNU09RwRz`d7e}=0 zwc9v>(N-Y%`4D>zeLBarhp?@6p+NpKTHt>0fcz?`J~AC*AblX066Vy@(JzG4_Dun7 z-{Ruylj*%@1C!0R6-9@-qmQyJ(nfwS#o()_gWRnuR`S%_7G?SF4fO_q57ldI`!yZ< z!&i6@ZC*{OMFlrLIR|+{qlq<*h<_M-*r94_&>5@+NCdVdNf@tkhm(f1DlIO)%()B< z&vXWtH=)ZE!G_(aJE<8CIbbUTwwFzA1ycJ=omxGCusxC8d+7V&w_M{N)2DjSSe99< zO60V;azdH6@IdqB2KmdO_^5eL*_2-9N={>V#nRxpyf+JH@)tlD>QIca3(O(3)SgM6 zTe93#=YG4?%+O&_rrr2p}-~UZT-_jWw{FH!s+`HmMC|fs=D)l zrSgsh2Q4%&;peHX>DZGFi~7lh+`Xg~(vpPfdNio|u`aJQ5VK@{a1M?!t}jPBaSttr z%UKvl-HVL})E7HG@B!^ik*Zl6fn~TD*~(E~T`Ob&+TYs}Xmw|2qaI`N$@2Da$WNWx z#apbzhf9|#=#L-jI!WFXdC>TMZuN+i0*3`h1X{BknF*C86#hJgSN*#zL(pJjJnd z^+XI3J7X;VAN^0R_Z-Jli?vn`UI84l!Dafj>K>utpAKSQqdn+X+Vbx@al`$z9r^`^ zoaz?-P4xLMRV*~+A!B$HP7u^o%g7`qSnKn4JVNt(d?NNNWlLl*03VeO0$Qukt1@LS z&&Mt?GDbEU<{xg0TBZqPOI15mAFlRU_TS4d5HtQd?nx=`Y}Armpxxjz`mkUlUY>sK zwK?<&ZGd*ZnLcBE&JTNQz&;BWT<07*(Iarw-#|K6KJlF)f30;JK#;BoxMagvUL2V8 zBLM4>+Ae(8=DLG;n3K53pdp$!YP3;(ZT*xmQAqDGwg&R@;Y^73kjfw2tv6S`*IF60 zV#5Os;2!fyW8F^Ff-u;c^(pZRd!>Y>W89c(vvMW1f3)J^C%GDjn;5N_GUYm>4FE+$ zh@X=@B!~M=PMt;S_y0U)UAqBM?2{`Et}^+>Qd}au&PGes0Ar&|la)X*3)1|kp)&8T z4wXD>)WP6c5G*2tFt4$l=QaDjZNn^i&iBbqlr7`$@1w~`D6a>RQ|&`2ZJP91Qj75( zZ*aAo+~2PD{A8+vAjmAt*~)OXFG6r~SELp+c_IOP?XKa;6ED}iUrcMJlXSalj&2X) z>U6fFQqZy!eXYLH3*{NN_62hBKJgf*13aMh$n)SPz#pBCdBWpeV>y$FoK2liVuU2+ zt-BHLhj&UJpvuC@Ut=4}Tz;{(0b5!=xKxb%A|g%565)B_5tjEDjp7a|Y#d-oR>t2y zU=xEI1pq`AZ>oWF{}@vohW8=-QfaG-wAf}`$Ui-ZRPS^6k7`kqPjgX5AU-kgIxk6N z+m*yupG8?0b3bvC9z`~>=L!l){^Gka=W)L*uAqg0xK~9 zQn-`tL%!!)f?odqN!c-DI;5CLUz)KyUG-K4pl}hAmIKrRBmH11p-d{+z7a+cEf4?J zZ5o@an-4)dB#p<9=vsqN834wuH#sf@Azp|v;xH=d$B*>G``!zLR9++N$>%$;MMlpt zVW`T2+(+ex@SV!Y2_^xQd&GFs zg|G3k37xq@Ly&%unzi9ca8ab#V*zxoy0zkdu_wVUNLRb`7OFmqK3_=r%-QwT3Lsx2 z#+Noj4|40qx<1;47ZvY~S1xYvLX_vs>vCSC43aOEpdI9*EPGPHXve__k>Pq`xizbl zs`yOE+XFjouUk?lui7H`AOm+=<{_Q>z9(=X2&QKODn~+BBXZ_tz`P*}U-P(Z>~Wvw z^5^$w4vH|d`^9jnb9 z-5HCIafq_6^+QUz1xR+5!N@(v(Sl^F?GrCNUO&3xm85@pH~fA=ovejNd^;IA%6{`j zkf$ugd6<0V-*Fk*9;eA;$@@sDPEW{RM))_s%k{dx^U5tF78{ACM+O1b)xQL2X)xuK zS_mmbDGx$U&j=x*TI>e2doSt-II#BPxJ4P2mNHpuk@RgUQzzHSch>btslF_!!j@Sb zP~bR(VqN8Csth>k^wq9JNq4M=YbDwvY!{e*@Cda(g}mbU-MgyW%4Yp!DDIH7N4E%i zqV_=#*|Iivr0}YA*9>4i)a1iDY#dx=VZr2XA77}ejO5pNRx{|7m-OP|!s1k6;R}sN z&yz5>rV-#WSIMt~1`B|4hu8^c+dwIv_Mk74j8WO7$^A<(-x9Kom8q(!vCxA$31oq* zQL?ej9oCTZ47-uH_!IoO;GnPemu;5|^*^r9=4t1qu3j;;IyGe92oeHx{D&$502kmS zAag@t6_{)d6*0Ikd@QyjQY%xx%sS0QsSuU*%b(}<9J}I zzjP+)2d4kv;)O=0Q)KHcpV~Umi>aBw^s8Iz)f!{FN`kfdIAncR(ntp_EEw|QZNXl& z_n`HMX_&6V5UB!WwxcCyRC6m}G$koOyV`cNt=@G32NsIY4PT)>_hVyTcn4Q5E(^G4 zPCqG1eJ$pIyp=gySl1-EVQ}E7a%f-G$fGvP#N$hD%Pbk6{3u=-Ov)=j6qg_%7h~R& z)w=ZT{)%M%0(e@AEMVJqFc!lN#9R9j!wGo{&8x#TMZcc3gtUaA}Y)sT*`wmdM?SQ8S{2+sH-X90}uwFfgF$;FAO$<7OWf z?3p_i$@iCa#yhNNv)=A>JgizsGy85oh=J*;`pFT>ko*6xHB~J1gqhvmN%}q6vhHrM ztd&aSQ(>$o$9CBG>3y7&b{0;FkFRaq&F_WeLQeDjg0#Q~Bj03`0>0%KyR0uMsAoI+ zT@4(T<*5B>e(2V3EY7~+mIQaMHrle0xzFk6;m7-w?s5yAKXj@!y*kBfPEzes`RGI_ z532cODKT6R$GjKj?dKQ-rZmz}xzYKT|Gv;0R+8a#myYYr8Yhw-ktgI+?TL~_5rPlC zJNFu*h34uBoF?`VYIGGNy+0lBxu9QQlMB5LO>JAGm z^&k9JuJtLf;AIrQ1MkKGQSN@1sG zZ^&FS=xM?Q=YcACh%~-^y_3>1r`Fhwkti>NJI4|*kxI)n$)6iyfaTJufM;(I!>Ny` ztd|V$mG`51(R8Pp<4n@c7dFSQ`gOIcIM9VAVJI*lw!HJQaxZW(+Bmr{X&zfiy6!G6 z=A%|in~Br1=K&h>Rz_99UdPt90M0;4HYaw_DgESLr)tEBUe#9B1m&8dd7=B)ZrR9W za>VNR$l@s?y-=UnazkTvRFmkltBsUr5N+27qXCUJLypZLa>v!TS>(-|cIA+JL!iV+ z`J{$r1X&HW5F1B%aN}p#{9%POV`*~%!e@JDGu$7PqJnOJ6i)}gOlAu zN9S9Dh?SShI#KH3uu1-d$oTP$eY~rw)p!Fk{U z&r!LYf?Oao*+Q3rG7es?Wt_8$hD8?{B9?LZK!VbwN365x z5@hs!@eSBB;nn;%KLH%0w+#ZsG+Xl}#K zT7%aNJKOLi`)a@KEoOQ!JNB8!-k$^KRQ2EF7@w&u+>fY!81kA$%ztPzsOT*&yhqYZ z)%}O-*KZe&&%L+BtIb=ale1i&!hkK9chD^ zy-Qsw1#Aj^n}O!fNQm_a)cS&6rhoP;?_tkNh2xt-^askL_e3Fv!Cfm$E;mn!;B=*# zox|p37;xG5$(FiAB)f$7^C#~GBNE91R5Q4YwD~ctnh)(v{x<;dk?;B zSE^Pc?#yLluBU%57500vo-me*DcT~=YgePw0Xu6ui4h0NVm*K0$ZrZC#g>1^i1cp+ z`xjW=&&)~9KEv!4mn&7oOI2sDQRogj**@O+Iw zT|lD35|AFb&b41u?!T>d5%aOqOXs+)YbN_7rmAaJAT%rCP>_8oop?)_rSbQZBe}nq z?Lz=Klf3Qr1#2Lxr3^HdK{8B0gxJb}m3aVoTi;2OdIr*a`l;z$rf1MWW}Ii(T#g&; zEz%F95wtWT7{J`MQnHPk)-F{Zh#byh(XLyYL+?X`cU# zs&UN&jrv?tfq_-u`ac<7skgJ>bwvQ?YhWJUr;KkGVKtH3{yvDhMgP{1FBqmmyTlS3 zM`54N$zuD`bSQK~RyxI*GqzH}3a@-1S1vJG0YFsUI>i~fPF=(IO{BG(Wjj(Q4aPme zEIdFp^X5%+3qIoMT^#&%&gqj8cW{rw$lM9S=$qmYofzlQjc&dD4eRXu{cr0R<5J!Z9x%(m0m zJKG=siOl`q`vAMx9t$1FRtO}83@&#m_Q9#+PJ%`8nCbhYp(!bKYISNIX1ib-SW7mO zNVwYjAyR!0+8?FWfN(!vEmL>!#i&7G7G5L@gnbVdhMKCU8{yJ`=hvIH5-*EF@#0&DY`8>|lIH=~!iPDI8W+2C&^ElVGi+y-@s0 zLNz|mxbho|SqZ`Q4{KYzY6noH8dmXj$A2(yjqM!Nm>mxO!*xHt!J9NK8rUYOb)WCC z)@?2>Nvv_<2&!go(AHf1UM2SZ*8^-OIYK~#$12Yoq$803CLW+<5Z~k8ePfOJ8RC?N zBl=z$=hQdJPL(&m-09ra{aj`x!qFe7s66kz*T>??&iiWzKu)fFD$|hLx`^2_Wfv{6j5A8ZUlrp%>Pz^T?7rgqNGODlN&i=VN<$Pmk-Fz`3c({)efKEOS zueB0A59bjjODkjl_}_bf{!d*kEXcxiq>d7OG==kGmVC-RJ*^BK%sYPM=IK->0dV#} z^Y~W>zR)8OJ4gUA7r(d!$YApFu4OHqhnhjJ5w$d`ZGA#}D!4_|J>J(cOC7pS3t!Z} z@j_xE3fm?IE~Qq!xM)K>u3MfMWPlmEY@&xpG+NiKIICF6%vE^fH1M=eU})1HkB$bD zn{CT?r5>1%5)Da3Ze12CPmS*Fr-X^*drEf3nCIIO2GB-%v({)MDPES;4)lXXD;o&n7j@iW<_=Uu_Qb^>lHu+d~E$>yz&D3u5n1ugjXWCp#8E?tl9ocz}?fa926+^uE&z zRycVmU}p;^V9WNN1!M~w`oG3Ad}a(Gbbt>doZwV3;zps5`qZ)Q&kGnyJg|EB`8z%? z?#HkaA$IWS-m~C;@9;uYyT}J;*;^>}2;nAb1w*Xot}aC7W@?5ZGsIwCjKsfaUOo~o z_2bNK;N-^&8fu`>uU^&6CQ-MFCI`Eben+Y2kys=a=r~n^g(ve z0|2dS+nKaSxc&|JuaxPyu2~%ghsZfkA3gks>*iC*AH+9l^-U3T+MY(VRc31k#q(fy zg7ZOuoquM?kfnhcu{!?s>E421&-;&OqKY*@lKlDXH?y;b6t7Z+>d}n&g4uo^K_O-j zU|O&zzH4aBjADe`vc3I(TLK`E7Dwtk^?Y^~T5W5-;c}$z=w`e7yLlcXQ1c0iQNW0$a8%0!tZl!-9Dibk=7WBA7g$dtL zLV0ipRpINSrJW_g^h#*7p4GIdl+|ZPIL+Ug#`(Y4dk?6l^7h|X$Jd#$i!`O^3@A!y zgAgFpQHe+i5R(uo0i_d~5C{<3SWpNM1f@3(5Kzs4fU2C#f?9F1A-+uNl&;I?M{XCy98`b7AO2T#71R<_GZ;;WJxg5gn zKZ0w13U9ZJfR(D05@{*B3aJko4&uLq0(}2G(oCz^KmQFltW?lr(w^;={W6@r>BQ?i zxr-fdU&DJYV>Ycvm6295VZwEOaVuawkbC`#U`ui82qeJ)X$@~fWlH)By#k5ya=L_O z=?~b>KEA1O874a6VAh;EAZP}lrNml3?Q)Tru3*y_qw*s%69e6`l&nMSb1X7e6m<>&l70X?x9 z&BiVJq70oMyU&PxD7$mXOXZo>-c?)ran&IQ_kQN0zT> zvUal{N`9V2+QAl&RfcdExS@XpoXbRon!P_ezD_HmY##l4*T2p5cbEA4oAbAq_}e4? z{owffA^G=!;O}wM-($r8{|%+FWLI5->^(JVPPX^b8fI;&_N8goFW3+NnojVi9Z+4# z=8B&xZ-xmYS9lz^c$eXb!~hQ;06)I>e|>%cyB)+u@7Q&~L}sgHWfzx4_)*@!KmXrY z;M*DfZERRimFS^==$}Vy_+gpQCIck4@xp(ezTYdn0lGRZ?6uBeJivaTk*;%^ngr3THqm+R7VD+3a=*y6RCB z@aK^u1>TR$Ybk{R27ey;)ds7D6kJNLTbri`+i|j9@9Z7i99O5G8&$iQ#SU&fRwzbPGYbss7t;uF+B>w*I=l`EO zougz4Lmz}yVj?a)Z%g>8hQ#|}rSp0V!LM_2o;2^?$T}tEk2sKgP2Euv^(hbGxvI4k z3v^Q_Yg-^jI2a)PQI z4y;6&uznBR`5MDUZ53fv{w6J)Dk_`>8h$mjB3QfNkeuU?*}8kL=7bave4x-n#TEU$ zS{|}7r>8ts9xPUs~Pv-`n12-uPZqo7nEtP#@7rEC49z_R8VU(L$@=aB-VeN#ITX+cS;I6v*I0i!L}Y^=mW?GIgx}_&c7NiR5R>uItQ!)4 z{Esh<|Ne^@qQ!a~^enuisCFT(N2-&_uU!5*f%s3`|4&1_Kg3P+Ag=ecYb{5EjXno9 zOYCY~<_vWG9+_DdwFMq%M=K53)qb6*oDHU-hYk>_^{vdvZG(+`Ua0M}x!ISL(F!xW zFcowMAX}EUtIW^Jo{kgEe)&{48M`Es9=p3>;8+IJB(iF-FxYJlKMN{2zL1aY=NGI+ zaSS~kFsW=vI+R}?9vKJ9YVy~Nw$cJ}l{#*aWSm2Ynr-Y$iolT1EB+cYhTfrPT4JqU z_f)t07TfeulD1A9jJ7yJm~v>+|r-UV)%UG z;aGPVzmzNf{Lc!Pt6lvY_EXA#EFKqDkBI-(=;xx1IIdWFqjj@$3167Qu@qK+y`gyK;Ym#czD`sw_ovBoy{84u$#$3zrN);H31ZqiMEj8J2khYiM&Vq1Ke1~o>b#av8+UMYJefb(q z!=F1rnA$N8T}vZ6*?qj&NG)3ZMGy=}%Vm%wGZFBgftT5Hu?^dm?X?dcr=^#on#z6~ zo43*%KXo@|&VHTI*zFCbqzAA*-Yw0+&RATfd#eq% zk9nWzSv{5?=PnZORNhuTk(M86OLMfffDNs@DZj}K|A(VD!fyR&cGFGyaKSRT<0Z)E zm~(lz?|!}iC(4_RRSZaMH0Uz9Z+VuVeZfQMubNBwS?#@32fXOEE~g!%W*&$Ofh5I8 zS4Q<;`~MKw4_q=RZkl1LgTW)hM`C8u-jN1nD6FUX`6nxk2dXHDlD~5P)WB=~+M^{R9Xk8j8`7``?^?uS5ARn%4U>9(bqfkVY`o zI$c9Is0;QlaQ*6D&Y#6I2=TB~b`BIfqIX{Nl5@Tr2v(Dh;nXA5rB}LLGxitJzJkoKYozO|GufmU*F%D zy;fp#(*~p0u*`@yw&m_c2=|?srwGK%;#%b-3S$Mb@2>+qGLdgDYG_1!0i;?VSF4=g zFR8}5UwRD%b=dVrT{piDPkfVn@&Szuct;^+MqP#7-Uz;mCe6(-H33Q!e>_?QZJtO( zDToeUeZkW`^6lH5vV}HH%dJyf+s2l+L z)5X4yA&nYy5QHS504;&0O{!eH0JOORe{{7KQ*ixg1!F1GS;Ph#u{`Aj>8k#HV z1NKL8k+$#q6Hkrl!ifVVCvP}g1pz$$R}pp1Pyqyfdzehn#~?ko24JhlwqEhuA~UEy zZK*?Q8;Uh{o$DL7mehpzYHirZWj1&42C!%^+V;^`G+OT4yfhYz3P7W=nugA)a}9Uv zApAno4Sjts^{u{gwMSP$1DBy~Z1H%3Q!m-Pmb5nw)NiSMsvL)~oA_Pk?SC2t{P%xw zQS{C9<0T|sDC@qp=LWQZ1N6r*E8D zT#K1#3qp#irR#RFubC6at;gU3w~s3QN9&FWLXsxRQ?EHo8YB$P)>?maLZIlHO(RG% zKAiEk;jiFXcck9{LJh_D$MXMvMMuSZQTm_DvQIP7XTt-RX~*9cvr3Alr?X28n!XNg z==bXkRWF&w+DY!K!(2)icSrs_Vtsd{0@{hKT5GUZiA>EZH_`WtT9H$zPHb5$W!rz-0b zIpB}CTqryEQ<JLy%-oyGmm7VV0?llMsFOZ?@7+sE&q&v3kr_@ICCHaLt*e`G z$TvMSrle3+&d&ev_MbQE*toT|foD$Eqz~;yQbN*oGwt=J7{ZM~JUuw0TC7(efv}B? zv_o?OXwldSXG(^?fn{n|m%!xB+%|n@lt=il@12s+CefnRUr*A`4a(mF^G(}jq8&yW;`lMjrNi;r3wd2t%WzJt6M)K73ZgY$k zpyYJIDj3Z$Z{VZB`#Qcpd=fP=Tg{C{hn1V_Ztg`KAAFj&fG*50AQnU<3UO4|)b5d@ zmJE~^gAvenIRAr66c#%B;jcsf<5pUEemDAg+HY;oLR#ePLN$lhUT+X*?_#Tqt~>1- zMYVRe@BWee$6p)sfBksJ`^8PUNrb7RozR%y zf9~M_+V`)eJw3=)$%#V1uP0cM0oUO7CMX4j>$O{H(_oll!!rbhQ-pm){1N`Dw3%TYQF&hD%*jGNJTe(6i39B5MN-qPq;CevBV6#${n8~ty zpogEzFpaCF-u$)aj(BeTegPQ6aIj$mg^`}|!oPFB?J$%n7c)yipO!->9jk7w3n5T3 zuQrR9DYaK)-G?}$IcfMG{f-=w7E7cAn!Xw@AACYirq92(MhT*+hgNQTz@)nkokWr9 z1vX!?iNjBQbR7A;b7)~+@r_Ww=80e{bfu-X(zM0;-OZddwqi>`ypzx3fp0fSqzuuOA+wp(TDlmECcua5S9Yi< z5J;sh$;wQ>Jl{gXh19e11d}di%T)=hPdoGziJRUdd@WS<^P0ypzt56^4=#HfwnbI- zEyS3>^DBG2OPynp`yJlVD3;OyTz!4~QC-8Wl&;~8Q|fGpoz9Dh^6iavCDmA>LwrJy zTt77zy`31zrUE<6y4<11zP#ld<5+|~d;sQWu3fer^WBW#Hhs;uc0T5f2u%k9yA)Jh zV{!W{^%}N4DV|DUfm(UDM+><#`HFKNz6`q@bX$ebehJTYz0&osiQ9j(?q8uVX27?t zABX-tl2A0ZK>dF^Z$EZBkkcf?S~zLh2VV4GeEr2;w`oVf2~!&iT{N3|H*Wii2MZ72 z=?cLFc-ii}&wO4wI1S*pccrM&I+3GyZ`0v>cJ(a@>28>g$l<~^awqKBf!3MEbKzR^ zwWH8o^DnDr>z7LpIN>?6O2T?0>vDX}yu8Dx6D5=0vr7is;kT1$T`LFTJt5Qs%>>1h zwd~vB3Fz9DNAEX+_SX+j*20kUW;-wU{SM0&62e(>`TLvU8+ad9=d`F^wO({JpRjwa z;^6E4uLR`*&SswgkM{z z!{EXFb#Egaz+e2I7vkTeXSnII8po_@WYs+Wmvwu+G)qp$$u;}YBKCc^5U%+gxw`l< z>by;1s=_O5Xu&k7NGPq;lq$`sE7a%o-O&KipgyE#nI}>P5us7H;Z8 zw#ME{hMkmpd2~V=rK<7+3xS;PP(~Ue+@pA>pu!9~+;%WC4uP0k?!x1#wO{w>%QJ}6 z>SuBOF+PbDv4}daa_iLBRtl;A*zWk%fsWM0zexCpdlL9%7+--aLGMa_b~*n7D&FWx zz}>R0f7}E|tQx%QODT4xc)zzSN!MWq&e1LAcQTg7&E8Qn@O;ug21FQ2jeL8rMw(NE zPd0SLoLD|T5B9QXn7wdcu8a1A=Cz1~f^ei%#f+%qUO6?Ae)oxd>o#=!rBxZ8w)=9j zQis7EM5IeJ7uOL)yZei)zUUg^9g$3dxlz9W6$S!s>InY*#cJk{6%*QLt>p(Kceb=R zKuFD4I!eAHI4Ky7VOT;t+)+HHY!{c@Ya2j30I;8To*nOelIhm{yv&VpDZ-`@)^$~$ zWi1`+RYtcsP)Z2jf0RJvSv1wimd3;UerOxdInF_;c5B(5DY)c$);PG#Q2zX|n{$$F zcUSFJmB`hBg#7wKai#?tv7v-61cq2ONHmgl<^CGgeV2z(Gq|n-6{e5* z*`_PDJb`l0dM9#~KHc}IP@|-gR$<=np5mgCg8<^k*KRl@wr*i^VhIRazD7pPFDP8_ zrz@v}oMY`1#x|@)L;B@?pQ2fnlnWTDDsn}_Ze;y?&QP&_n%dp!JuZ8$92b+fRm zca)~IZILpCeBd+myd*cXobmIBl5^uWz_+}}tOPrv26=Th`zNd8k?xz4uzJz+3^DB? zK&Lx*HV@CO&q^A)Bh(r3s{Ys(ABW>T-{|Pv(x~K^)sooo(*V%lt&!=Sm1}A4#jZKu~T}}(G`Clp&orc(f9e~ z)V}AOaI`LC$bC^5yI1P&whVEgO?=Afw7V^hipBXVHNqODXH`Cl~ zVh9@ZvNp5K5CH*A>Jy9ASY!Yv)3-b9yz4R#TNSLZ6Fph3n8O$bjOVR@pO0IKMIgV*9`#D*fE~RaoESs(^a~&8Hx{6e|(gCvAS;_Q_;p{ zaq&MY=yw=JS>a2rb84ERYW6mc6@An<|E8_)PI~bWX+%8$H2~%Tg+99P&{7OeOY{(| zm`kme98`uPkb77BM}}M8(KUtRhc_Ie#b-**;4>4<%VfK<-Gkb@u!6PR-V#uVfEH?) zw&|g`|NF)>JAaFp`WqOzUM?pV%_TxvXXHV66 z1-Q3TNm+Y#FRZ7|~r1T#Zewe*!v7LR1l0T2!dHcf&n-M_m z>Q0u`He-3<*L+5o!5{Y>(NS*xDk;%Y#a9Qf>vW7cV&{r1VYIaLB~u zI(TV4ZNBs1>lz+libIQY-Ywg+)IOdP0fqy#68TgALSOn4`Yj^F#O%4nYV&i;%Ct0% z4QAzV!sP0(ZDCn!fTi$dZp2)B7YI7s+LC^!eS;Zr>Izch_qsLo=>TY@*|Q5DRHCo> z0h1Qm^J-=9AdO3Mi;V^?Kndkdl)c0e0V`T*qR?;JURYY;zPx->@9Q;Q#vEsaygV2e zTCsO@Y?3C`>>ItD{P<(^DA)5e$GYy-n4BB`0m`TZiMS5dKI=9B3BU^5+KFz-9Rn`V)>K~#K>{3yqTT(>D4g{LM$7e_!ee&%m^R@lgked)- z@2P@hBgQ`bZ(4+NgTM+3r)^ zX-!Tp!md|O^5as4MR&p183^I()E*qJA5aF|qeQ{3J49A;(-m57l79G}nK%vk$O z+UC866~O~yNKYf)^lL>(XU%c*T)GpMH_KaQfnsz7I@IXfwLktfO!*%z9ac6^$-tZn z8*dt;fGzz#|CBCFRL+ez6KBa?=FE|6{}8*fdh&~;V9$u$XRk`NBnJyX%kbL3EvU}% z8Vx%Im2XsJ-89-CO%tPLPrMb!;2%%E{i=A&F!2!!j2|cx0`N$5BBN30%X)EJfLy#f zWN;Q3exEGn{VuKZ)jXdKFCP2Q$~df7p170=N8ItMXlZ%yOFjYpf@OuL2v}Z_%jzpv zLmEFF=k> zD0UTgJ6_~rW&J*{Yu|nMrhn0QuF!HGu~*=$JYQlg9xv)5b$y7`!g*|sG5q>z2Rjx| zU8K;9j}1sZYDyX`!5LrUmmK zdyBHs^|^%iITg`A#gIdVX3aWK6UKszhpn3tUNn2J;%eQwYAm+-+h6inPfT4`E;D7= z@UQkgSz>F!5ekS7fW*_O#3du~tKO-FmLg8kZlhmE23e|i7diT4MZdoXdgF01cc9W; z)l%JiS^2Xuc{>XcB~MwJn!z@wDx^5@CW51BfC@s4@~v=$ABBJE4z7Hc*PAgqotwIx zHLAK`Ug$@$T@dbYHf^EWMh(H>$^xZ8VccTW79UmS`_k-eyWes}Ou^od47X5)vktdn zx)8&@q>gBXP_2aQgyV6^^V74jAZBt{GkaU+V+VQF*r$;vg1XWbD#Ju4IeC*j`3h9{ z=`+W#QPsnqp#gaY$|WFS;fs`Sl>V?koXf3%F)I+_Sv8q#P`*~z ztl6k~U)?c{KXA*{KaeSUmNxqj1Nn)`YlXeyqx|XKy8sk|T5tQxZdqt6QlU)9Uos%y zW39YB!D5ljmwti6A)BaY3V*Dc>5}IUk^Y}lNj%-R#zQs`4gb~h&y-pM8>g{XFhM$Rk zJ#PVe4Mb+*8Zre_)hdF=#TYuT@38HvC3eYF`!S^lBrOEcg>LVb5@Qf{a0(Y^?Mw5y zyn_GGxu!*sd^WZ=1bMR6!d74rYvepISTov#9ONb>T#kW>r$%+*s1ln!ITe))i*qYB zJilRTnsb4(Y}=&DB|`jUe~EP>M5rBl>61zhYbb_$7;VWbiKlp!+jy?^aSY@s*PG=5 zn;nfh0_szK-jGcQU!7cCEd%^nlYV)0dC`9_K$a>S71^k+CX*U@*lXg~`Ozu8ccelz zL|86x$o_HW;|WH-(ZVl`JCBXfr(nymOYX9Lg>NW&ZUY{j9hzqOiDQHNxQ&I`t_;5F z5Ld$}(*}YuMHju!1NuTjDrXkDrRff}UXi*3`}$+k^Lw8bTX|A^WTKK4YtNW#Upk4n zB=#k@snO^w1*PXS=)nf$@Z|=Ot3!*Ahr-%554e4u!V~Un2;IX)f>RG_2;=`0q%Hee zr)NQv7^`NK_X|p&!=*SUgSD2RQ&^h6yKAI?nzLiQ`LQo?ym)FjlyR3 z@71gn{nNt?Xw3W9cA@I1eTd?~$F0C2eEFV#bUYZ|&Yf^p$+B8)9aYZQ`fhAXRPB?c zJLu%BOQm{W4tjA8Vw~l;FFUijq(se9Bf+3ZK^Mmhph7~xmmEY_`EdKRLDR@iykOBC z&x&2SF0VvH#e9G}!ku`1n@sXaiAp6Yx(F&qbt-Xh3woz6IiJbzw~BlhcQs-DxZ|6` zL@dFGrKVg0D@Rz@-ds*!jGP`2{UW>J@OBu@Bo3&%<6=<)Dr!VL1A29gU$gLQm9e$K3xUN%pd-jabS48)EEMlyX8QSccggg0sHa|+NQNF+hu-QUB zj0JlgX!1aZC%-9}i6N-v&WqOcdhM@NO%q;>$qWeBXjwPeteB4w3$sjVGhUBe)xtWb zOb=Gjt!{;Mv(Kswv>x+%y~#s-(aW)Ul_OT%R3i5vo_=7PX-*&isOI9=e*Eo@*hlN6pE~(t36z{YmA7gDINBI-MJ3g)cJwa z3+^Nkw?f%0d*{jpt)=`M-x9_XzPejlda&V^G&1#KbgSxxcJWD^cfXT+Jnm5XOJCCF zOM+`RS9Y2n$%a6ul}+jzyB`h%@MS%4Y=#DH@}*{jkEYITTOk#<4c5s(oBce_`$qFJ zHc;2C>?9}LkzCczj|}h;QB?14#=*roAdAIV84rfiQ?nqsq1AtwL|#j&urb>)b6aLx!;yJZYOtx^#RqT13M1z7Pi;5kxb&;$ zlnt(0TW7@T`;}iPly_~i_nPmN?swEb1j6+ZroNk>=@YhqUQc$YcE4OTW?G;VM5TO> zB$Vo5UCyT|_Bhue?L2PN(_{Bn&gC;^G&?^vSUz*(^$~A4G+$-ivb-kegvv>v$HsWP zSwdK>w6RHhQXnjtOZk89r?)l-$@NHuHrVVTr08iN`VpU^>TGf`+ki)gqX zJA^ExwKuN)Q=h~0hw4@&7&rlr{0zIMu302ab(8j1z5FwkgJU;3`Vyr!cJ08s3Zo;f z;xY^FP#RIjxfjMi^okHt1z+Q1s^x62`Hp6dsDiY}$Yx(zv$Qeu(RnnU{c|H`A@7Cp zQpsKjsSLDHey`(`?4u+E8GM&rrte`~3iI58yqVFEehQRur`J{tGPw&CUew(NjIwGE z-`SR{comO(K!K`O7$Lot4MRyO#q{q*Nf-NaydrF z*D4J#)Id<1<%H|DnKs~0Rw&{!KQC~>jWfnnfxxY!QbN5I6xvCuDR}EA`T2@4xnzLt?(|@{PHNaEh z7S*>%0SLu{2xu!xrAN6>hR>3j@XhXrV%vrZ#ZiAA*>(tg-0P{D9kGwOUy#B%oBimq zHJWx|l=~(`Z7?uD@hT>F090WC;_~U|)2nt8SvLo^G&QJ)17V9?e-(u4x`QAJ2GN*L zZ48Xu36jlc8j$f5phpk%2r;|)SI8XqD<_zVpN(Xhvo@KEeya$>yNw=PlJ)RtSC=%j zV`g@G8p5$kr}{(Z*xor%)1L5va?={0%d{j436Rwb`I=?xvC*KC6@TH`Qh6J$sY1IZ ze5!g`Y%7mZebK9?&#_qLbZB|IkV0`=U{Cm!H8^Y|od5JqUI`@EKi?3E+ncsNCK$okOy9T5qfyF7jkVLQwJb(K3)tvMQJ^y(0X&~Xoh+>aew7*TxBF{RFQ~*_0Y41y!&e+JR=`{UbVTUV%EgBd$ z+Ay?Uwsq>YZ!9NGlW|U#5lC1rd}D)$eBeH2{lhIW1}#f5)&SIDiVQVuq1Iea^YI$0 zSV~W{V2r@gvKeh{P=tWVx>+%vJt3vpXj;%J+>g3M&$y-;l|8X*`Fs@klS#Nu@!z31>Lx-pCq?q(kbwQ;K_6g`L@F%RBdrxwYCOz#i`Df?EN7D#` zo0~w?)yK@Ba1H!S4yrAqDf2co-hD=#A|YhsKlxja$nfeyBHaq#=FAs89a2to|4FJX z29lf_x#6qshhlKkpA1&}T#0i#o9dc$3zDxV{bH8lT2}tB&|Rugno5e@Si%JBbto6i zg5gsGQU}3Dl_&IU$$_{J6yJHD<~8w%jwNHK^1+AXdshpnnMh|Kx8Jk$!B!ZauhI_L zI!2>QfUQ;SPaVfgSh3|g)|gu2in4BrlF<7bUTZp?@IejBk`<{6-ckw3N+1(7QNr`< z9l{b8ura8OTG?7yGt9vg(jp!!+$;_Di!UKvb-%BV_HHdXJV~bZJ`Uiq@xUGv{;+sA zloa5p-+QWFoNiKT&M`0)){z2KlxGq3EgD(nL)QV@UeuAHwkrxk6GX_}Uhx+M^xb4z zzcIbH#1$DUFX7YF7bUOL6P=#Ei$xojtb7|u6u=@qmd#mfVta}7j~M1aK_`1y_Qp(B z!f12)DVmIfa9hOHfbRO>c=5Js!O-3_=f0rO{j5V}f8DfNa8VCq8km$d@05HaKjcBd zSqCvLp_Y#>rZyEu351RQ5>S2z1~WR8MyOLgjCgNQieeZ^2FJPVPAAkFzWW@f;=k)) ze-m_hmYri&Z(A*Jd$PMju_#sX{l|B#k3`WzmFDHLQVUCK#A5<0Yf=J&$%;*kv6qgI z@jcLP-T(~TIHkgpXV z01p_T!%v&O_Ax*uabacF78_Yhq@dn1Ip4PxeMm40;=?DXE||eCNZZb=F%bgVV$1Au z=ns3xtk;(ny*CCkaoswnz6^Dc?TTF|Ll5sMJ|1$N<0lGxgq9Wh>O$hfGWi%P%lk^*Es`e?=}U z`O-%1LK`Y^U&I@z=Jw^M3~<#j+g~cW4DR=ieDA5TV9(f&Y{I=putR=u--suF31sw; z`nC72F#5kBzB*EJs|FO$+@mh>-m9b;1qmL`diF0zl4st+trQyX8W$ldC;9xg7yD_{ z0c+bq`4`{0GmIC_RaNX?ZxRjs=?7-d!9Jh=W6t9R~uq$A0K%h2H7s(+uy)rz>yuafCAt5Gm7_7kCR{pMx0a$Rx+)DyS%A2TLXGkuALGXhlc2%nnKyiE&u`rFpXFc*GrhS=?pZ%_wEtCstB;`-J zNi_YMpBG(5rjg^Zgp282!pnB`eA#;hLO|%G*h0ghe}4C1(ct8#QP|T*qB!!rL(^5( z#|OJEhP8m+`^AylFut}rJKYhLHa#Gu6zbF$BYnqjvk1Cw)W82+JO2JhlgOO~u5I+d ztz!YE0kuy5#Q)C%`17FSlImj2%?m@$hV%@DsqM1@{$zY+>M8mcq{4MH`!1K|%%9*? z_(OJ*Q?&&os)ZCX117{Te5rCCz!tnKXet6-Grr-|dWLerh+Tb$iS@Xbe(*B^%{+jD z()c_*@;lVm$`7sbL3Ev$tEWCMEf`O|W zz~+f&e}{B|)h`T_usDgz=^X}4^P_!(f>AaQSpr}osl-jFx>ph0uBM6HFLzt=rH_B` zGxJ59L($-`LEY>c_;Sl<7}WR0v^PCF>{Un|#N86G+fdrm)}s8Lsu-D;AREbem6 z>ed=PJG@hIF+fvhET<>D?&l8ns=xD>Oj!L!%k+~`@1ALk9V&SY9pG-h`+}BwM%8b> zmJfm#>kZI?Rt**DA3KU)ef{PR?c#EhyZ5<(17MhgG-Y)!mLF-H1LW(2Jpq=(00a3q`w3sE^zE(xHn*y;tFQCQRze3r?-xw1ASttm-Vp=O-fX=s1KhVn> zskJy1f5Q3-T=}(WRm*clzRszBndc+NExf>Kgzu`a1AOI+56;a+vhyd7JJhWRGHfpv zR%UYlJQCe!d!xj73LBMatS zrbT0k9MyOr>YV@NH;j4fUeuNa%DZ1YS2;1x3LGkV!rQ zLn`NA6na|B$V>|gb;j-Jf{>C-k`6%~F|T3Kx}f?Fkw8LR>&QAWl^fhuKy^ApYrhl<3Y-G4kia%$t-8kZYRlpq zT!{&qMD{NQMXc_DB?cAu{Do0VYGO;QJBL2xth{_6JeDZ9!Yukmzrb#=elzFF7NL;v zG%7QuUbSaZIb}74a0o(CYQ=D%;_TxTgKH*buZ*ot25~PN`i)OHE?rQkhC&QHGMb`U z-X-4K5_wIew3!moV9R45~Xc= zHh2m7?O2k;GE|Giai+wqpi0{(_Fn=&Avuxk&1GLnWvDIFRG^H7$Y^pZudQ>^n z2o)9Ot*sS-ly-S;wG+mR6@(7ie$uHjoC8%}zUYVByY}MGBk|_O!A)xyQ{s{u?H%^6 zh1{TZ6W#RG8pCF(8*?-L&l!cacNn7+nKOL71T?t`&cH*e$*QniCau7$C< zbb0Fx6JF|CgjvS;m2* z>GJL`s4>?clnDapOjmxjQ%>2a{C$Km%)o7@LLR;aP&c7nJ#EM-NCG0pvOVLW0+kh9 zj!&Lub$X-%Se%rVMk7w?!w?3FC=C#&xZUAJK;tYk*9@7D$@vxIQb?7Lr3vbVuh1 zBfaeT({C$WQC%k62q%{ zEa|pSn5tOZ`vw2mqXPWkO)JkMF4MLkyt`5NDx9CbCR;OKeC5RBSR1G*{UCtIDFiZ^ z2$-3=SMQ{9pZ#^o5aC)fPrEp#twUs|@uskhY~694y~vQyFQXX>3Zz+r)qIh~Za*To zc!WGPWzf{%3$$$75vzxc@be8MaR=ciICH-67;VvwTOwF+O*HHhYJnwrsokvASyYvmzdRYPNphLWx}UNV#j%QZbDaUpL1Tw%r2RAM{`i zp}0lzjd9JK7k~-ttsr|Z9P37-)$FCz4!)?a`g0!XA)l7kh)an(-Tb!d_xlT(t>&J+ zU#l4YA4>2AXoH%<>pK;Y7OXPkrVU_MG;G`bVNY*k{X3QBvCciwE}9) zHjB6CORlHoT&(-{S>unqHjS6PXO6^)CM9{C#Xl|H0@Dl_g@8wRI2zo9@38In+({0z zV@&#v12&f>x_nNLw4wPi921A+1S+Ki)304)4h_%ZSriYLd46u^*exp!Wz5G0o7F>?;yxd<9#dCD+e@j`CG`Tk{H&3d zV1$|3xf3yUTPbyocCxCwk-hpGb&Y{;D<{%s)4l<5^$~7iFUEM+rP87Fe$7ZXIjOff zIV9hlizOT*d~fiGP)5OBDpk34+k3Gp^wX~7U~DOhwJ_H`5DR44^RJ9bC6u;;H8S)~ zoH3DYz+Gz&lrMLu-IMA$cD4#)>A&PF5mtJEHO|cX)qK!>6XXZy=LhT$$2oKfmf5cr zaaz(9N7OKgQJn?};qV9@Q(|=QSdU%I5ZeG<98=e5W8%VanN3NFInX63&B!P#j|!_F z%N{Ah>+OQm4-}yNII}|sKCygHHB9K2t}u(0&duDd<=;x!qYhyWfdS*Y>(t(hnYX)a zSNtl{g8ha36(D2BwWi%`oR-F%`1(nn6<`C!BEz|L!@Mp_j*rZwDS_S9?iEF(E~JG3 zsIncxa9N2M;>kq)%GcyZUB z1Xbo`b)v9-#sDbH*t`Hdl+90-sMzx^?sO|SAo4^o1h>e&*%Qt_m(Xg0jf}V4K0&jT zx!Dtz&K6Bt7Na1q?>(^Yd(FZZZa9UY%YCsS333nWrk4cxs`5H7F&nT74>4|;b>FI2 zTL(x7Po)KI1H$UW{P=i5*L#YRU9?{$wAB)t!6S*%ikfda2?MZn@ z3uP_26?rQ_SjCa}xvXrzfhO__E0741=>1k%``kZ6yl78^YR^JPQt|U!?xbLou*&b- zb#)!4SqGYU5XI^`AmguOnmfF3^NDWH-s2|g^n1=jsQ2hwRW=P z3B7o=os1kln~wO+RPg4)vP5gJyB88U@`!W~Q84;WcEUo_C`o@R*h1eO8YID%@;`R% zP6Kc9e&LCb@rsviIrqtKzeH_WpjA|-< z+rAkcov{znlp-@Ul{yHaDWD@oNB;01*|>4BqF z-I}`P=vf=yl)^aOt&`>F?dsg>HT-um$5Na_nbcHhwbw{+-tWrzNqow51vBT}O81f&&o8#h`{!jqf_|S&u90(j6T?C?VKJFS(koX{ z-8T)H7$K{gkbpEEoM0UO#D-;J)4b-5>{7GrQ7loo*DCL8IT4AN#v?5;DbJSYaYcGn z&m53*4*uDAF?CLN!eb-(BkaKRLZ*~W8uVoqL{5!Gvjp|Avv`3aG*I};ftVBvkBg4Q zNB^vO;=NeX4(6~?h}sXJ(mIBJcZT%kN{_@voFJ%si>o!^B#&@AyE=y7xz3UA_cwtO z-&QQ^#)%$XvZ`4)8^e#gn(t}_(+Sz~takaO7+P#JbqAWpMJo4Dt8n}4oygq(P*E3-%~|(2Mtj!2-932cs1izfo0D3!ilty zfCa~1-8Y8k*DLjwaUHacGcLw^pY67hrRKVAtuJUIZ7y6**+ZPq`L3&9u9Iw=sDF2` zU19Y4BzNfJp(ROzK$w<{JTRn3Vdvl!PrC*&Z?JaKJvyW%9YN|6?D8Hc|C;y3JL39B zg!}cV!DXj?>3Znq-gmi#N@Kba==rh}%$2GN;7-tqT=yk~bDaHyRKH(8)`bG!IS#(6 zd-7V!47(p&s|vCzE@ou~uE(ek#A74Bl@Sf>=5;uy@wkCLkL7B|+ii*Vu`bnp3M5;# z$(^myY#Ck+081U$gLp^`PTQ!WRB$(TNgkl@SNCn?yQeC? z%BDo+3c_rc4sVwh)8*0rdBMabP--dHc7gmCrA>|6MI2ju_;%&&tzBh<@z;otUnZQW zO-OWcG05bZM2ZxpESZ582m3M2eRWuG)9B&yCF4B!z=kZ_FAsZo zV^)L&lWRb|9vzE#^;LZ_v9-76-iQ*&yz313nOr(iqT$5=^w&nfQE)t_~BN*6?f0(7)Gq|GzYa|M!0e2$-=kQx+C=+HBcH zxDd$gV&QU!OHj(;c>coq*56cB9e*VLAk7SZAMQV&c*$;_ zMHs-5poJbiJ_Q4b&yO~o3Xa}4(?$82s(tq?Q3NW<-Ek9L!nc#JAfBZxZ$Ajj6;zms zrNPnC(!U3P`_KQQv#W36l`HhsL*p$@Ad_7chfocnm!v*x{LE{-8JcJp%>hu9af8nH z8Ue9Z4fix!DBZIJ#RTy-X;3^&!2faUnB_g1c{-$1sJ!M_1_s9hYakoRn_(O41x+kA zs4P19uen;t8xLYW{Xg?bNSXObicYpf_m&ky!Cj}Gg zkoAMBoBJr=G+4C;7^>=!c@|IS;m`z&PvLH}%t06QBs1dj4=#mo5!hGN)9Z_2s>fa0 zhJttQpTR~VYD;&V!d6OoAVqKC2y^-|O>Tz2y%m$Ak1s_(#V#JHgMH-&>Y|vY@3gI4w;l}d- zmBV}Ce+4IeDs=N$gi}0<_gEPFU%l;S>pZlbX~sTVJv;G!@qY#`zN;dBfFIi>O2;0e zHE2qMKEpYwrvp&!#R|@$#=Offze_$MI6{E@IwFN}wi^Ch`>oZY&aaXZWWn8+C}ohl zETFve2vu3GbhiI_)Ju}b?${ajRXYEqLX`OR=vfU=ST#KB?Bf78=f-56nm+CSB>Lx@ z|Mk3F>9^KZUD;hFyF;LtLhbJ8TF1Tq=Qpx+{Kw+3|`~{X_q*+ej?^t%V z_L^hgb+mXvb~4emm7$ zmY4{@x`kBTRWI(X*gr~(crFmpjNW`_u_WVN5^=MrZ7bfP*2u(OXGGdUYhDm9yDgJu zH=oSmU5=6T%c$Zi=hQc*N$;;!vI1_5Gvox;kIX%3i-Bgs+AyvhU>P+|a9EKC=HyL3i}3Ki2C48k_}3y8nqQ9V{u& zQyqTsU9pQkXx=I&wY2y(u48n_qdwo68uo9Bmtx77f6c}|yt@Lc62g>(0`Zu#N4P|yC4^8yeL4@>6e<|^DKT~Yv>@XmDY{NS?QwOa}I zSJ3xMZ1V0H32`^lf~EisJA{NlstI@L!}vmWKF*-gz^8A9RW;39xjm=HMR1N5J4f^5X>W_tqD~TIkouwO^)@0}vi26Qt#Hi zP4k_gY~Ewfv&-T4tKEQDn+=)1vhO=m>7?Q;r+1TlgP2P%Ep?89ZUXf6PS5J8&SL05 z`5#qGFy6#Z znOaB}r;&C)3N*`F?@(083E_~;)fH`fYx~5$6C-(2O_`h(N;u$bOBmJiV6*PUD zr!U5j+GUm6=8$)ym(vL4X!)+#H=s}*(~G&@%+HXre;&?z9}{b-yr`N}v8W<;?jMNU zsqZc{B@TE@JY!$xi+i~J$FYr2%k|7T01#U>b`MjEPwUgsYZc<_+gxtwEDx`L6Tjq4 zG(+TNTJIXtlP27TRV%Z0hRLixMnxjHy{|5|xFRjKn2zVztFHz3Yd1u4T2wpU=4a_s z7pJ+-8)->YWlS!7WAK6v8L%~tw?@y#$8U#dpg_rx;q<#_3wrB4v}0+pxAKH3dB#6O zwe-AAMUu5{9yBEc8V>F?dciy@x7+=|MU^`VBb@li|$(0A)mFUFTAnMpmF%cgY2IF@H zfdgc$r2*P8zg=mxsNB9b*ucFj@bka2$>Xw?6AFJoAZqg|dhO#chM#e~UHafcJmgXn*#zsA1nb%)B55*uPXfwmMo$nQB&=4;h6~H9)JAg4)<$y{uqyqNEYRs= z=nt;zac!5(KM~u?P7E`Ix)fuV<e}v;IdPty{!a&=Xtjwd%uf*R=ZAO0 zqz_RNF5cQv#b&Px2v$J`4o!zZ)N*OP?5!*@USckB;d4ccDd`Epz4kc0vB+ zJ$nDte8!Oo7yrYxI(6@`z@D7~`KIiE>gD;)J=Rf3tISJWBx|vvswiD`kC@Occo?jT@D_LAD=}yh748tNqB5(S$aWXHCDg&xV`<==in%OSY zn4Nd~X{eS&1arOs3J?kpc&(@W9G9GEv*6&(&=e*eVXj5G2?~A6f=8?&&oH#nq=ee5| zsyzso%iDXNNtONL5#mEGszJC6C)4t`hqGG6D8Cl_8yd5LKFx`cQoeK}A|iF$H($A} zO0_WME#g;t%e8tU6~eBnSZ zoLf&i{AZ$g&O&D~^5h>`pE7XMYA}Fp--^5N3(k7YHL)ElEX5k^RsmxKGhEjg@5s#D zzP2^zp)R_@!LJJd)s?P=yUU7G!E$(w=Az8AptvQI!f{EwKj%HADbQ!8D_8#6D&K-f zHgU{$-Vz|x#U9bSJ8}7nz1oAJdZ`v4_n_%x$+~3^vp5^Pf}D|4)9EC5RB=%D=3l!O zIuQzD0KVJQWWGe*(QC2O?Jwky>OWUI7uqk486g|SXY*r4@#vHC z?D9b-yxV3#^c^E-&a@WWDaKjjzwz#0IrzXGP`7V;PkG2zHqdW8Ns|<|^=>+`-QN6R zuK2M=VnFEwy?F!f;^_hK+Qp{V_BSB8k>1x*5K>qK;DbEpP}so+41eU0`qnYBS0?wU zA0~{8={6PT2Gs}e9z7m|OrDrT9NvwX_yqU-y71a@T8k;{Q??en>+P7p>02bSx&1js zfrb5di}Rnq9xo#fs%H{c5`5%h5ON=fv?NX#D`Uc)DqXpJonofF`%I1qq~k<>d}JdV zVM*qO2GvNf=E;nsBUtB)o9M4RW?(Vhy1Ppg>27%G5f#UAFhh8mX+oUDm)({K7tz9m z-#c$F^^TIXc4x`E5Vk(dD-aupZ_BQIh@1{qs=Ir5q-@@wIO?p50JSNu_11q$1j1eU1p?!6H!tY(! zEC_wbBFVHwhLhom|lctF?`{BF50TtjG+b8VfmDwAlfnX-6cjjx>>u|qk;P zd(Im-!*r+oPUkZ0kiY#0mm}risr~1DQbe%52tNjE8l(LivXFZ(zJ1jlacG`}V`)hG zh@B>baD)T=9}zd{;FB@8%0+BR{UMX;OTO%V)DuJo#{=o;Ii*`;vHPJ zOtK`A5z#_HWaaAyFXdkYYDbng&U2P(z}^=@_Q!8tX>CqYb+(W)hv2Me`Zqh3C0Y$* z+TfL4J~ae}iD~o$cg_xvdR0X&if({xE;Q68NV230PMPCn;K3{uKL4X^qlK%VR#gZ{ zB;4zxS4csYlj$Wt)%!ljQ3@zluiK%Y-(IX#13q{60eMqB`YbjvGQq!ln(>GhtTQ7g zDQpkKRRGy4v6&UXT8u;1Tue+%dz!i8*)99R3h>x=4iM^7r5vEMD+=7wp!PYnSb}l9 zAYO%{M23G+l-jFgM_7uZr5{*Kv6s>8QrxjAaFdtcdKE-j4Jbw120}$Fxcaab$pDhEMs4>tz8kM9UykL zxdyp*El+mDUDzFWlA8@OL5C8C_OcFWN<5-tQB}2=R#oMJ?=ILbFhy7}%yY>8LjuFp zjavrijgsNJVw*5#H%cgmEDPty@Ev_pT*_F*r!xz!Zk!77g*8t;mFLQJ+(#*^=kaHsUwK%-=OjFQAZPqXI48s{oy+|AH zpBoOH-GEO1CPkjO1An28E8gPmrfDr(pZomvNHWJe?}O+FVMp3=r7@Lud_W$G4=jD) zmD{Wvsv1qK8kG<1CJUM|a20G>UWf+7#9MyyK;6u*0@!SJE78 z?%z$#SkN@!MDXRl(Z>Ld?2As0WR48y(B{Q~)(h2V37jOxF6JIH6!_t1#L@(NQ?G&c z=;K70e|7v~y7X>p{E1RzEhcU?QnAe(8mdr=%>XT-^yu>qXcxa|l%o zQhSkC_~O@r)?oMRG%~nfKv4iuZ~q9UPI7PY*@^FI+1Y-@y4T2(!mlhRjwZGKJp%xR z^QyhTuXAeoOVrGB^Iz>A;xiYZx1hP09H7?AYIc<& z3#lmh7L8ApzuI`**GkeaIJWTd5tD(exevJ~hQMa8-x%AMf%H;dQw5gtT@F|nB;!`g zqI%n(=?nR0J~0?-a#BiDtrrbXzlywEVWsw|OHpd}7O}PobPW~Dhb7@K4&>eJXZVGh zB_falO^!y6s13Q2i)r@5?w0GpE98k7QgN>`T|HLAHxpZGACJk^?2L7g$!yW_wFq?? z^$65@*zA{duUF=QA=MefxJlagMu?|lw`1p1OzexO0RIye*U?~A+W5ro!}(LWXfiO^ zCKH|0qi^h>G=kKQ6SX!T09NCXY<*C3JKH@f?r)%;ZREE-CC1FV+;aC_kOo?-Nic3^ zE;Wt$NF5Z;$ZKQ-(X>!x?3Eo4 z!=-X#4Qb3Xn!ZGgd$KTH>~yFp!fp}mrP~cwYmWACT^!4X6vqbzkG5%>^O*AphL~x2 zAE*cyg0lyN>41*) zrVi?^@}pNS=AO~ys$Da!iV=Fwehy7Z)F9qC^v?`asFVm)kn)T+V>0_}fvrY*evr*gd; zG?SgxKG8H$Tne!N#n9RMp>A^e(mc-XbzQd!V`1wcj5B*(r%0>yS3u*!vsl@j5?Wk) zBwIvV``N*?vBFY|OG=Y445C6mmlvOHV5jHoe5fc^5 z_64Q*pnF`iq4>Us4S|HV#o}VZrg+xz17Zfqv`j zw1aM~B!5Brrbc0n#YS#mN=%9d3bpQzsuu^+=Z_sdH^aRLcFiyN%+lg8>UXb=g^l-G z>vao{f_y8@6?@&T7_$WS3~3vB1Q0d+=%?Z;&-JIBC!%;x`rxB05q}JNvgjMfUtaod z_*3pz9`#zo$FvGbSag&XH(_r9$sODHe)w{n(XL2f*NDh3v96Z*4?yn#r$fE7D`EO4 zCB_j`d%c%UhV3UbBSLSGC}U);`Ava}7cY-u+?zcGu3ozRs@j(5UK*=cWo-!$oDJS8 zjH~Qpa=LpFU(dORPNzoaG`>)#OVhqfc!yBEdJ?aYYg1%BH16YV+f@uc!!mckGBc3@ zCR8y`%3}$^`XWn_XEWDodv4B zApCDcrLoSi)(1&N-|WP{OlXzXgA>d^wYIU@*moxkuEiMBf`qf%g)W2h zVY~R^nc`7Z>?z^QOQwX>M4;P8uX#Vg2InKbdioti@}}dni$13?&Y zKgsSLR++k{TRbSevi8O02-~#(n==1ZG-TnqbLQ#S@wD`1!t?@1q&}zrFMHqzmpNpf zY2Ss5#a%=^A;&5AZ9&0E)Ai?I+DA@YuuG48R=Ff7BEV)a`94DYU4TJfTa6hh#3g~@miKF;msZ3zesZ0t zgJFUW7{uiBH|0BAPW{34oV(YpgP5b>9R{b{5YqE`hTSY{hr)w>;kNFZ{yXdTo0G!c zf3^JJO8lAeCP#YN>D%tG8%FzdicAaj~p&(TbfRWS&T_r z^%?#RZ8)uPn%rHl=FR6#agAJm>u!RdZhPop&A-S?k*qM+xK)&!Hr#DFcVwQutOHh8 zg_B7uoW`rEZ$(@@~Pe3w@ehtwWUvYM(94}vwLym zWAVm8D>ruC-Mwpa#>9{mbi{=@nAQ3PU1lfU@i~B+Xna1}HX_RAblWm8n_|y-u~XVk zJTh*g(NSnWe1ciB#kMS~WNgrP%yyJnE9UWYbDZ^HBJFGa_{knd&H!aKN7b@8=@uPp zDbv#BOtj!8gvMBpi-RkY;b{!t@0%bZ0)7&&*TO(S#n`SnaWXfRkt!gcy_{?@G1Bo* zL{yC&X0rXpHIU!c*sH)jBO)L{^k(@j7SnLAZCIZZ)1#3_^yt8?*;fl)A@YqY7&unw z5(mcSDImM$SCy^aSnGj(U44I%R6&{@-6JfG2mAcq71R^=gZy#)11$Q%B4+)AP-yYa zNwbSjM+Yq!xusIXnfdmghCSI-LS6F3{!-2`OK}1n(#VPj*&|LkgrhE!r`>T82KJ3-xt`I-~iit|P8WJ;bgtscYn?evwI&&t7!#V9nYB zcO9QVwCo0_X7v*GuEwG0&FAo|`Lv75le>GBpWLSe!#00e37*~*INg=A?|(X`c{5x; z_yJ(-y{5Q(nMow z|G=Y}izz8uSCvrDmeFp%Ewdr|Hx;?#^`4i?cTQ!yWrqv>@o8EntzXDhCM%~8lhdDM zlNzJ`O3=_G1)5f$z&v#JDky=QWnwb^LcoM z6RLHnxU8`^uM~T~(E@zhRgY>|%F56AOVB--Eo`=7A*hYp+DtBbbNKK~K+$YLQgtcp^$DqF?U2pAgN`m60>a{p0ARxgqs zK^@+5&xrF+sCcTE0jeBtgsS)Yfh<6l&dy4Sxnka;zq;6#`P5?Y9>)49ilr!nNVt>} zCa^Gg-Z}9cQVVzT;Nqwz}0JxCp$*;3P6F>wmxn0bq#-q_j70^eXFNzj0JBP$2;4mW zy?)N9;+$i$&VqN$eg)%Jd`n3j#T|-?+vLd`x|$b&53ayXy!c-2`|WrQ@ZMsUUn2zb z9I>s$MLzaru)0#76W{uCu7+LXFVg(aCS~juGckpq_GFNv*1ui*Xt&7Nqq{D4s z%VHLI%b{9nzp?NwF)C|Z=h)2;LUf)a?KqXxS1mPkd(sD1x9L98`7nSkX!op79BcaM zGX}LhGQ>ygRoiMu%?{LLnYDp$QmAqKhH;X2?msac1~Nrth+8HLWbnz|T}HF7TAw)P z^%OC-wISJusB6B7m)BLy|LE&zy;4k7noer6^*Rjw{CVo`i-UJ{w7(cy*pRHANjuJECDk<_ukH2}bGxI^5V@s`=I9C21314<6C<`Ah z67olxgd>?-%yqk*U8WQBt>pEO;>{Pi5559hBkU9pz>}P)je!0g-?oZ3 zjZ5~wal*U%XCbHHtvGvd`%D1P6jX$A;WOs$>ID4NFEqYaklf=3a8UvYv!DR6kLU%oqXh8rs~*>d82adigMluc;+mW z|Ix}1u0f*I^a-jII}`7HAH!Fh|F2&jrY9x*xx=m90-4HQ(Y-cP1H<+X;xQqeFN>RJ z-*CvaDVr&DSCq+_9?f}9)HqjfwEFevg>!@D!*A>3D5}b3iJRQoK0Ih^w)SmdD+fpc zkB!_1LoW@(ngX4LSlQTl(M79#&(C$sN4duNt#vc*_)>ja@#1l85z`rsOHwa`2b+#~ zYfBG|m9hh78ZX1PyH>joJVs>W4X%_PtX zXk6j+?z}?j`5jYEq7Z)5)H|Usyl^gM&}uH&&q(rI2`?+QLAWcHewX|>+vN2QD{ywpXKc=?t7Y^u`pw|0$u-L9!eOx)CNO^Jgmb#Gn#bVn@3ByKn$g-mz z9y^7nyB~Ks1zRP$^SAqGjt$zhoS?Pc?sIyq`g=v&-)iyj#^`?T2ZO`9Nrog8?{6na; z!1f=5>&~2NZI8M~w@>keH>=_?b#afECkr1vg=KOIeKyn->P%>TRe~=pF;IKMbsTM9PTO()h&cRe~qgLmCme0 zAG``!4u7+t@i2Dtmo(?`BIh2Dn)(Jf^@Bg-KlLZ9u-G?e6_F;mScO*gS>+U3w%AH2=!b_Y)Nw4AuUv z1N)`y8_WwTPtl03TEyj6)RZKUCjIeJryxQP5iCTUgioLSI`Fz6XvlU1uE2=P^`Ln$ zdHdn6C!aenjI{-h-Oh?y@8;| zWUjREn`&?Qt#>UYgXY$oaQ39kSFJkclQRPeR zbc@CH&fS~y9)#k0SXK4%B6*}qyeNCyaNa@V^*mh0ABbX~EqR6Nwujg?5XCK28nQJF5@xtN&qu}agWQboP)q3bT22y z9={O$hy7L8&l&IQfXRmKwStTS3B4LVtM%M8hQbt8oj{o?KFIC|R?)aD1=Tifwl(d>6w`kD z@yfsJ*`2{Xk+)I2gYb_6ZME+C7l`TJ5io zd?e~(V;h51PPoQM5+s!mUX)_WW%#%bW%wl}A}y;NAQs~IIazGZ{=xznAVf)AikZ3i z84d+3V5h~C_Dx@xwR%<=K}N*zwYl<_8jiz~PhuO3`5srS*6Ox4Ac}@9()*-0y5jZr z9TIQdz9V2drZp+20T4{o4^MJtQzkCcV{;X81En9d6tDnm=$&Wjacr?x-$EA&z;E5= zX3Fs4g#aB#B3z!MN3Rz8(BjuEf}Nf<^AI>Jo^%bD@mWMp#CCG;^y`U(n?QX?Gio3n z^RFug3G^*Ic+3!^l6!N9RhF$1;pn{^b!Fd5?J156?niI3H{bp9!ur6r(a}S1MrHMD z&q{X@aLRe}Ye^{e^bxMjwBZMrlb6ty)}nAt&*@Zi)&rM+KPt46exr~XE*1t?vP?$S zkBhPf3L@Gp&fBf*M4jU8uZH?G*#M;-{cWAI#VUL)(vmO>=|8(Lg^!%9hR)2mnqPna zOo?xh*rCcNzd#QasXbKyTe1(U=KLa1p&=2mC2k4!XFi`y6&ff1YL8eVb7~;g<3sPa zKvQkLI%$aw{kvzPR4NOuU>O>RYHV36K2UhB9l@ibbo9^5R3EJCodO775`zFP!*AGs z;Wl}|9_zKgt-*b$ko9dQmG*6-SeAlFxPrLvx+YC8K$U3**&cFl=uOtD_t=;Jlu0uY zP|K&u_@xZsbIct4ERWsyQkMVf@3blw*&ZNd-~127!4|hK1`r^faXA4Zi2*GkPlA#K z+xobxy{?ZBnU{JSjTg>|}?GujBXy2uuD~!E`&aOobz^t``e0n^T z`_l%$%PW?p_9Z3v!PRdME$ixGS@55Tm6MZC2|u{db7vp_Rr!Af$Nvu{!T+_3!Sz@> z+$&3YQI_x5+HKK!6(C4o^IC&8-sG!bRL5aneznUtA_qklpfl+r{mVaG+#ms8j`V(~ z;_Y=s@7%u{W@=qayMbCxiLzf_WdUuf(j8HfF*`_uhSP#fDgZhNL?xcgLP~EKaB-ch z?Atv)Hw1m&;@hZ9TpQ0Bzx)A0!1zc$#EXTx8wTLd-sg|s6)WY4CYSKcW^KGi!QV^Y zdAx_OJ+5naue7nIey?{#>7M?Ic8Qzw+0nzEM#tjrz#;A$t(#9qBpd8Stm9qpD849x z-J+IXl7kZ+~p9SvNEcc^Db{zy6f+eqx=VzpboBoYA?G2Q5|jb}?VXI4MQaep3Hg_7ASu z56MS{40v2Rrv(YZ)mP-W4E|UL~2;Ht0ys~0h2oCjG03xB&BAtm>Dkh#gxHDq)cQ$oY0-=J)(~B zP`Bdhj0@?}f;PW^pqoaXI76$o2%kwn-5t-0ZgJ_73Jp!${Z7(2Cc68|`RJRQNtEc{ zwa`}wsh)-bFB~WPvq9~yS@{i6E!4zVg>EgiMJMbVi(AS)>0GArdbK^7AAjMQLyVk- zhsDxjjg168zb+v`^#|8&Q;Qu$6-`xsjZgns;?EusI**od`a0glfDP~2fAKazH zdwQ5(EA)5pZ1aCF5OGu_Hq#mJ06I{Kc!{aA_As8$L;2eCvgqW_ip;%I3nYkhI=Cme z*Z-m6yqvHVZ4yFGT>kL1BcT<(Rb&G%qq_5Cjt+!qas;9H*nFpRM>Vege^?j!9jSVL zve5!SVfmtN{A*TBpnWQe4XI(G+M8eJno$D?;Av^5McD%WlgWlLqw>;_yWK;QlH z1PBO?4|_VkEW8V@iHCs#f)4;1Za;_cO)p;gN+#T;3p+1|e6Dro?RmEePYk4%WAuEs zhGn7o8bB&2C zpN@Z@#q*pMsSmECdf3%gfYjHtoo`0lynfuzmf)`!bjBp2EKiN?TQ$%-Oy0{L1#e`< z4i1Q8->X)nUL}}!bly(1D{t?9Jm+bF7$`xc@vyrV@a{7gCNr1TI1C#9TC>#Y9X>0( zc`NW*P04DONyorV`Bv-lmJ%Hk5FGu9*YO9}jT0?&q+*ue3M4mrOTwfrH(drNax-EM z6eqezto8Z9mG%6J!2PCOBV*rVk){K^FRrw5)ZpS%IjI-qM)|I=w19-+mA+*V3%{rd zf^uA@__d#fst!^kwiK?5E;dB$KF($O3g*9f5GF?pdr@sb)u8GAtzT!|G zS4==QjpeM6wh)X=vkkC`Y`;O3J4aU!g(?Ms__}BC-$6dR{wu?mq@p%Ti;k|?`*f5o z+9}lN&e!KQi+9Lq`OH4=9SC}8Vp(8QtLlp|gLZv)3Xpaa6>*z3HQB8Yt8xJZ#AopI z$Zm$#-_eh^O^>Soa%z}db3)Qr4S2Yk2&8d$#NvKw);lm9V*cLE+Lf|>FWrlTjB0@n zuY-^fR`q8K9jh5TOAfM7hiL|_V(Yu%V}(~JmqzUxoT~S{LwdJEowwu7dx4(#HV-qi zXKh{7`XJ_&?}-dwCO((XoHX?`qC|`KgR2+VVtN|j$(hH!^@ltEGo{h|t`{4e;OMk4 zBx6Hyn>RqG5iQ&{JF;aYfgC(st62bt15%K+aWZr?hplwh=p-CE%jt`YURMh2%9X4! zYq%P6tE~o-?*RsISZ~hmmIU`$x`7kt3F$t0a{hj?>nDc?Y7Q{75*Q?x8y6iNo$q+= zy#F`1zqU(NO#Wbzz)^GlLgK5+!)3iU&9rjYvbq+_VAg!ceP_jD97x#a!~?+wwws5N zN$s^eyeA(Pv$ij#htfMw^2HAi!>xmk?ZUg=&+_cTj#e*dGeQj-4+TEl*q+or`ea=0 zei1*i8eu}xV0$Gd$$pjJn7i(A_d)rjq?;*|XquI(rB7bW2NF_M8+!k-y*aJ?LPa_T1nT`zk&shzl;Ifg;Ea4;q{vp$ zm6Lv^;*P%+%C{WY$I@~jlW*!RxvC})YH2u=6D$?i1C)~c0xT)+$tvf?6JE=U#v#J( zOVf|jW{lq}ql-S5Vo4<~u4LT>4c$gO&FZmCVQ~ z!~Zpn{#6Yd5?IFBAf?&Aa&E+8jxwhrxc_{jIIFf) z#tAMhEr!R7CkKJR?ll=+f6wMzb)A<7BYFVhW@I6A)+$m`mz1yADCZ|9kziq2FQ%uO z)miS@gQqbFkIQz=pLm_xb!`YYOV-%kC-|~9G6=T5N;(~wTv47H7yY(*q-tcnV2K@p z-uyC^bus_dcu|b)pKg22rUB))v#YH87~L^{==eg73i1w3$C5LxkB!FRE>^2f_hSal z+Y={tKY-=c+~g4jt#BWAHg4F$A}Lv1cMizeR?Rxg!ydb>DaOM_?X;i7I;6)UplJGe zSKQKaR=2PsrHNac{a*|gD*o-IZz4Z8ANVvm4y}sK2a+fTUZ}ZprgN+{)!Ga<_x;|i zr^2`FyYgq67FVUJyb6kHH#29f*B}&#(1M7$@Q$B<4LXK))sdL?Pye{yS>OO0#MyUD z0#=KAJ{ba1pH|ZC%Ay=lsjm!=UP~UvYD5?M0+f+rZYfV?!Z8Xnxn{v0u@_X${^jQm z4YgcdxA9B;v#of}F}_{F8aDsQM->lP8Ub|0?9C%VDx2q>-1+0|wBZG1)^Z!q8SIS* zsPy#Q=#J@_1ii8ZxUUZ9^Z4O^7b^dwO8ws#L^9(^+4VmZ2_`Zq+frVObOclL=$G+@ zSalJzD-tlse2O}^Rj~|3M+Z8fft`^3M+NDuw{vr799vcKrur9xLh8H_#MaLWLUZZeqY*GDYT(JLF0mg)gg#<*3Xlu5@? z*{=VsZVu}ImvSXt=N?g!A!R6QaR)XPyF^y)w} zNK+mQ9C3Nx>pRyD&tt0&r_d?jVbJh!_c+bcLX%rWdC)C-5if3z7B}POxOz^OR6aO_ zK60JcLcIbD)w`FYFu@ojU)24(;I1_nv0AlR55_^5_To9!l1KNa1Z&dFSQ6mXf-AlE zULIO)$y&};0?ME6#e(LMcEhK0{!CTICsE+<)BV8e;P5B8a%cPD9r9kXl#)sj%; z@{EZSqR5hbTZrRIi_Q(Q=^=D6-z85k%Czr3O=7+pmc6`qZAI`sUlk){FfC|#E+6h$ zl(b>@r7OBIyqDpT`?z`+`FE}s&3I7GGR3y9m=$bNjdQ#jIbaT!8Eci7thBNAT4o$!cO)S73J4fImJu!FegJYnWp# z4?DE^qE~TtZNr%QuynZ2=wZL|tXN%vO<-|zsr~Pa^vT++*mki-Po@+zyhs;H`zxS8 zPyjp5okX+x0++@b`4sy(^(}hk!e!P#mkD)b9%-?9CfM;mk2IBogZ=;um7G3suAU$_ z-sn>@QqHIy^8knXr!2%*+QJJF1B1XDQtL;BzS%MvE~Mph-*c0lr4gxWOosScTpGO= z=R!KX;-u|q4Ia~5-1}3%)<2~&BQ1fc>Vx*k&VtCPGW=9_CUyq;EzEl@W>84AUh`?= zfUJ%9u5tW42Hxg1B=`GB=u=$}YcidG+BEKRF|y}ffq0F|^n0DXR&%AaKF3E%4pquv z`Hp!`RT+;ZZ{E1JqLx0AGE(Z#;6$F!&>q#s#B*jSI<(B3ev|5!P{9>&aRmUVva+JS zI(uux?J3X5M7N#$#Qn`zQcrSTm<`z8$2OrMYFR*Dv$9tXns;cHUukxn?F2N&gBFK2 z2ls}z7fl~$S26i3w?P&?@VhAInyft6mX=+-cPxx$9*pv!aWY=A4kQjd?4uDk_^Q*3 zX|DyituXpe9r0g=EFINKEL47VJ(QLLTXc{@6GGC~V0^;SBwtqw$Q)ebe$*#2EoU;M zH~qQZ8NXbk%%B3kqbhXO(2CrOw_-R4zF4fx5-FQ9!-@0uPAnS`n0+rfFy zuMHl@XJ&hIe|K;eLmpu)5z+-_%A?ql#kFnQ!^WFC8c$M8F;#tnfX3eozBB=Wre3WZ zKe%$qoj03=P@_`p?4Edi36G_0^+eOEMV=}zP~RUzdW!+RgAn{2O$o@Dpoj%wq~O?e z?;NDQU74bmENYh@jLgB&bC?}`+-7Fazp-;J;m647yGL^CPk;<0 zpSkvHw&L0KvS~$4DGfu&uJQ!N70#g?}dI(EuInO4+4ctZYa`1ei$}YEQaa1&=#J%1Z2tx-72*b0oqGK?qK!7(;VaOkJ zC{@SA)tPs68Gdr&_2a)*e{#2=F~s?C;OW^w716+|vh-J;{E1#BF)7M+ywVWYwd}x^ z;Wfkxpj$!E&$p?f4^wj{F^#q>L#|SpIi@)?{%x`DETxIeFB{hMEc|1+QoHRhtl(+! zShCNKV&i*MCB=H<_M@jRFPtp&r1|%Jt_FcjETnn7cg>gUL@PH=TAZo~Rc=7G@W_r+ z4{47n&4K3h8taDIxs@%JKNp^-Bl;9oR(9PK2E~Voq8lvR4|1fCybV}uO?TJh7~#^V zzM`JV;LZiD#0LrV-39{EHt#|-yw?80$i@k2a<*H z5|o7@2RcTu`(r6hH%qFF;^*l?9rjS?*c59UP$kRJP|{m!SK3Bd6NFL_O$xM-Mgs&m zL2?~MutuM24ha@X4%JpH*>iQ znb9M_BUcQ7E^+E=HgAG2U)e9Ha@=|Ks#?Y+|A)jIlCO(XR9idKzGx-ru3AnK9Eu~S zZWB_Y0>=e+^uPLqjITLh;rQ`k<2H!7u`xB)(QPtoUBF6J9uhQ#%MNS}`*pI>@AT6- zI!rLZC5+2YKD7OYS8VdPEs^5>6O+mpk+5Y-LH4UPESPpItHeOvxb#YpGq@5x;O4(tf$zQh_&GijY2su^CY+4*(^X zUZnO05E5?Qr*c7DoHFN8h`9VJpCjyIbwB=I>9`Wir?m}=MB(>iGV|P9+Y~L-Fkx7! z_y;}dC75Pw3<;zCHq$ex>e1mIyhHXz6GfG+-J3GZVuJcQ|pOH@T{@5t!r|3EXiqY zFD^3<%G~8NsnQZ-I3Qu3ezHpXqcjCOp4mFCkHjiNWrT=ZrHn-uz~E5KtGmo!(h+DN z+z<>(4WM0t)|4~EQ%=h5s_lot9M>j^jT7-fIc*9zlFqk2daeYfqY=6xx&dC34mfoM zY$Nx`-2*MZp>aD3cO%{Fdb^Rq7ao{#=fVpS>p*1A@=a7>PWL?QCLuI~3DqR~jO{h8 zBrM}cAY~1D z6^@NS-dLyRL*`YCUiO+f)=!~sNm+>_2c^)9RmoL)X0feygG))r>a2adE1;(v=3URM zMj!EdRQbn4gVym`ILYW;F=3<<)WCi$C#0YPN%bhf;#`rO^jn1#E((epI#!V&I7{LG zh@VR_+?2K}8nhnGg=IyzCEIn9iAzMNeRlc1hNGaxAdg{q4Oxo;QsCgh6U7!yD4AFh zg=qzjBr#Q4j-l`Nc`SchdCS;iyFtlX?#J6P#*z{i6SYaqu%?uEc+y&nLSHWtifW07 z2wa%TE`3k^kX;m7zA>?WC||vWM1CzMMy@$sUygSYP)Lj~cQ6pg??9dAPvGitJQRuP zq7EN*$&SFSpWdL=O5cvBWer)4#Rn@oS!RpaR$#lTLQ)Y;FeJHw$PmNTs3?!?H9^4& zs>|g(sCF?VfaNodpIvJIAMYY`SjFIDxarT9Dmahup8K~D7wa>VcpJbSy4Bm3Km1<3 z8q*gbeVO84;S}J71hk{)b!m_{AG{_+%g5?$Li;m9KY^H1=qif>wX9xf-|9jL##Nk% z+UpUUKST_wFzYO|4TFEIbN9B8#^#vKOx!c0*c^CDuGCnMykaHFS?G2@P%TIUV4Nak zC4dB}J5Pf*$qvZ4c1eJuJ)-vIH=NsqgUdCb7r%(AEp}Ijw#@ZjN6gxb1rRL zRw|b)>GM-R&il|~`*qs=b)hvnwo@%Az^^?vmN>pn@@Mstyh$NW%8Mo*h|jozf(+Bn zW)a}bTLJD(R%Uw+C?fBHt8J0vG1)=4Gjsd?0#ay4N^I5cfEx60yoqmmv~gd&<8k=# zsi>l&-Co+ISY%Efh=5#YUP=Q zo0yN+j zMoR3Mz~D~=(c@~DVxMwS`mgxLt18vO#b9}hq@=&Bz{?(`y*@_vJ^p0p#b??UHRUu; zrrl^nDIiF4!_Ix$!Bn~6*Sdurm{%%i59=X8OGPhkB0|6ER|Zrh^uRU=E``G`lMp4b z(YSLx`t^`^+o4i~VYOSDc%@$AHGra2?rxgx35s}|3v3~gkyg@AqP zpS-$p9B2E>=(vg>+A)iBBw4P)lIQ^f1t;cObqN@SaM`wbc{yeGMlaN;3-A13#r?uj z!LzsVUhQnKZKCH7PQ)TB%)w18>#B*fn;ShSyGDIC+xzIR8|^O~)lN$PAT>O$XG}MG z9N`#V&(QAKAk|(4-V+)zp+5)^*_F;d+&e8mGm+pt9h;F%R`0bs-fIQ zf>&7AAk4)s4a~&>^CiD~T_f_`>zr!8XP_c?A2LC`8#ufn2i*^!ozo776f3RH`>Tyh zVV8SIJ9eXi57XVV^Hu%owa8y~D_3JGks8k5=K`KY z{QNso@O<^*Q8pv+^tpkZb07rm^pVl%T;SH+qxvQ&bm)GYQ)+HVS}>xEgs%lss^@nz ze1QcWbvMfHw~62L8q5w4uw*-Fc!cAuFF<(|P-Q%KI1xQRj_n#PhE8ap1qyCAKUuD+ zGkVKBDssV%it4`YL#C@q@mD=qw(DIQYbW-jFkZvUbxF|zg1_93c(R#}!R0%lF#9-DM1QZcbR z4u<|vR`!_>guypZrcqg?`xeTmv@KhOuux%ZPxJ0=+ETJKKf$w>*3}CnIRS9~FZS2- z(w9#-iZ?u5mE#7@W_8x4R-m4ilK^Idr{{kS=IrglR7V%1%ECHLpJoiRP`3vyAo<6b<;)H?4G!rmh3&% zIkfm{BR~xcC{ReLb(WOiVy}VRKE@eVO0$EKbcR`GJ$o;z0fV&brZ#_S?;Q1c(3Vu? zna0-%oK{q`WhLXtPeYwa@=q7&VD5y4kprKxv@5IEM>$2^kMf!b8@Nf);)wL6-aK03{Ec&c^v_66SDzpBhf%8UIba#i^XBG^ zYqH+%pIBO>W#Ibs*EVkHydLAQdsc?G-ox&i4V!y?dXSHqn_7#d9$}t3d z2aqzr*%oCnR+5&3vu*d@)~1aEN!le=Tff*nTeu)2q9jpQPuKiBw$692*!sw`4!rQz zRuaMeAPHS9;n7^(qg(zelY-Hbs%{Q;LPCHxw?d91ztp_b*`jbwi?JW+o0Eu$1MJQ_#+T7xY9 z17N`LzWHF}-J*X(XLJ&yv}#cOl(#dQGkTtv7US9WVKz7sz;J<~4!mSM-YL>`zV3dwfkY`|Kx|V(Ikr7$?!vm_DS{Ep1hN z0n`zZU{Rhl{3P)9lj+%uGvd5;mj2`yGgPQ^{jFsUT*ZeK~gXJa&{_ws#*qQyWY5 zOV9Igt@&Kh@o@B>w6l+NVTwLRsz#N8r$k=i^!_j*4=}3sCyeTmSyWrBn6UJ)F@>eu zx*&{pt3D`eL1l0ILlKQ(S@AtFJ#s$SjBpvUZ$_JpMK(3hMpSorpO5e1*v%J?$jrqD z1cNWcB^(uWGUap;@j3g*brJBw$*e7ZhM&K1AZJfzo|mzn!tH@}@flK+F-&(*Wu2^Q6bRx}u9eh?QVn-HNQ@v3358Y^6S{=>5HiwvrRF zbhSSh8zE*(*pSd->ID6bSCFk)XWfNEn_KD_OYtzNT^Zko6)EZDpp~3

197P`8J0Cb3kj#=?yV?Y5Mh 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

tMRus254j~jVysNO2qPyEb%{_aQ#Jb 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%0AhxR_BZHhA;FCH>W5ldU4yW{7)zbnN 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

xmY{3#C&xBjMhyBDoJb}R?m8}B3kvF(vU%P7F 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%0Aekk{X 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�~LgT5~#PUL4gejc5?Fy|dSEvA~8 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 z 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�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 literal 0 HcmV?d00001 diff --git a/docs/screenshots/stats.jpg b/docs/screenshots/stats.jpg new file mode 100644 index 0000000000000000000000000000000000000000..68983760cd46a9c8b4062a89936b7d80bdf2d340 GIT binary patch literal 130064 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%0A3%VD#08csX@f(J@;M+M( zoZeNZ+M5!9kIJf)K^vuE^Chw4XL|dzJj4UURctZE7nS-!NU-R|pNc1aVmn7d#(B6Oq?I@I(- zoNZ5|9F0U4X~g*bM9=Z1;mL)-M(UNNH=}%}PeksO8{ivEpt+ei)y7=*TIFPkU3(<= zj|9T4WSa13XLpua{6*Z6z&-WH9sEn?NG1BT!UU)aC?kg_JW`(=9l;-n>ljEzXo0U>1#R0kX5k_o9SLEkM zz*&lylr3vFCxRYtWjyy~D0f)al(1;(=z5S)Fks7dyCHh`D<`{h8Q&Ou#!&^uQ+STo z=fjA6>xAjXF1OJSP)hYYOqBiyIY)&gZC|CV$3Bei(|TR8&6gy?Y(9a>9;<9^a(JnN z@0!}1?}IKfu$MW(r1lt7IfuaE5Yt?&tQC6E)>>|V;;RH)E4E9<#aP}J;v@i9&MvRA* z6y5olf;4DwrV+I&F8cBr9`=Hj^)|yOw5#1&wRCU-h$>6O;Ox*bj;fWdL?4M4I+5n% zV)wALe0O#b3j^718Y~gdm5JG z@75@Ypd#Q8lwOoxLXi>>5JBm^CO{}c=m|A61r!wN5PAtkq(kT>K&T=F2oQR&igYPT zSBf|Pd&U{}J>|V0<C?FBv;KBYS0K@3q!5pE;iyf{vR*Y^|GeOHQ$D2w}~toPtW% z3?Az{%AJc@??9Kh-Fpu!6_T|I;N|iS7_&nQeziK5e_Qs(6{)Ssjvn*Lo8A zdwZ5~YmhtmrBEoRDF7tfKODya;MA@#tF=B8^t4(<%{F6_Bc>H)lTd)@A^MX`MqtTSMxmX>~v= z=o8Q2BX!g}XS->a(N(M$oR4=Sxm_I%F#PjW+>&-{6Zk;RHj%;P5CyR;Y;TX(d#BqV zg)cEGKQy5CG1!>x?bN(0&*+>lzUOTS@&;KhT2_J#+Slm=lNf0c3QSE^nnA7lqX`wrs=mF3V@eN+vI`}WQ z_=3n2J1Il9{)~JW+%l>)gngMTIVT-{+>uq?8OW{D3ZKJFgL5VAZ3$|r1z<;>Z>q8N z$s%S*SAGoJ?le%`>J^Zre7w$Ww|P={EvzPQMWG@$RDqxJE%=5tj}?!G7gIbG0L7_6 zOUudzLl~aM20ofoF^|pTk#HYCIyzR&S6X1!VhqwED2uep+P7)$vYm1p3CpMut7{?s zzq4#$4J9pEspq`g%DY=GKUTCO_9}6HgC>q{*$ss*EebD$vz-$d)Vv0%M?S29VAxno zV!V-m9@6e7Dbilo_B2s=sa&Z?$AH+g3)XCCmYkU;G5%M#0{dU5EmC0F{Ys< znGcbrpM!U1?YFWO3HSx9kiK|v>S&R){}odx2G z?JB|+zTHcr0cqADBtC$e!Rse`2gi;N&q^JBaA04T&D^;7{I4$m*Mu5bc6*k`FJ_zI zBINZZ6ZNfzIeQ4-nwKp?w`PpbrB1@FR0o3Zv78wZhQ;>0l87}~d>;&=|y}eOLoT;;$3sZoSNkS0c-1%{cdPl~@>kMw_Ry6p<#zq!cA2Xo z8>!{oKtK1@KpybN)5@S6Em*vh2gkU2W~knW{$w%u2H&@0JE%e&2)EU;n3))?#2WSX z4GNIgz-Z^}ikq||RL5Ia8W&7_>aA8vO-o6eQ1$^v?>%@`6SNQB*``4i^ynv8+XGR!SyKC z4;hd3a;^_;sM|y5(0C*pljo9udR`19ve|h1n&&UcQ!=|cqd|_nx{hLqP|@P3h=?eC zX1th~)cj&w8^6L><{8jDFOHU;ma~=3^OyU(0n3J+h8;!+t}@FCy%#A7C6HDT8K5}5 zr#;i-b;13>#SR{|eDrmQ9Lgo|(Qh{MfTuX99ag>vn86`Q z{dQ;nk}$P0UQvc#{c)u-=0Lq#ifb#}RsBBp5@=9=f;K6#1|1w&fD4DZp#;2&NmmP4 z7W8aWn#yt#S4y#ZY9&HIwGAbR_Wl%h&`dtox++c0P0Vnw*wQZ{pt(xcu)u=MmiaXk zD`yIyU6WwiA9t^P&Hj=^>5$@g;}BuBzr`ISE~c8K(8Re_M9e5Dh*C$ukdOXXEgZzk zz$5)NI`iUSk1W6QTO~f`$UlIcU7zTSr?fO!-NvG@Yc-YCHmCrQ716;wzz0+mGWP;& zzkP}A3ACVP+61yiw){ktEPsMp_uu*@)gFI*w{y9hO#kAR;vY=l%I2iUnYWGa?R!MC zfc6V>oh-yecKZTcr(Azr5U91wG%D!%tp|nZb$z8va8<8GM{edn-HT|40`jWXJ=*$%5|RB(vYw z=9f>zpX!xRth<`rRTO#Yb9bf837%Dq@M{Xn-Z2q_%41(7R&{2`~| zNqq|i)cQPWIIVax27R+{&lB6}qe7N0%V^$`W~PwfWbIIt;$waX(zD)L)*ZDXeVfjpTzUdE5FQ@ zo-8;XdNqKX_=K(vr>%K<23>uNv_S5yS3p1Om;)yF+PMs}7IF;f9QqZxnlSH$zLvo0W%4(k&QT;|q z4}O?B6mJxBRZ)dfuoWqc1g7^yPJ&*J-ZB;uvwz4PBc`5Fp|e8B{^&{pNU}!J9wpO) z?i-uau!2CQ-qv?IN;5yNSE(6chVomvciSy^pQ92ATPCO63jgq}KhxvU7-Q;3>Y5Mx zn0aE}6a}2uewLSf%UGa=)y~7rRGO*DSr3gDj7Z5A4BOO2zTeQ_t#2(1Gi?=xPgt-C}7rK&f9Lf^{*s#uV>bhrqZ; z@im6u-{e`htGY{3#+yC+GqH2Phwa4wH=4BbO@jWaL%?5>Eg-RYI2Dcc=@W_N7T1Jr z>BARC0)AC_bQ43y-!&`gjI1TZA4$t8Y8J)cCAR{2cC+IaZzzkoc9b$Z8}1g3ELlaA zjco_qnaq%ZLM8Ea$$XR3<1q;us=*+X>D9wZ%~>=y zB#%#kGF-$BOP_)HlJ0Wr;C!rD*)p?Z;Tel-lj|@v%!Dj0Cw}FRj?G_^A`$ho*EN!+ zPGZ-A-Ve{fp?^tOUa>ADKK!NHBDNJLe>F~;74mJy!S4K6?2l;7Uy=}Z99eo!cJP&D z%fqAjnG39v%-u&1SK&UyA50P4_KJCiV$ zEG%+G=H~^xi5=>+QFkBkO>mt~V)=m>F)!;AlI;Kne*IduIvgVu(_DJ1^`Uy@ zsrKQY>`Krh0&l!I=3_Pt-DEpAr_Rl(gkx+Avi=nlaXl`-vT7ioE+`1#1W>4HS+ljZ zwMl;orMz!g5#C!&4L1Z5cZpnFq<=!lqOB6;6WiOe5!F$tB1uKP@lI$E9-ysL-gpD| zj@vkV%iV|QBvSmHfy3Ky7P$NNHoA08X`VGYbo=N2?^LX37-E$`Sx(ML)p# zZTJc6K7H!%?c<>`6Ej0)BF(T-2k#$-O=~*vBf#J=1yd9%>tkwz6nbcA2pIo(Ha5e= z5jC1PkeClNtJGOs*I_9um4&CK-0Tn?cxu7yOh4hXec|G?JMcMjU9A!1C(IP9y9V-6 zhyB8u_TKW&dl7cy{v)Li{hr@2Ougczad5-O#6(Pw5-?D(st?q=4tgaqlj>1E-J1$d zFMd&kZIH#AJuXm=pyQrDD|C5B&rRC%3nc^l@CGls*FOM6QiP$lD z@|oaQ_Yy~s^WyQ~;)cH@`}HA+W!^6f0P*A^aRu5{M{T^3ZdX-1{Z{?JN6FJ`eue@PCyEfyv{1pL|`5AO6gp4~rTS~vIs zRzX2z1hZ2rd4(oT=tSCTO=P2eJv7$F^}fs=XxwjpbsPu7{P-gN<2`p)q)V=?_W1~8 zW$VGpSQnKrT%3=4Oabe;v(=g)8DrBJQov7K-0m#_Ip2Pl2-UXAL z2E0gBdPaKNo!6$WK9O6Iea^)!NEPqpGUNv0;9Vnw5P5c5Z38p>p(0H{C?!g0z;aTs zGNu2}dFnZGt2|zy5B_Rtxqy9^x9XoP;a7nmsy6^1(oC zg&<0^S^pi!O>hB>ai)H7kRlGrL8`#o=I(GWHxgdM_G?C%41H0npX4M7Y6SGYYk=c8)#flU4vt-Vy?rUUtdX61l1{ITxPlEEE`muIr}Hd z7!?}v+FMR0B-%6fhLKlT|0Qui7k~&$1W-3_l?+_xRL?Ld#@1f&GNy1lMaY3~OcOHD z(4tqbc>@CBW`J0;(i`zOwJP;K{^} zmuK|A&~{H^!UfwX`qy&PFtZL}3;JJIhv%bT!Ypn%?hykX5x#+mt>XZuF|TerMR~TR z>2Jt3vUG(6{Xg0po#SbC7Y(1-`^i51wwickzdk2`YDg1hm>N`SNC`9+?U#?-+NO~D zm-B(DIr7XKIM*8DZ}YFX_J64T?|68(xGyAMen)l%t3iVUBF!3qM6_6*2)C1vY?<1o zUwSR9Yz-+M7z|D;AdZZy$j?Il)!qNfQM)A@D7~^O@tOE!vR@59W;seCx=2f-g6B9u z9^v!_k&UZkJVV;N!trK^x&<025C4~pzCAhlZwlCjgfX){cy`F!rI^dgo%CC;>t9P) zAG<{Reyo~$C@7FTTkGuG;1@Ug5jjJ{W-23&Z=Q{unnXr^mHA%hp_Nl%FaNr3{GWzI z&gaDhZ0A)rOG<@V%$1H^`k#=Qw_kYuP%J;hxQ6OrER(zBbF@RQ=_2;greIxoc|=^U(_HpQEG_7#~wS&f&|Y%$4M8 zh9+h)(8*8{?YY=vOsUwSqf7G$dfm9zhUq{dp|Ir%p~Ma0ll=T)IX^3O%PrM(a0k^e zY+qJ>gX=)m#iX{bHYLq)>}H6V6gp1q zo>VU2pLm-O#p`6aGDqGwLih0r%sk^#lmWvR?i-cQknG)p7M#bMy*xtqU256TIG#SK z+W)eQG~267k8%)9i;kmHt7Ja}GW+$u--^|Ntiv%v`TL|Z%d3`&!GEx`9ATl86qfbV z0TK3FMhwuQaR--_Ov_eYp28yA!Y2|x%`(JeVKB9PBDl^ZUq^bWD`t0fhTEMqJ@&Q6BdItJ| z;1c2^k~SO*ERX`s_i6@%q+6CO_bOZAx`r9KsWsk*ZiP7Hy7wn7T?wJWI@w^(_>dDA zoS=a!3pFf#H~N*tR;S+GR_ur`%iG^*yo{raBYS)21(*MVt#FGowdyY?Y!tQWZ*jI; z3|0DoJ6V-@hTm1>_2TBTZr|}D4}n$S+S*Qf<6)~Fb?q-7dKRbGm&iV~hX+pW0NsrWeg~V5F zYw>o|Mo%Z{!|@yQcOoe})9>jrqn5qdawjR${TPTQbOqP%MuLMA$S|K_{tk-gvC9vy z_E@h00u*<5CB|H&lld+N>oGe)cm6mSjjJSQry_^8yuPX=yr1bti2ksgGBH^;kUBka zZU1^Iby$slQ7;lN>jY zKP;|p5M6;HHAqSSUq0{9&J}m;<+m>Id#0d<=F0V@Faz~WdoJ9<*czs_YaNZ9D*X;K zYA`-R=xH4S+Uii#WSHF0cEwd7_osUk&7f8;&H>{AiX*5;2-y~wC1 zJseL(iPh47Qx&>dt@PGOBNg)9N@DaJq@lStY{8;FgSK^Y*x!j>ku&~-m)uPPLe7D3tIXP#={&Hcm2Fws;C&DgQEyz#W zkf8~n%%hROR=?j8=|Uoa3|z9T*-_N0ph>z;c>GIADAW}~zj;h6T>yd~$0c_2QXFUmR(^27)4lC}d%B~o(6Y+K(*etm)P z5E6apqI7XV)fDVlij@Ud(-`TqtQR%Vt~)tey>GP}s4b(o@1Ge%2k1BnIN&2C%9=?g zwdv>B@zDTWTi&6Bd(m+oqL?qQMPc5tX$w%IN27z;Ym*?~Hpu~u= z(91z43`9OK)?x=)%(FOn)PYZ5sf03Tc+@KGtIFkZ6aDExF5aHSg_|~YQ0u4s+$e_o z^GNu7S(zcR`KM6J4D{7G*hyHnr!8?UwVhOzO5}*i%P?q_(cI8hjosWB#76gtzJ)3W z$C!Y3ZmiFdcDiJx;c3PpYTtDl!1+e(AB#Va7bj6Iey8W!z$ywocz`@M(Vca{MzTfp zU!P0F;)jb&+vF3mj*yJh-zvp&1@cZ#o8P{C=tmtykROgf`Ml?c)m?8&oTo<@M`TKP^<`ft)nU zbeJjJeS6Nz%2nWy?nH2lHRv}GESC$0<`0elEl%mUYH6*?Z&$)rtD-4NM#bJ@f@R%j zS!WxPJbBSmgSHNh-Zo;ZE8^sbo?I$0+vS3#JJxYCSvgtJ-=ZUQE+m6-(#cmqBC_tA zO}4pfH+xqkASeF!kCw)UjS+)AYH%&5JgF(UnFpB9+w_oV8Ad?B;hE8yqcn(x+y3PV zpdD)rZe&x>sM0O>WBr82rEOQ)MDeE{O~F(5e1+&W|9EnZpS3z51L?jRw{NXP2mzRP zarDWyqu>9E=U3;*?#S6|13AE){x!Eu1kq@dIH=JBr0*}N8X&rb_>RNfy@1>sZ&4-< zi9DOUcJ*>>uV{H5VSX;gw9EiO^fD>b3hfqRuN2~*mhU7~4B8EwXZFNS$_1zeG_pg& zq>*+-tBT~8uKZkFRV?|>?T)-HBl8kkDPQ&mO#dZOD*j8dN=E{|;5{Cw9QRX#F6(HT z7E(r!R_cVq{*v6Dd*%5FOY47J=cR~|`TT=-cPVwjp)&dSu8g<~+F&=QI+W8awbTgu z!qs0Hj^g0BOB)Zfd(WL{5e?VX3h|n#dzg3 zcGxx$mdZCg`$xyT3}zzW#g{!BA9jmDntK+fR-xiui}0^R6a(H1yh#@fHkUkA6^_d* zezunn;t^i!VKO#Zu+U8j0t-80%~|Lqge`k4`|=g&t5jiMDRNGg16gIneM}WCI$JX{ zKh{@%QcX4AdC)Ua?7;mcUXtd9@U0$D4E5GIQCD#Wgb?{#R-*)~h^$pi`n%+-fIi5d z!q?}1bZESs>(JoTNCY`>%I*A0b~R77!C>Xh6`NGH()g#0$n^r`{p7~ zJFFoUlLbcK@V(h#94MZRI?87ch-;ZDZH0O}t+AyOT*r${YNg6-4&gxUI~7ydG$(~` zjDu(Z7=+_u%eztU&kfD#<3F7BdPCN{@B5;oqszi6iHni&ON-L9`PsEAGnAPpV3e*? zGyJ5BRhE>FZID*RJD1$H^;lbg9!ZG*6;m5LTB^8G{CVrmy#1S`cE=wTg-iPUS)Caf zIiLDfGmtws8`HQ~L~-6zTZ_BvAP}gJ&4fmO9iUWT|2loyQNKQM?UVVRRM8?qZjs05 zRjEPFo+}JQQ*O&s1&*~LM3r!KoMn)0+fwGi>Y%-138M4o@2?UMp&sYSbNa`r78h$u zGE_ADr_RJb+adS*y+-icT(Y85W3HiOz6uNn%!xnd4ucdds7)jO4GSH1*vyUi)j)J9 zv*@0?)>f65kf5)>w^CiJN8a28hzI{BIe%nqbED)X1`itnBr6eRKtFVB77e>xp zq7Q{zsVedei$&tps@2O!jd*0W;>3TB?T6ADT2XRCfrLOW6@vbCrZ%Mao{_~-<7_Ux@XgAL+2o=L_SUxRu zvKB$(USJ#q47BM6fxm>$y1~sBi9-l7r82##qVF^I;6zX|%jTZezsBdk(QHc9*Ci&m z|6FJGWl$q5P9^Mx7il*D6lBO;c^#xYrKP}5n>s4tQwQ0TUG%2b8zc@?@2aYnEsM3J zh4wt8!y2ZOlKx~4Yr2+7vhc(hqT=rTO0Yh6?`2byH%_PWw4|penR<(tvCE%-N#Bsv z5u3ByMwyz2Mr+MOl%04B$K^9ua3kWsmV2FSCL?GiYb`mkw5AG5bBpP_d#4MQS>LTh z%}u>((KQZS=D(YUOxF1=EKB4#;L!H*F(QAb0AzN8C`cu!=dZ34sL2|ZkWU*WtlFAS zCwSl>am&O271y@KFfD5}-=v-9!aLa45seRgbYD6tRDlcX&6>Gx;jHoZ>w;64tP^zy zZj^}Y=V_^rOGB}SNnrq?W+6F%ePKEyj$q0SvMrXKb;8ht$Vy*JuASH&+O`ORCTGuD z9Fn59(Qz+$r0lg~k5zPqyXT{^%^R3XcR*&Zcykp~DWdUb9pm6>V#-xRM|p0m*;)(=AzrtDukG(lguF@o=aeu z42|Q|_&23^YF{}vm6Yh}K#&Ziw6v@x#BIdxtoGy{Pioo1pog>lkF#=hk5Wyi!a^|I-Q}=Z zJ^Xy&)KN#>?u2`xT826x-G}mrtC-`jI5la+C>QXt%3k=0yZ2|uMdWU${b~B!hO(g& zKJ-Y9NJ_Tm^w8AS5tZ2>IsR5#TI5#w`Os2Ncd!9dpTJ_|;m4--1m~s|2hkBHv-Ekm zyn$BnlJU1-{Q4)&M)_lL?naq_8YmxgMwNS^rSU&IR@TWn)#FaipfV=OhCi`wnwnT= z!f*r&m0G!VAAejG3@zj#z?RyQ9Z`qFD*{ff$%n0mv^8x7r(PuyP;PH?NT*mH`127> zK;s_wXR3)C?*Z>?lUL~&Qz<_>HAKh`$P2V?)}1zP>>o6;faC_dOLY1cUff0zA6wWJGEnTtzYZfepPp_Z0a8kZ48jnt@|JXY;fK+TE< zrjof|Je<+>*jML;^ZT9UD$T>ybu-GI!L%KoqsAJAcbyadk}#7Wgg4jjS?O7dcX?VG z(Abz&dgW`x~%3m!N0J{AWV;zKt;k4;u|lm^-hPCbJ68bi;* zM}oe}gmMb$flUG%ss`5sSrEJ>Z+s7mD<*eS29uQSx>UNxk>0dYQH;jv^8R4T5X-~Q zH4Kf%hfZyPj+&Ip`sx`$nzV$Ji7%nWF&}Y+k?4)umPB7{e+WDnTF$3^fNzX=HJ&~J z4YZ20&MH=5f=ce$BIRnklve(E5+RsSVh=qKOREep)%H7* ziq16Ip)ETRgsZEm7c%WfPMNrB=9rHzmxX3U+pQW%s5@U}tz5W$y82eX@`SppfBN&f zxMH9>8J>1iyID6q2kk}CorLI_uC;V7V__Q8`m}Y1#J&} zg{bNmIFT`psgzc7sz{HyD3%AswA|bvaeZ75kwKI3gUrYM?H8+i&;`D&RKP$(vhgq2 z$t%4o&|2Zc`?|8U(X$45%6h*a)z&OVR!yU;5#Mq$-yQuW3G3tLFoEcWAa9N7?$+9S zgG&d-*Y*_h&5jc1#O|;^M@Ci$CYD8?W6-3{pS_F)k+=6+CFF+R zWmKhR*+LRS^nF#t)xH5|_4W7Na6?mls}?w%nvhiS{8DF9IvJmaF-Sf3N4JP{dj8VZ4`2tj_KlZZP5t)+V`b}OQY9fO^A zy4~HY<)xc|vetPct?}YyV|#hLeXV;u{ z%MDO^{p>iZC8n!Dy=p~O^=FQ}AV(3`3{Jy$GVUJJUS!hJZUJXi$+g}?xWU6<=n8+# zH~jFnn;WXbf^+^vugtm8r-HgBHEUdHb`+JHBSyz<+yPG}H?gwFupvJai%oH;tH27P z*|{J$EG*=z%gj*`<>omLWqu`91*Xzg#7+|R!;$eC?pC^F;6O$!ivxEu<^lq63i9`! zzv2kM>lm$XNUy%|c;;}Fl*lrKfAxzkXN@T)I-jpX9A|L*EnZJ$PVeQlVa?w9*wV)p zgQO8njf&~TiFMC->R+k5Lm9}v9rY$_ca{^;E&AzMYr=PySl9JWDlnU<1?rEBn;`@& z``lanp6Hc}F-lJtlxt%l_$0X!RX(=CRGW4;>E3G*+A-5#sMe_P>rdiHLoDsOf=A$^ zF^`S`tB=FAVhz)NpHg*OO1}IhNgh;6cJ<$as=K5DP?7jKrt z{FNLTT1sGF=UhL6#WqnY7Fse7<4e11nNiI2AGy8?%p_GjDbVY6j-j(X&d@)NP67x)v4Y$#S>Q$#SAMhg!Y36uqfOg?(M$ zwenzfz}s;HEHgYQA1XkED659Y{G52q+lg=5n%LbDlc{nUc8w~P>35SlS?-hrYa>1n zEQ;!kX&t{#K(;25$Fj2M0uWrAYRHY%(#7@3IGi^m&K(dg#P+BEg3GeRy?xr%>P z6@AcKp;#Bw(NcROeeOnoy5Pe8Kv#-C9I@$8AfV40UO z|Hze6gr9;C_8pgnrA=Ofh-v;tfk|d`wpOU$fRXWw*`ag|cYg^SnoL$J7&Sy|=<}#=x*iY_+Z|k9xUBJ4B#MeW&FdGj? zf*bv741ODzGM98@_B;xev3q_m5V%oQv1{B*p*O%NI9P&z|4zH1sopR*Ik7@dP7O4U z&_{Xa7trrS$1UniTG`md|7>2f8;H>yrTv^$bCp7sEJibL;7Xm@;Cr4LsA-f|*e@YM zCnYTheX7YcPnn2AeNSc>Tg(7VyH zu-jKTdXw9-(hugeslXA9{bakHB|BGz9$j79?a{WVSyeLk$7#heJn(g7F8@nXNW0A_ ze{VnoyZqGmT}@6rO&_&C--R~aZ;$8_PD%I@-*1SOXf8dpt5{mdmiZU-YZRSCQ=5vr zFbr_gZW%o~@cOmAh|0j9A&to`j3$h}qrN6zxyZzksay<5KA2rsFEnZD=mgzprv*iC z3IPp=RjC>3OeN<+n&&OtJNCy%l|Oy<{;Jt%RH>DfVkepE_5OE6`6qD@$WW0nTt1S* z5D%z*9YA|vWOOajS@9)1H~Hg&RGwPyf?(P2=_=tE`Nv3a`bq?MJrv2TvJU6+}z@Xbn$k!2vFYll%R1?<18VBPWT@m6dC20#7WAI1gn$5P|* z$gM|~z^>VtQu`-vs5~QEi8r=rz)OYuO!fMzx|3#OTtaUKLX;ii5aNh%{FiyFxMyIt zfSxtvV1B{!bnrH_KaUyp2qIrnVOImTZldC zbZ5SKjBmRXgtGr4^7mv+<4NS;ufUXp%W;vFOY05Ajc=ozdGa-eKl>G#dFY74 zuC?czWvg`EybX;ZqoJF4~Je$G@7^J)dCO8vW`PTd2;LX*k9OFq=~S!cD&uiG(LV7#Dhn zyr5?mTA9|xXyZ{G<#*5C6wYWfo2?Z*IZjBL4xa@pwmnW4=8j+2p zx7=Y-D;Ds1DIX*pv$17pRL~@K&c53wKpop6P`kj`4wgRzek=b+Mp3L4-PAOblv>|v zQTJ(&P=M|!ZqXy|;M2zog+8>I<;KRID!c#lt$ltm`_q5&Trl+{IJ_<{$fyH*9Z7Of&8yX9S?u?xB$#43@5>||svgR)Sau1&R zo}stgR6%}}#M+tj5H8bWOeFb<$GehkO;q$`i&pnR*AwWkJVpgrl$U>JBzrtb;)-HDIF6TZpt{@Jd^wt zh~a;I=f@e^=GN2dyPQFtF3kE1+B?__OuQhALQTcA0LYmkJfUyPk*|OlXWN@+TjbF2 z&jlJ9%af}Z@cuBT^)r+}#)&C+YCCag#g(|4c2`46#n--JJ1g}zL>(Y;B8r11&SQ|hX1_GN`t??8(&c0 z=+yD?Qzd{xQS?!C_p_%=aGrr|%|yN9jw)I9X>M&-TAFrxl(@TuN~1Rr&j>(?v8WYL zsC`P>_Y@YRnD&Ux^^#*A{*>w3(=2OMQWtSX{#7bw6CFyl@b$aA8S3<2IF;iV<$0OMEoFJ&rv* zVtVFh$k~@+H=m)J@jhptxmien(Rx5sT~^(JG3eBKdU0v#v|Fph{FTpk#)Su`U_pi^ zHZpo>E^>6SeOFCXAUgBROO=dyqN=3cSWqjq9^u{iUM)xD23z(6hvGa*W%4*R4DC?- z#|)xMEAxc(6<)a(^co>kuiw#DR;g3Ch!ZWGZxwv(l&c6d$gxY}<{}JLRZK=kuEPLR z=QisqpdRV_bzz<|O(~#W^Fem+H2p@(p0JAavLS8P-!3|MM?9h1`)Gqp8T$3SX5uEu z`hxATstGlJtLw+W$1>0D*LQ&nMncCd4+=FBZTbi2!yiO*Ba|NT{G>3-`cr!=DIZfkQzM&0V6u8{G?0F9b^6xKoP2+$pk-d& zyMl$lkFD;r0(FggMkp4zI}fj?a)w0JoS%IXt$-|96N7iK(Hy zf|4&IZdkM0zJNjiPfMqX_|VM5o=6INyy(EGaT!+0{PkkN#?F3gsf$9BNJfQWL9JI3 zGC|zFL0$Fb?-yMcg5|!xPzu4pLC(u6F*`t@a$vfmAF^!hdt)j?j-dl0OL$MJt&l?2 zi0yT_0(~uK)elcgaUFx(cb_M1I?P|#ubKmfG4w8SJUcwhrNFx{d1|StaI#>JvxbGtY-@>2`hGEIwEPv>=)+u3rcP&og4^ zqbMaXkSf*ijp{j&E{p>3m*keH>cb2nv0}ZTue`z?&tG%RwrXl&PN8dMbo9|YyDVYP zT#}9TeC|Z*t9M66ZlR+qy@|^6;!l!9CzAiBF?~5ZbZE}H%qgDq2-q)l6DDvy(4aHq z<5q)q*-gV?;X&>XflW(e) z$f2F~m{m@sPeKExijt9s$brZ(=nIi zmcmEs=`QI*(PF2_+Wjnx{OK(A085t77kEj zN5JDfAn)XFnF`schMe#kn|4NW_zDsZqBk}weOQ6XuRp4fEzz8D`L(gyHAnT1(ghj0 z1!+Ad7Q0$!(LOAw6{94w_1!amL8@VH0h)Ak)6Bf^c;5zw1 ztFoZ7->5K;PYm@DWq(^ozx}{O<$7#V!LYnRTa}Zh-024$P*$KURu&~2uKwZp;v>%F z9HS+q4l(B_I|1IIgu~GaJKC5rs5zuMAN&4B(KLm} zzfTq99K$Q*i4A9Armb>NeK#E41C4rZI!b98I7hEMaCWt2RR-5_3&*g$u<=p~Krz^c z@=Xsy_xc9k+M2(jYu&R&o&JnKA^iY3%@nHt{-ytqe*G^?&Hq`{luTV>(PBskElT!N zp@F@z9KdSTG;bA}lke`AuDbFqD<|9|{vNG++wZaDNnfq11hMODB4RK@nwsj~J8EM# zGq?Z?%E?H|(C~XVnq4~G_|yrVuk6>v2NT_|X4drurK*+2e!J%kF!GN`hA3 zSB2^Mg{oSHI^=e%2&bhtZOVN4Y(;bem~}Z}cHIOa!)^v!>;mZMHNQ7Y>o=@=w&~1Z z)aRg|SX5V=Iv{*mz`e{a5+B3Q;Z|!b0p+>m$3PM}a*k-NOJlA++6e{2sJ$mgz=!>F zt_6to^j6m4WXMuuk>L>54a#%JCv4Ie8tkM=R|zCTu?6Ws(4mj zFxlF2S{iS;&N&H?e=Y5oN7tm97JHJc8Ra?^_PF02q~YGpaMsL+nW}IglpD^lOf1ER z=}jMmi7&S8oL!7JzCmk~k1B2*58VA@>>S*l_x+65dTO=Ii_s-%Un4F0s_J8$AyKEa zaG&Nuz%kCq@u}}b9f$W3+tqy#Be^R4KGkIRO#mU~DP4jHPa7dX%GG z|2*@2P^IB?ot8T{jF3ZAY)??fZVCx)3Za39kjxtQLI8u$kl=8dc*pAvh)&(=EZo~0 z-0@rZWW`)lp(0O9`sOPaaaQ9^$2^=jGwnbWF%GttYbul58L$HeG7P0xv^Uk68&AXh zxcSRp`p&$B326O^6D^~;&&vtQla=J^ilF31-IdMA&$lJ2ZB<>OqA_au0a-(qum2ml z#!aAKpSgu0kXzPy_c+W*1ZcgjniVozMm|Cq24iN66@K`Bi?%JL_QYdi!&2Vtc8B-Y z#%AZjA(LISbZ=(49&ywqntU7(-QG49Q6DT^ESQSU$ln~suo0D&8iZK6}j*LHJS+TVkjNWI^caHh>FSbpmR9bZvS!zQ|!5+Xk+ z6!yqk7d%BQ2iDv7F*%o5Ne{mwTRPLeHRWwMFoQ8Pw0OUo^=Y6eRgQ3G)guouw6A;- za5LYycxLxY^l!|V%A%D9I;OcbBk zHCd(?E(AIP5<1r=TE+RKpIY_-4B4|UajM~!VD1`|0Qs9%O5kk?#;$;~saFgv?*c#p z3KB-;&(8OQcAXj@Q0QV~Bkn}5ndmyBD#68fs=fZX>sF0wG=2Dn(SxqU4R*ppuB8Xg zdYV|mRncJfb9T;o417PkQdcjGu@^h=f3Wx7VNGW1-Z1KjV;?|3kTQa_(4+(m)j}tL zAqh1B0Rah3LK6_MQbH4Hp({NJgc^EL=`BbJp`%pkg3^m$oV}gx%%1aJ?>V#2JEwdr zf2@@&&sw?HdhX|0_qyAUkmsJGJ+!N8w?sBLXMfaoiBn%I!uIFu@%T4UiNPm0;FfkN zkqr2tDG#^v+Tk;R#CLv%B<-^GsyFdZoyGB~o*Rmxe$=bc_za=oSe`VVN_J2;L z4!;oaW5ViT`geK0d3mP7)@UeeysTNKdS+*)Sk)P+G_?ndCQVIG*3lL60~Lc6?Flpd zClV;v)7>{MjBwT$>=r|Xn}}^X5Q`>k82l;ooP@%F%R8PT!0bfM;%9iH;o=K6HJd&Y zgXuhB8`C=|J9W3T3cER3CM&>_&uDlE$B1hrXGK-+k8&|)t7uHE_kKX}#+Y~epuO0K zXKA&XWc!KA3!hQMPFMJ{Pj_1oA)&v_mAi!7@o@+tt2pk_oq&_0RE;->}-eWb6j(g`_1S^MOzE{){7e_MC?DYAt?w0D%p^jI1T`uOD6z0Qhc+@o#-=~7~u9y99 zy~Wdr|D7wFFNyz6Z_9b7PhyOItyj3bcg1_w&F{&~ODFUSty(|_d3iOTyA~&(_<`jBnq0a5 znl>d*W*Afg)G9TQtv40s+b{W16z(Yyn)ZGmQOYShSwC0bR%DO;@pM*dK&&)j0;P)P zpGf1eB;*^Mn*ra9#H5hp7Y&Nl?Yg2EUz$a0n@yWF)SU;AaA$!z8s8j_p3u2bgz~VQ_tM730c1w?C zNAI$p_U`8zFmzVh6E8xK3?=kcUAp)xI&MI^f)?D1=r2NIfnAO5;{*rRt<=!0nBh zBQ0v~Ho8hBOu_37(1PdWhihPMVH>EW<(^JTR?86dzJ(%S-$3-Md?D)7T0KASD^ zU}*0C5Hd54kUQlyqpK^@A(L()Y&64T*(_hJ!<#fqbo#M>zCY(-`8hcRfRiOv_0i%7 zw{0W+T#xgX0}g6w@At3T+uZz|sOo9hZj-+eqY?(o)V%I+BI;)#{MJnRz%=?mOO6A~ zKHV4Ybg~lr)L>nlMFvjMR`irLzlysN8y7zq)f@FTq3IPIMN5d*3}SC0ndcd@0@8r8 z^$Tr}6dGe{6HAlQd(!(Rx~;9Pu0}nf7)7xkk3maFL1>XK?{Tc353${kD;ou}lSea$ z1~HW?no|i0_ZRs|7B%d-sxYghpr-UOA{u{>bSN@^jOMa+TKD1gQdxWLlFd7{MgYB$<*&>Wo#8J>*=g`ojymY6AZOH9uBwB?^~E`U^T&Q~aO3rFuNi>D2{#~Xf# zKaH3U#o%XeHA}FE*^gpsfczPUuWyQkNiM8RymS&j@kX`8ga2(sx4|X;3jI??@%W{C zX_wS$2#*o8fYkPYnpfo`>jobjV`eoDPA&60nI-E5Y^@5fjuiA8$oYnEDt5w<9a1rE z#AaFg9PU_M_9k5%z_S&uk`(OO%DqioMZAUJ3KJSU2t9J=iCs)C-f;{S^D*-(M2Ruh`ClyZXTLL)5})-4T|twL4$#f zK`N=w)u)l>h?vAsOcYNA1fOe2%bsd4O0pkROxkAi@TBdtSHvtoZ#IV{-H28ct9an zQ`UKsSs@ob2b0sEMQZ-x=CjJw$wcS64jcb5?L&J+f36|cZOj+CyWz(gy~-Y|#8v}H ziEX-b88&vOyQ~=t!Ow$QgPuuIEN0b=%JIUUkZ>?{0yFCUn5_OB=rrz#G-=m_XNv9E zaHvE%99&Tni?8HR=Ie5h_6C^{0t7Jx%%Rd3|IIhve}Wp)EGSv~&7A%KV}thl$CjHf zZSTA!#kVId_SG|w-&B0~{@KeLh9XxSQ8li~30TXj`C|oi_X6opoilj-q=|bfmM>OvrLu)=XG<41OKFjzq@i#Jb?CS18Zd>Xecs-kkFhj#d(@&Lp# zRaYPGS+SiCK68PcmQh8GbXn!9A|2haz&B4>WwlR7bRuSEdBjfEKJYnG|L%3rOzkCp zPzS<lZ=d4ChAI+r30Oa|Z`)icif9LW$bI#xKaSqe+%bEbgieuyr@7S^lfituk zk`hCgnyy>H$>xjKq@MMi(Al(J@GHrzu6k1CQ(3&hZV&owWKg6~KMyzhk&wv!qpq4n zKKL?f=KWr#s;cNG0s$c$t!@gO>h&`y-E{CZ0e(PsOg|+Vn0fQblnHQh7D|VL-JbEv z*Z45AGQTEw9_VMCF^7>|aISuhM%PRf>t{ll-6?J|M+(_kE2;)@hei#9|D^lIXpdn_@%^i#Tm(j2%})tpxJa;14a<@7dlD; z#w&lr+GxSIpFh)y_jcxOyzKGAoqE)9o4IxkYh<@f#dwINyhcG1)gr)i5ZTT4$(=LT z3T?RV7Z(AkZO^UK;tPI8FalVeCl>!Sqt7(XWyVWoRSRABIG#9xnhc+tNyu^ymJBcD zZ>i@WPA!g?yt$UPWQoqc=Z4Wy%hpScfW~*m1~r8C7je)lUbIOA%)4?{5~^}D&ow~F zO9r-P+6G_{&j|3-?cs}FtOP3ylMtkHW;zAGJ~$UdOww58Sd`XN=MTQ{h==#?2sPN` zPH^Z{JT_K1cc}fEojkW2-aQQm?z1!wX^ncJt=mzjB;sX9%oZyJ)7;p401`3ZdK78X%P)gDxf{kRDFbd7xdskEA+!od?~ zo^v2i;7LSOb}*B>CzS=F19@IC%Rb;y<|%FZGIvZtAy*PeG7J_l%hRJ=96No4JzA|* z6-{uB;h;om-24P#RjllGzZFE7u@c#S>%K0><`N@+#HjKD-_6Ro!3u&fOUw~eT4z%T zi`*--P$FLBZ%IbP`|H<0-ANT!zOx-)@j&4 zysE0`LWvb<43&(WQHKvl=bAk=z2Tp za0%Q}kUCa-q3|`tfAty+Okrc_Yh#5xpPtkns~2wL~n~8pS0tbl< zWNZ6CTg3(s*P#j;4n|(5(~rRzTj4~*+wt`lM88ErPYI7$zeSNLub|Jn%KWVwkSu0t zntxNpa=n2V9R3<;uc%_pCL~9tq5GY5IqpW&pb*602{}_YN4^FF{*NuH&}w;u{nU}0 zH2lxQ*-T5{AagYHD%0n8ZiJg4V zUIKpbb%p-7TjhUQgs%(nOZn#{RNsF2A6NgeP4PF=Jmhpy>;cUk%6jjp=+43J0~)^M z|Jd?}%Gck__Wysye7_w3dnNXSl#BtBUl2uqU623O56Y{=llouPB}#xWo9o!rHSFll z@(rgl@%4LkxmC8-$n3>8Q4&8M4{>CM>l>&BC9ZN1=E28dBa@q%vq^jMZg~H}>HF0C z>UoS22pl=8Oe=do#jwM(L0U@@?V7RFgZn~f8}kXddYblo(wyJg#t;B-sV{_C_I(Ak zuRH}+(Y)MNJ+&`ad=aX*E$y@~Yf%M_+Nt*MQ!ObXc5S?z=jYpq{rc+qgJ}C-@8Ex+ zacq@r&rXX8rJ>#736FS=TJq!Cn7yYzj7}%1JJu9rUH4&zynWfE(G7vzMHrt24d>u0 zCPXRrs)}ebqCzUv5_D(NJ4>L{Dv?{RC~V&ShR{Opb9AYRO|}v(6IOdGp6C6U)2d+&f;B< zqa~;|y;HGsK~~l2%8czLLyMa5vtVCU>B<^S>3wF4!Z^91-ugGj09po}@H#i*i#*17 zy(C9C|L0a|DxPF<$JG^1)MXf$j0kaUu0@;E6*Tiud*RZbKo!<;&hkp-sW2s@X>-B+ z^FIvioUkLLlDiakX}?7`6OHg^j6FzsFl^Dez$#9y78-`1-&42@rZ!?ZO4(*8KiYp4 z*ZVo-e$cLg^xo_jI-HiD2?J!w(cl%(sGK)d{N`(@FN7GF|Jhbxj%R3}Tg`aTbYY`+ zr{N&jLs{cfiUHNvtgTKnf?|_39q8FB<{+1RqT&(}!MqfBLij>e9-J$DYy^c}aJpvx zA#`yeD?dqZI(9?1dG72P8v1K7F*Uk*Hf6drB-*{JKk3=9JsTghKaa-`8ghjy&5EY+ zlLrR!RZaY>tqNuoHL|E68rP;mA*6L+Grg?B-9lLvDnWD%R%x*5y(A7IY^+%Eql zJNzx_#zjYFPy36Ps*BP(8olZoC1fD^cu`x5SoC7{S>1powx!Hh=A1@}98v&mW zn(rUKJf5y#wi#oH%W6|BCZ|TD#u`c^gmxQpfp6_ZpQm3pv z#yH(8d=&Z-oO3#Srks3MWqmz$7g*)4Wly`o~@dfg z`)8zX)OCHKQ!7jsYJWvXw|Ur7A=d`1)f7xG0&I++!B^2Rr*L^^flwWnPP|kg-X0%3 z3k7Ly+N_MN=T@t@WfmkSJ_*9T%u%$7f0V|5JfqPx;CR4^6jJlC`&0d_z7b_s3`&)L z9n1Uoq^L@;2`U>o#15|r_stpKaj_njh=mp>o)|Lzp|id=LOVF0yYd_@yU|{!{&vfc z?efWGZ*umn+J=|SHHuRVhq=R;cJ$)o9emf3dF3G|^n>kQ$e^WSLZcH@AF`44m`oOF zVTY<7Hmuq8U6HpzX$5l=RV^cLP(lC_uG_9 z2N_c5ptR4M;Z;RJx+6DtOB_=@)Ljix(#Y&#ork5MRa&fmW3R16a(uX4ne2~paI@5I zYn*HRhF52`fan9d{fw+Mjn3-{Ps-&k6z&U6>Mh*pbZd0$lqB$7$F#hSXJ?-n2vZ08 zO@qM-P0(nf;yKvAa(4x6kk@h@A3i7)<+S7wY#aH2F4)~#RldPwA~04iFlI{ZPgEX? zbv-sU!xv8NtQhR*rDX)qQc}*aRcbn1>IEM;t2K!1 zO_FH-q1i=gs!t7%!@I`2B__XF2prrI&P;;n;dyvx;}E;HCJJk@e`6EdJ(BXg^G@>@ zIx!Eu^zlvQR+{)StK6fvYfjs^(v8T`)9-${lKy(3=O?dHw#pOkzP&}5#ibC4DmEY_ zTsQ$=v7}1H09z6$p6=LFH=?dM@onUZY?Hj_C<+3TOluBC&Hxv%B&gIQY2FuZA1?6` zyIp$b^|ZN_C{sKiY8q)f6O$9d+||=w(Gm_vEHaoR&)>o%66=}knXWZ#Gu*GVMfUsh zDG_mti%T@=!YT0S+S>GeY}zJQc)mGOv8E<E4opubr_Hnz*o$z*CB5I_qCqVE*D1eyZu-{0I?>SaI&!E6swANA4TT+0dHh$! zk(2ToUIbi32Y!I=^oiD!!uE?bBN?bTXD8tvDVUJ4NvP3H3+Rj2-g2i5NXfe<;Rpu9 z&)8woOivFr9yX7}V==A)5;Qaz<1cip++XO*Sn9_1{8jy3`lQ5YFIxJm#gQz0jPUje zRM{>EO@e*ZXvQn7iO9xXGWMqXmmL=lvrtSg04;IDc6RuJ+b;mLlops;-n!>aS%B&N zAH9f`cS7fH7gY1?h7Nn5j7g#zI^z|KrSM9k&eh;-!g{BuSi@C!E^Ux$F-Oi^PTp?y z%}BpRBue&HF-`Qtk=JVg!k6i%J;g zJ|75u4YG4>{E#7o6h= zHdIfZAu8{kES2gk3I59Xr-}yX&OkjB{ll$2_udp9><6$)+uIHyz;VLR{ z*oevv%0?xwG>gZF_mf~mvDou?_%k}TGmj5h&oLH^V8tTj$q4@~Y@*$)3781&+ilmo z`Y+Q$UiSSkEu>LQU=OimJguKWak~;_Pc%9Fej7kjP_z2f>{9Or+k0W$A^mfE@jIYu zOO9c(P;zoA0=nC~G8lHyvS2Uj{kDHeRdwC|8VsA7=xvj0)YS+n$EQPN+4~yZ`G3Sd zqe0sw&3g=obGz4zSgyFG38u;OeoFni3tU;l{>l^d{_W5MTCU1m|FzlMJBnZET=!Bp z{9N~G2xz1O{~ciHoUYkFE;g-9N*A>ryE6xOGblBF$&0W$CySR}IkW zcOYr<-zwa>lIseOyr}H8gtXhd7uSGxP(M}u-k>&MT^BkypXydc&xRRw@!{5SBV3tJ zX${_Z^XBU9tG`nB7dQUNV)~1J#^E#o0ALwX`BY6lx7Qq;8hi-ihN^f=^(9cstsU|d z`9OK#+gsXrx?989Ei%^8L`_7sE?%@-(3ZgHK>`y`4aWN0u0s+7Snd3v5+mLge_-r3hFSLVPn+W=xzFRY6kwLU(&- za$qDRwe19X49wCqPmawn`2jJC`A~i%e)k=r%y2KvNv}o%`fPF}w%USe&Y_stPK_-; z5K}apwVB!!78~gB7Iw%Xt=r=L1C`tJ$J+Vv!we>EASxXEAwN4-I19 zs;wYK9lV`L-XWz1#nzxVQEWLRcG$Eepg#Ud4h6yfc~@-z#O>TAp_0%!Q*7%R>p9wF zj!|_pbzu`3pgUT5s;|4ui^h90tF*NoCgoy+I=wS`Ug%qw{Nyt$Z#q^*G)uo=Z!c4v zL{z!GtF1*d5&TOG1vFogG2`8uG^H|gEX)b??B^S=Et~zhJ&fs`X zlgSXpUT$1%%{7WuNoT-YzP5691D{gyo~vbRR<5t7*XsM*$@Ya#RBtEopzsS_dver= z&Hb)5|H199&rt`|Z5sYqzf>mZ-gnn~KV=d5F*o8c@5_dODIcyzm@6U#0{}P)Y2h$w zc6g~^gM_Sj3@$0_f`!YVRvVSkq{Zx9JKwzbORc`-#}?ps35m>bpMj7GWV3I3fTzeb z_a#7;YyQ3aw(5x9VXhT~kKE)iC(=HOqd055vtmLj#us9Edsl#>G=|&IGplDmlJ?*` za{j{R@A&fXu2yt(XC*GyV;TclE|^h9Y58CD3#HiKtH2s#(wpI~w`v*l=@%8XF|2dd zL;Be=+{$)twOsaH&TqVX7S^wIJ}#*=-%tDcUAl7rrt7UGvAFt9uca-uICK0N2c=*i zVf6<&v)As!X{yJas2>z4UL`p*ZwxrD+{He7f5Vl5W3{AR?z?8`qH>Au+>{z1_nyK4U9_y3#LPo=f}-HSS@2_^7~CxM5JLlPQb zU13Z|I|CY!R1oxs0}_IPSr14(2PMa9AAgg7g|Zxy+%JXCk5+f+DpKBNpJ7*ZURrYc zxby>R+6IvBV$iP5TX?%f#9g9p&+ru79ZS&GEJ>RFJagnB(o5MbByUI=LmbMcDrb0H zFEzD!@Eaokoo^#Lx+C_Ij&+{!;lnGANuQ;Yo;1D5B&&Lyyc~@CxyFl3WMG0i#CW^oh&8HE#JS zojRGRf9^@9qGgurMG^J1^c379JfKWJ>-<7}0~Iv=Q?7d6oz`(7`)fX=q0tA`KJg{8 zu8t0Sg050-+kI{wxyJV}IV}ZQkzrPmcacU;Y43=y@8$t*@wc$8UX?!6#hW#Zlp8O4 zQH&wC3B@da&Njf{%!p&BX6rj#3oR$GfOmB+MoE3GegGlEPVYYX$SSSr4?~*A=Y|~V z4}-y1BkegkmkfFDS}Jy;h;%zpVV9x4|Cz)F&{7)0bifgtqfdTU%6$+{s65>01PxWN6*X78A~QVP(bvb&i}R2>|4LHH`5v)GDp5sal7(tB*JO{ zA8sG%qN=LXC)=>XMS0KKqY3Aj{ry}RVSS{pOY(}K)%rK0Is^?No#k2g z{zkxGS^v{v|L-Z!QAei`Vo<7;Z)w`TBrMNZD_Y+9%#no+RL>=~qDu|*Hl>%&hr#-1 zvd#;~Kf+HY6HdD7KtMf27e%|OoH~;Lh|5yA&gO~-(5JNQvYjTQx0b<+aLxGhO zER0zW8dx#Va=%w`7O<^Y#MXxibg?geSSzUs;+f}=uN%qYZDUZUHq81avw`gT@>)QF z#Rs*+qYD#umH($Dpv=Axq1WGvl zhE*yTJ3j+6qq-y_V!wvY4cDUp8$Xxly6u_%*S^9lUBSQp9LC^v9u(h! z{7l7&htZPhxm+=jcxv))6HnYoM!G1KFQzErd=EXE*tkOJN$iZp&EplJ!yJltN!U`O z-%aW-`TEVcI%IY z8`2FWvZa?~!JrnR{m2v=K*I6>Q2Nz_GoP%%lM2*$Q{mV!gNaO>9?5{E#Z2*x2UCWA z{9%zsK)S$D_OLjto5NOa*J}vb=45qXF1~#T)u~?`#}~uAWR84P15UraJk+H%+B*r> zd{eZ`BR#d(pn+;sZ@}O~ALr9nCS2&zWMzsM3Ea?{qz-k~#?V;e&r5*xGIjEufu5z1 zkGFY&_CU2tGe}tQ<9BH!h}HY*Q6v9o+-9ukk}s!R<#Z)lAj;ipa|_?E{JOXeMZvo_ z!S4I`#_zJ5vxsPPbF)jUs{}K&&DOMIDhy5V_+?zEFQ1NdHy8@G{%H;Jd!ysOOKJEw z_CF$kV4fEpGG*b@l7Aiu?~F@zB59cHwtNVlv?SJyt&?9f-vtt(@9Gdp<@$(KL8@LE zRj;{tIL^i~{W*4}uWaCR@d2|pe#0BT<*_TZ)LL`O#}dfZ%$qRP$jo{UHhv|#p1=`}>=&`}=GDv_9g{mY!}~@9ivr)ao$3i2>7O6K08qf{mXQ z9|^)_@>}wPkOhS$)kD2j)@&45gNpZzyUP6`Auo@4r&Uo0N;=l+{WbI@HjT4r!0QJi zuBI6Nor?C&3b0OI#t@wBtJB)S*cG+q%PI%#LP8ht-m_~#8!CXtQkoC*ccpBwDznwiz=^ZsC|O4L6Oj# zChxPdJ8AZ_BU5*DD~ytY6CV+{&oH@+*8>gvks+B3R8jP&&)FCgy)70Plxt+h|4LZv zfEN%cbw13U-H9iATFzTccpnqw-JQD+Mw)93>_d}GRQyGib4B{s7IGb@7P*G)R&7eF zv@C6p6S z-`ly8=03W@#MPRuxGJ$Nv4m^2`RuSG2b3|;uQ zJ?FWRs&{p#_ozJLVW(5xe6sS+PPIV5xyrHF5sEXbsZS=N-J3st9SqjpY%u@r)+c+G zFXXK+zBtkWZe!OCh`w@X&QsCFsOMg~=_bAq&R)Ki)H!O)JDqi!Rk*=i#0nrPw;$ub znLp0Bm=w@7v+t1@Rx*oBH7rfZKWkWNk*ssE_B*!wtD66Gy8p#=`#m*(+6DL2(HQ1Q z)hy_{(S3lhbc~=k>T%@g?nT~6qlVY+wg3Q8`FT)cxIX86ui(s$Twk^|@5D}$QB5@} z4V_Mrik?+IweI6v(9d|J{5!Ia<=jkD3u?J+QF%!tT&*w+3~!?aRx-J3Ra4;7P$2Ex zB)R{%HEqzukz+m6j5@{nxY$Zc&Tek=Iwihl91n1s!SRq6;3o5Zv)I`lr3(SG)-M>@Otge2>TPCTz!5fwC!zE=qG@=4a{R zEGPP*d8!~XISLcP!Y=6v5ScddoV;3k*KRkQ58ei&dD6bnStcDGYog7;KqfK< z*3q#0{xf`RjJ$5GLyDf&fiQ8+`C4bb`;MT$sQFuY^;cENchvmZpa(yu-lCF0Ax!9= z$LrXbCtIs&RW6|@0BytL;lXKQgUE&sB97uRB?E(M?r;ePu8L#`x6D)1c0(!0JZr5U zupRqg`LfYZ6N-p}7ipWJ{e*I?6RMfxvII-ar}>0bgLn6k=ViF9(di;{L1arwGI4OL zt68k%(EHebOyX$58{~9mwN{b-2SzNZav! z)T$kyQIlPvh#|ahG)oIi?B=PIQpY>1Pg#6iAnlr>ti1A6)m{B}|$#L>3if zb(z~gL`TJX9^9nJxGJ=BwON&IWtXjONM7{He(;TqLu+t5>9(mSD5RO%si};5=E$EL zEkQ(8Fl!$nx5~oV&Z8v6lsyX&j(eKuQxh{!cF4nFa4F@ndFCwIQPIAUcb12KFs?ZF zx+|khNu)uX@k5M@QShd?@7`qAJC5SCap78C5WEb}p{w2vH*U`r6LG}V4L0G|;VVHDJA|e6quBfkal&^2=R7c=L#%CzYa<(tZi8$; z^1 z1uP0D%4@XcP$U#x#9x*FLq&MBXu+|gada5tnDvBc^i$fM?29xH6+5wgJ+Oj(K*{b< zfG~T#U856xG@Iz&Tx3OfvdiXhmWl9V9mG4FHrzs%_f~W1#kW|9F*_6Qbq`k_AP1%X z6vX}ID}0t+e6TN*4g{tOp@>^Ef43TcP4oZ7*`_CCr&L1JcM`_JQE#cylT^eQ)Yw=q zVW>FnP52bYx!BhTVE`j1YJFW7eI;5Pc#;I@i^-D2-X%(^S#8dinn{h_Ey-Mr*#f=Y|T%jT!5(H?Gly$no0)SnBWAdSfgrshWgB71>pzYNsgun@Oa7Tu3X zs_2Au=#b0{!7K3_YTJFIax`QT)okq^JN?|)=;(lfn-)?oVLA;oOlPeHbGGOpjFsSh zidXfpxhTHcltL!r$TmLK>&(9ujOi>8X=19SJJ`~+j@Ei@)l204P^XiM$C(zzs-=cf ztvwA-jV{WHX`wN9+jZDKDZhumPc^Uf@$;+xoMMQ!kuVhnEa+|ZZKYFI!n@)KqEaCz zM1IVF$aR5c+MTuMTcrst(S02jBX|tHZKlqhroMm)MYaAW> zH6#^`BKdQ=+$H5{-YP^(o_h1{bXTU&Bu`zwwuOMl!Bpwy!Tcrl=KTBGRcP6tdOzZM zaJw+y_$_xQtTnH({Hq%O>EgrBd6bvhLRJKpKn`wR0me(Boi2A@2j?*ZD@+nY zcFwKZ8A`_EqF;+~n^?-oDz(iArFuFo4jub9v@1cr)_E2Xc(N+7$(!2olxcFRxqj0O!kPl=7zm@21CJ-$!NZOc}^` zMG%~sh{~3>JARxli)2Mz!}i=G@yaI1If`-_!@?qAlA<({U24+rW-ImUUmQ`KdTh@a z=ZIp_wd9duMjL7#Wo3p(9AV|*-?%)=Y`C3sr@Fd)$3BJ?Z-q=+*n|^4wa<(nMC2bl z+DG=_D0XbkwaNa`U9Dx={2o93I_zK7_&-HE|Ba?UX$Cug12C+y;~4g=_*vXB1Dlv` zU=>0TM1)arKASg?KEo-}AwC3vWvXemFRMlOy@ovT7`bObxKw}Sn zU7q1n2~ORm=&sCCT)k?J!8?-m(2RXtY$N=#`9Zf7a)+VLE3(Si)o!*6QO@0?EI9j9 z&AzdP^7}#E8h?`E7rI%5&!-J3W1)^$zYh8L#x`Dhj=lOxqgb}^85?~krWZ|*86@ln zXgPiSb1JOukxw&B2FY-hS*9h^(!%0uV~VvsTQAI7Vjm&kpr3Wi)xPnTa{h{GQ<1)-lXo03~hpU^P-_V||_B@eKUuWn+n`!)duOdQV zm@y~bhT|JmN5hvzc?K_dT`xg>z6}!kpc{%0stcxpqE#JSuQCs6wxsIFP1<`d;p5xK zsTbC67x>IlHg_*{Sp(JlmT{wrY1v)*=?}h*_E**amdDC>^!&-=5l|R_>CqcISHpiH zAuqG#hE*gpv;HMjL!$iXe6jvPDqDCBCE3e6^{VfA#6JC5_=NY8aQv1UWV?ZuwX6P; z;jb3=Lr#^6XBj^_RzE#tvJ@0P$kgSO+v))jUeB7SI%g>!Z1!|mSXmQs!;F@ZRrg4M ziP;xAe|)MVl(I&J?edsXPMo`Pd652;Onu<#oPG)1BOsGL50BHu*!v_n+z>{A%}Q=? zUHxv}zcdd$K@$o@N@AHyiTW&38}38uHf7AgfOVCiQF9uo9L?W_x{{d9;{vKQFEN>2 z971ivIn5Pamf|Cz>eFM)tnC4DD!x^seml1L)3oCj(}Eq2I)eE1U(N&|72O^AhK(!h zM`l&R^q#cP64(*aB&4{?iKNC(_?0uV9^Dcb?9b0*vc=sN!g_WD4%OIUt)G^xcS@wz z0(G}cPeH|g9qun`{2!z?OGE}bsfr^m*HqN~Jy|`39nJG9OFttrKPg8QEcN^XA}6Rn zEv;r5W)Fe7lkJgOc#IW@M+(hU)ibt<+|+(=GL#peI7A+izd@=LWoc}dRS)3&Lih2W zo%{-EoMHdcr;7G?R4cVQv559JIS!^v?qj;@PuX>OuHt&KA-PG}jR;qLUK9M+JYt1Q zaQjGBmrhqxo`kdqYmk@W1kPbb8|e2NDe$bBm)3! z6`Q2KXfWxLtB9i@;wtYL0f(AJvg5&BwK=o8+cB`xu3vB3kVv#hVur(#)&Tg8k0qSf z!Oj{zZ*;k~H&SVnkeL5?_1+q*mSh%cz1+4ZR+we8T|_RJV#_gKhau*qx`XsS-7jtx zZF}(Rc;6aF))HdkQewjRWRDiU0N-#EEEvO*7SXqIaMd~ZK)f)XoI?z5vqC5ss>+mf zGB>%<^i1Kd!}byqR-)Dh?K&zwN{NzNf_s;{Y!)~J$0(`?#UH?O+qxIsi))~}-Kpod zv}aFgO&f6flvGlPcJUGSJ@lG>deZmn-gzPQIns_IOfYe!BKco(A#soP18o{rN5_Mf zI%H)P?3=w@JU?H_&WIS#5|}1@4n$2c&-PlFh5Ms%5l^NKXC*uNlScn}Uf<^T|ET^q zCBZAHgZgDL7jDE%U+?VWtpzGI_2POYd)jef8U<`X28irPVK=kqU3+N56MJKFcBOZs zjt4aU{LpY<37h&Dy?VdX8>33CTsb$prZ?CKV+LQ8EQh7VX+rpk#ia(T7%_Dj ztK`dBwOP3v!j99|yR3cmiruzW9_yCE=0igpitMePpF7rh1Fq9yQozjMnKZSEb8185 zOe&KLE>-`sK7USid}yn)^Lk`-m^>%^qISKT;X5qR9?HCaQ8OtW*nR#z6Cc|Y() zUR>d4AEjyd{m>*q2=iSk1AF`~2DeeTio+Ju9vA&>arQc2+o0jGNql9N)o4oIpuky& z-yME_8L&^GAa~1TrK>0AwWyQ5DQFFeXM5_=6dJng%vVK<319pJQjW|*&yE~)B5 zGTVN{Uuh`+Lf6CG+ijk^B0)id?%Sm>ouYSk^7}^M5t}t}oIK4AtuQoKv<{;zG%@#uPMxJUd7{aGQaMTQ!(eYhWze?~{;K+S(O|nMou}Eq zcvBubR>jL~q6igjZ%B?xdZ;LH#wPBt@bpCbaO#Ofp)=A7kh^62fenyEBL_wG_015w zUHu9@sI9oHSa`GJN8=D>%e?ibvC!$%q7Dr=41yT0W&mdhYI=XhB=a=Upiqhiu4Ir_ z!E|c6X^N^ckM(M^$n_2P&g6=VogO83lCMQZ-S5=@L7<~ijsL!h!^oA(tMGbIm(Tg& zaKJ1Ldn2<-c_yK{zt&V)bRz*)ncbn@? ze3F@`;oCX*0T=d}DzkZ>D)s%~ebcgw;#?67_%>TOtc%Pqu-2L8`!lje?1D~-S|uPN zx}kYCN;kZ6^6IzIrJB@TTj@XIpPxaefqS^7`pbpm*||eLgNAlGFT|DJUsNL3!Ch+k z>U_COKDk`b1}&v|f@SHZhz;LH_^ayQ2@gQUZ`nt$n(oTNN?G{JtnZX&d31KcGb~$Q z5n6#PmYQ9tGVQa>y4w z5j|M$O9LmT=e4?4m$1tt(K1s}a!(@$1W@QmP)C^kiI zpQ>@iyn{~fg9%FAHhOTI&2~;=A=kfMIU0Nrc0ocs=jyk+YJDp4wguU&Q$~aqTApw< zKD@8~9K0KZTbbN}9A~{l?qOH%5hWR1IhrSZ@VS>uFtwL|I$pN(-M9YV)%0?0jLD^* z@^(M-y0{7cGy}QYpKvADn>ewRP)7+aK$}qCPHX_ZXRKzKxCw}9$B69Ld2{P3dTo~e zY5|;`uO56G%eo_$-||)M@gHwe={vM^Zul*iW&k}8L~lT+GZ_pR5M8%><=6I2{Vx&{ zToQQXEptXwKaAFg%Kw*ptJ*)5Vac5OWm-qxE%`l)i7ByD$zCdY^EUUO$)<&t4}(`T zz{7booX+>%-zNX(vX0U20F&kxcL}@5;^Lr#tLNRp%Y_HI63xpZW6Eu@?_0Q>HHafC z+y1J?&UTs@-^|@$qQqJUhRgKWS%cFY^Ebo^XiK%5DUHvoJx64Ht7D}=d zfO;Q>p*-7r-@1KY`B_uG33Tr=ay6txNnM^w<1>ZndI+gH8}0`p4V%VWlam z9Zj-Ud*j_uZ{17F@4of@tLp!3<;#iFd;S=0+9T<5cK_2!nJ;wH?G972U+CT9wzn*ptA>xFzR*RhcNxxiKW5zd)lg5b**caHy)c-bPNml=RVa=-x0j=9 zKtnu>4*Np4om1zLly@dEBCKvL#n=h)OcuYOPc-`n&OZMx|ss;pV3 zpZyGNG~oMiHyd02l{Lut7rGnPQ(sQ~#v_V9@s1#i+T!OthEoZ&l+?2fF?r-|6ciRP~#w@XbS2mby0odf4@yiPlJ_pPt zxeR~n`IptdJ)ZsImzuERZ+vI)BuOHF0HQmt9=?ZV|2XuW;~=ib%S&-uh_jIM%wfT4Mumv2K=+%+Z8dlQ}n$$2d;*-}o4}R=OY5(iB4S%@I_b2}B>eb6ZW!gwcSo8g%T)WrgK^fjOf9*rg zLq{#X6$igo)n+TJV-x&4;PG`R))l`bn{==&UcIrdUngmU_te^%TDQiiD~HA-;s!*z zj@2Tq-oxhVTp-Hi9UT`*`Ecu@n!9l}M>GA8@GLqkSdBtwyiIPB(YS|`F#Aku2p6SV zrUEe}hKq=2GBq&2oNk++t6!zGY*MIE7f@kfO-K|-mS5A>oPf*?jEh5vUp|{kEJ{hR zH=*5YSsykj4Gc=3A{TOcgpnjrWRNzoz6DmV&noKeDiu?u3UNO1wjPQQ&CG>1DJT6n zgQzOD;Qunzmg1Rvt;(rYD~mG>qwq*N}{N$&mte;Ay!WX z#SIEREV=J}zbLtxmsj}WA|2?i=SA~l2H1+CfFS^lMbF!+7{~~lKqwq$KCE@I+cg*q zSlcK{$7F+Xk%pDm>TS$fa!q9ony;?RlD|s%e;7^(CB8Z+*KRr z`U=l-mesk}y<46E7o>Uya>-!~8&|kShFjKn4ZO_xKx9d_G>Y(k|q<2SF05IoO0;GjFTd#)KP<%s}GstMVGC!MdO%Z zMfkF`%(&PToPpqH%XikVN-hRY<>=+1P=s#JimAb=w57Rp9-(BnZj%lHzMOrD%-X8j zI5ET~8`4fhGrJ-+F0ouHCrQgBudK?i)&9!4D8!ub#DEeEV8F=$1Pg<7q^^Yywo?Qf zqZtQDEyYz15_<){g6%n315LBQ!XiO4{acN2qX44H}ycb#uS$*Kw=k2(c>IZDrdidT} z5B*xxw;HMJItjBT$g4){qK`Jak02*ZUZFe*UxgbkZ|hV^CXY#v)XZDWXtg+1I_KaB zeQtXyc}F<`Vgj8tN9I%#DIG|v%|@Erev|K%vGo}AR}~sHB1TKX%Y~ifvi$Ha&CZUT z>&az~Zi$z;>xAG3-_Ko9lcJ0o8y{fo=7Co-asHgcHgIR>zp_Ouc;H zxJk1~lQ5f2>N-wrU1DVxjG7UoPfL^xnb5x!$M>u_Z$UzzB;Q(n#zrud)|lAG z;_{77zb$C$QrgMKvYIk@Cz(tF>T(TmyMPnR4%br8b{EzY^cJ+Aj1G zVD8ifH_*~JImahTx6x3$$%0>FE4+E2e^V;Ue4PK(QBya%OhV846YjNY_&%ZeU!!qgLpi;V&%g$Qwc2-#$%z`Vx)ePsxUeo+Go0P{xWND z%r&D$Nsb5zv~pe`n%)YH&gJnTKj6665iq~0StCRrUMr)IVlk~ z)BThUR;1VMO6*KUHRn}YRVT!d!r{Dzj~)b7)p(e9c-}kah~*NSa+u`~k<>!RB%~%7 zlk+y>u_*U*wx9wPVIx)I0nFpbwTVDo>Qv(ym%y)15yheSnBHT{}?$MtCWl_%n+cPO+D=-_G}eIu|X=1M2*4hb6XuvYBpl6>zEfKLdL1T z3c*;EiH)mp~F#1 zM#Szxyrzc}GdA7b=9>0{vgav+n-YEBr_M`2nSLOW#=!_4o358+If!gIngdGHMfsaDo1 zD9rIE8r#awad{WTa~%QnI~c=jqVzo4RSzPv;xc}04;9;(s{+0=Xv(k5p8|s=naKc^ z$NTpV-HDav!quZKkX}iq_2_=Ge9>We<~Iz#rY9{*>dFB7rovb4><1{___WBFB}x4A z*`Uy7EoN4E5D%_qh4u70M=&+0?#o2e^XrfaQ6ZE+F#hIzp#^MqCCY-$Z8D2vd@3TF z6)9_%1fGYOAjo8Gf9Sl$d-j(ec2~bW>bqk&7eiYM2sSTvHWZB|Z2WA!4hc<4ebYP2 zddD%L=;?g&MSQL$MCr^WM&IzR%dPPfS`) zxGZ25c;#}GZ@<#b58od0SEYZ5w35H{?Jn$BK!1{Jik7#-l_XK>xsD}<<65`LZJ_C z5Bg#%3>b1VM&)0WOk+pyHW-*!Po4&jHM_X_?@xgE#~ob{8D|vL{Zdk(0aZ0Bl^w#N ztEfEOC$d6U&DD~u`M)#gJ;6Q%O}2?FEiFgk0r+Qv=X#KUHge<;6g=qJ<}7#z%wdL6dhZ;C^T! z?wY$$-ZzW9*{?JVbs!VMb~Ww3gkj~Qyofve{l?JDe3tZBc5NxK-m2~+m@Vd08vQ8B zc_Ku!(n{JvqqA=u7oO?o%P+^*T6{rj`^Crs2boE8$0v7`Y4ogw>)F3a|HQKEj?G~C zmz86_52<^iDGmcImoe9_TSR&arG!>hRjyav_P%VcJ9(2+Vq{4Ei4FHY`CzVl(`hz$ zDnUvH(Ic~KKnN767kiq@wsrm24*jb#e;D;ULnj@g;8=!Fv>p`N4^m{Kz0W&OxJnIMgfkj$i4 zsD1CQR?eZ~jv;{^4B9?2SJ2txP~T4D_BLm{f1i6UZx7`H+F9S z`ubC#C`0k{18!PEXZU(JIc;UF3w2Yu zAk-c&>uagGZ7$@Y2~*05K)#9W=G$txxME@i)W0xU#-cq!Tie3+dRPiOu~%_B!@)T+ zcZlI7UANjd41Y^cHW?RvZ1j87%W;uY3YWesc6LAufyGrmT}nm}@^a85o7}`NzIu+w zE(02x?`3=X;c)R}v_ohf8PRM}V;a*iLy*Ja*u2ZPV&gm%!e^xU?B6;SKdCkNi6+LL zs;8P}n`i!NU{qlQ2QZ9IuVIFeEUvFR@aMCEywdal4wjQ-!aFa6b>#^IGDu4@m1j~+ z+TmV{gE&~(UB|k`Vb^vuqSUZjx!inrr7O$cdnwAj|A#EK^EILK?>&W@wZ`UzsQVyx z>oxCeVqc%A2o*JII5pKG?k5_hGE)jf`U3v?@|MSJ#%2$=2X{M1Do1y4nh)(^VmK1< z8rRulF!!chgzapvM^$CuN~X+XPvNNmC-%d2hJnWcX1x%gU1;l;EiZ?!dju%a;{hR z>!;DKKPj#=X~w_MgGi2EwDYbpH529Un4p@Sub|sESORc}(b-6gTA;B#<(0C7cj_t#bzse=0xV*a1a`gf}6Vu!s zEw;9gf^z>J#O?<<}kkk(Hohp0oNSaw^c4 z(2+UBJqh#lElDb4z5MGX=P#&eOD2!^?_nF0iVS6KuJGeB^Lp-k<_(Kg>XH?Cnb*_y zO~WRp(`YxGGlsKApPXq+CX{N6hW5w%yBo-7Y~n_jP^7A%1nAK{k0Kfx5vhBVBg^MN zB?aAx+c?s#bNVaTVXhDe7jNyGJp~^>d3~q1MscuZ@9HLp{!}lA9gB%vJ z>#+Xi=~9NEw0n&JC$ z5sR#98Zls-XY;SzZFPPxxpU*WvED;vzq6mmFJ6-t=5EDgCeOeC09F^R5vsu?mBMcd z$_4J0yh&!$ydD3u3ZLs_RVV8RV7%`-gPx~hm@Plqg}&&Dk= z#Zao{7SyzLWj@X~6YO^|d2iE;{s2c`TGK7g&W0 zr{)n4nbtEX8O?Ea6eiIdKgt%Q zPrnC%)nqMvsWf%bYQv9x7G*ZK>Ytg$6+6nb9JZ3yPX+?>D~&9C%RM&*UnX!iozrME zecmj;!KBEnXAk@MyTuG;(UqfbcuIPQ*3@>+UZhMlw3o;l+6 zuV`p~HWKVm(`Zi1NPZ?N{$2Q9ai;v`7LYWAWTqBYPi2h&bVBk^0@Tr54*d?v^g$SQ!rbXjdLp=P1)4vGZulU_s)?)Z98t4;#3l`$~^dBUwCk z-(yS5-i`_Bs49zIB4R*IN&#EQ4$#gL8S8_#`r`VeeAn$FNH70lvC8xCdF@URbU~|` z>2lGKskEQQ%pnQ?Y!f>hp=qKsT$VAOKIy)5@~IL#KbJ!{MS=ksbO-k#p)FT} zhvH@-fwl_rf!BMcW9?>8Y_}2wRb0}T{jv32dK;-kpVH;_y4>%*Uj9d?{#Ci(uYkYO z4E+YqpLBbnQA8ol$?y>rGWN8xrk<`a(@oOj-kMKEgd&zX&sbXa@_Uw^;PAaNamc#{ zY##m?=4Z=4($hN>yTq+F?GkP2ZZ4Ejp+hO$%dD7Y(`i;0m3sIe==>Q0=a#@L>`_uq zHwy-8dOv60^P$>Yd-obVF3@b6r!(APF~Z3fI8yzeap0!qFbBusSvg_$3+MT!fR?HV zP!#sQrYQi@7M5it@FYk~Xoj5cA%Jp>UoNTOm=*8EyF(!JXNGhvGSe%`8I;uRLmrPq zn~a&{I!3@bj)#P)oHEvBEnpj}Oobf&~Cim3sW-oOPI*IF-N zx+_%n4fcs7Z`M*cVa;UJS=Yh-Xw0&Ib_|7@rP5us@6%h%%AJ~MeN41>)XFfme)~i? zbTg>3ccG)Fyo#(-A-I}h?dOV!nx3RDs102U(Ty%zjB=F)D4a}G2~P>%9`~)^CfA0- zS4FYJB7Gpn4-m*=57L-GL&+p0Cw5|{+jZ~JRnwY}VJwXH%`=H%}b0qU#ie7zW8<>8UD zuSnh2N9919jKWjvy>es=MtOEzt%oMnjbn;x{S|F7LP>-8)%b6u-qIFsKe~S=<0x5y zG=R*Vr>v#s4(G511F9(!<3sl4TtsXn0hJGXFZYG=?9;oWU?f6DnhXRt>*-Fzikj<- z>JK<%4q1HaV&zMRfGeJB$`5~{F@pKaXW5wLG&rbQdng^c4qa$Mi2nYJ_pOWnsp)_J z*^`FmfV}Pz!SqNO7BCV`F9aV z3&;uVCmbhGhw;r?ghs*qN+(0a!&?C|c3ll7%H}^)we_=q?xNPTDm#)F0Opb ztADD>%|e<#g7E}G3(?%Xkb!iQ@sjrVBoQ&Djs4VK9Ech1MtP!bauuWyWTP5IN;;|D z8F_;;>TK-@hd9h)VlrI$#X`!*CRN%z5fw87r-&=yr7_X^jLYs8hu}ex!G9Gml%8(<>gwoV) zKxQD=eQe#qQscFdI)N+DWrUknGoyKWvQ0Bk%lYse{Fq4bXb=T zX3PCx`B1I_5n$qLEJmHUoRF(g0(YK?-;4A4IP|Xhi%d-?gNMK()u+cV%xtNeXplcp z6rBkZ`*d@}e9jSvYA0(4HnM+p1Fl%^63U$-BtRjsm%2*hDrd3S;DkeD#se(~npH3Y?zJsWFM*DEh^k zq$H+>AZ$(E47gDrM8C7xnX)bIQ-GHs*h+*~=g9AStffWiC}C{djmtg~Qbt%;9pDJtKex6NE%MecEgusxMPsOLIPp*pjqFxt6dqUDt5uO^JY z;G>e3U%MO`Mf(eBxf?h69W);g07x_Ay21Tsl3IGH8q@ddA9RTD7wEE9i*6TO`4I7jOCBH(J;FC%^vtUI+$BsV?+j&kbs#Ra~Zbx25uFB2! zZH6r^n}nKyuV=p~u#MqYACNukJM>@k(*-AAQ>*@b7+)_D>xV00bO;C+WB!RI&70ug zs~O-oYg-tXy+j6h${Xq@H;-GVW=fO2tn6xhTc?h1jiz*G?UZ`yWTw)t9(uajD{f)~ zm%F1TF)yG*&-C6ZE{ie%dobeRKvI&Q5&|)%3}bSUrUMy4%v?v-yvNl6I}Iu(a*c`AB!Az6Yw^%@0)(V(|V$ z2moY7cC|vMi;7J3r8|_uGFtFObjPftTu%^KDJuQWF`Nu!8t&B~3I&81BxjQO#!onD z3%_m&o*6_bHy%nSwgmH7*{5c2g9f*4{EILfmNLCyrKUr^P3CXgwf|h2Y@Pg_xCi$Q z9z1~twf5&1$G>`M9G*ilKQ8)QU-3uw^{$)PD^J~l#y>0&xvXOm9UnMCY z)nO-Ze$XK9CzaK9>(+n%h=1B&{-7TCmEQk10RE&SDYd4PXKk3=Mau^E5~iM>{E6m) z+PxMhCOwGlYml@a)8=1jQVe-;aPFnsaF1MAPnF%32?t4A@*B0C>p)%@cFC+#{aw82cK>QFj*m@Ae zKvWB;X>%#Du%gEFc&dh6J8$MDsjz=6uSdC5iM{a;5&d)D{6lZ*lvgrYBFyjn4#b~d zy=YECHbbk1vTHwJNZt)w8bT8OZD$+C$(sz7e|g!|%}jT|wPsRNjyXNrKW})az%8byfl9 zCCB=cESU5_*u9ub;C>KEHzd@G)lY2IY;D^}it20qiMuL24@GerA2wT6SY0VQ&s`#wq|oX7@H-rT&h_S(s=nLT?7Wkj5FrRUSNyOQ{&U!qX6_;W^AWp3 z*U>!&<7wHRpyuq3nyAl->M6YukLs_kx}m&5G0zrctpYq=Sa?E6t5yUdymmm-oo7P}O} z5UBAHwXx=e9z~ujO`Xo)(&62=fp>6RFRDQ2Y;d^{S97;fJ#MTFqyHbL`0uv*?Mt`A z_L!?-S?WnuiCMR9SXQvgcTU|aW&+RBNk=rt_jTZWeK5&_k#ly~tCdDTH%3;MAL2DK zExSg}C0MdcARTI`j4O7Rexiw}4Sd~hJgl5*C-0p01m4MEt~+u-cVV%nUpBKme+0LM z>hZnK$o{Xe3RAn{561ET4NP}`8#za|vwQ-xR_!_+?~)Qx&DO?1bToXVCU1_AtvsP0 z%OIQW+A-MozLHpiwa0>}G|@nXO7s$fxCgt9ytY@K)2h)FrR*t=PH+-;PdKESdrs~v z$V1y$b_482a`Pu-0G-J$#u351VC4!MGd+vay}Goz4~h$e=z_WnX@O+*GtQfMj@OF7c`Ad$?GW%$>_IEdE@)f)O0WHTS%ezdrElA1M71> zjYCMfP9y_spuT4*H@9`ya!T+ylu(=fzUWwMOC?WX7?XevTr08eOQDdAh3_n1u0%j{ zq_%ti0a?DZT9Uk+8f1?6;qNj1CGY(48#$&fkYx}8szPrjw~q_onBv}Sizo^05L(7&1PnMw zBaMZ?l!MzxK4$v**UH992RR=BIi8_sHUv-nysaK{+Kmt%kC)m3D1#wvO|9U@x$Cvx z9{&6E2g!Q=q7aWo<-tJWr^KBBk8w zxS|O6p8djI+da2y=tkj+YsK;&CaoIlIQ)xz`DtkWDU19sk{8_hx-1eu)$wTA^?G}o zgX^8NXA>>KSTw)zzLxS(a^)LDUq_1C;BXPQ!4zkD8EMtfUB^t4An^PUZVFjvnO=>b zREoH{Xg9EU0b&K!P6ToE=HNej4`Zl z;IwSjBz-{3Z4v3;4}iawPA+HbBRtyoz}2^(KWVRA>kgr%1CXek$wm?DW53M)XLoVq zFFHCL*8$BpRbgGT^bs|%#MkXjA%?ZaMRVq3To+TmTPo`H3Z%xuv*>mztTWJ)>R3*w z9`lL|RF5|`koM!|N3Rm#7Mrbh^&W>;ix~U*`W|2H=y=fhl$J@4s+_U{6)0`+@Mqm~ zP%+b1x3@PVy4l-Hf^=Y{s*H&3OaBP=U-I7H?ehN?D)2X0{`8gZx4NjtK)TcS{VsnP zW#m-BnYZ#^pE1)2kEo^^17WFlPe~n?A>wEFsk$lir5AC=?rcLy_gfB-gi!3v5IO^X ziZsy0#0g?>6e}YW(zK&~^yi)Yyb!_h)pNjSE?@wWJ~93zV9;$9`J${Q#q1$J8*N|T z_z6xU+xKXz`FEjJdDtjd@BS)+!_u{}F!DYUZ}uSDyCV0cODUW5!Jz+rxXSEDz- zq5YPgSriqOl;P$qNUc@dn5z2OkV!aodqPPIk!>ZpI{)8Tyyy1j`ylV9(-N+5Cp1_<7&ewOf(wz> zA~r!KJG9AhqteugTP@9T=2&f-mAk$PE=kwb}G7bi`Ly<;T;3gf+t9KV>kO`*% zZkKUY>FXr}hh)7=;9f{z=>`9vXaJR;+}+j_%6hpep-H={up=H5LW}hqa5M`ixw_z8 z!qXrbn`>qnfWUS)vZU!j)nrzpa-Yv~;>{L?xPt3B4xejeuz2jYYCVsD%-$FOu6a0N zq{aA_MyIsWvlEiBXSy0TVkS~Lc9D&1noMhadNx`aD~$Q z##a1s8h2_S%9ZD1B|*5R3U@jh(wB6c=2`vdmHDOa?3kweNvT$-AaZ(&qRk$ham@1maPK!aMd&Z^Z|hV|XSIr5O%(S7hCe z-rOx17Dn2A8Hbj{o2USR#>IRVneUhY>z}(}OkU=gZbOg$A^MVelx5-(X4M;$pWcR* z-1hKTyOYlLx8v@~g;%*f1DLGy?vYIc5U`TeW5f1^v0O)r2->@Ggle-@E0@2IaVjgl9cYx9DEwQae zqf{kQt`1jQDDk7YM*FO)mPUs1oj4U8MoJY0Bci7JvSTQ`(LHjhKQv z-|dAsUxVuhdM)|O7?S@M`2cchg)?4kkONYfUZ@OnA^8p5wCKgM1A0Y{u|`{&Q4pDC z7MHc0!omE251XbnEb^T5gFHL|4{}C>exiXhs6^OS81;F|fNeP2zM4!W#;up%bIYa33M86kPwtjJp{@YR5qGWYFq{j+O1(PH)^jsxRFKD|Ce{+B4kLsW#w zFhR@MsR8g4&0fuXWeG<1O+>A0E_)qoLLkD0 zE{4tHhG#t>=G2 z=8r6Vzu}Ae2LJzxe)3yo|KjN%cF;TjEjs#taT)vy@4s?BJ@e)9rHkQV(NB=JTTL%9g2mk&q{Cht5_ni2fb>XY*DFObo1BmsU z*NSKL0Ne{sW{xoCOZlwKClCI(5dDp1aP!y0$iR#3_5 z{|6V;XPV`Ym>TYIUspK);%FiL@W)>aUw~mtY4EHySP7Nr8h zO;lkkt%v}}O7-O|hMDJR47}2CtW-@<`;fw<%5Yo9$@6^5!s~#1aUaee&mxr9?kd7F*6qJ#Yow0lrcr#O~zvtK*!QBh=Z z8W(%f7tU4n;&Ie1r`!oz=l}hcJt0+R?=Tzx%28tgdft&+ua}X|R$%T^XQL5TC{wQo ztO;Q-q94~dZM$8V{BCS~OltyPoUdf6C^UT0!Tz%E)!r=<((+L+?pbMkd(c!^aP8w0 zwr@vMYB6n1C%Av>SFE2%?bZ{6p^ zc(AIG+kKotLCWO$H&9#+i?W+}K@Xx@MCv{~eFzjbS+_7Y)2#$MF&%4Yyc^=fk~{=6 zTQ-)V1NwTpYTMo}D7d}TPHGCyu!PQ68Rrp#SM+(4cVu8>r8_rwng>csqZ2*y@p>zc z;VE^)j4@Z_c|i<@Y2ugQcY+DeS0%(e!#?yXUT$z~KRAhB2{VB*S=C=Sg>z7HZB#xo zZ0n-B0@}$LuFXeYRKJV2aryfF*=-dQ4)%)oC2G+*1(1=f7eq%*y%9f`;XU!s8P10^ z^)}83$MKkzr{m38r7SY#sG$dx4p)udk4=UWR-QcFjoqZ7XYBL1c!-`Pw6ZEc;-!Rg zYNxPJXGy~$A2s;kcy>y*0lv12%|xv%YyGwCZSaeWygcQ%4P~N2^P=H3Tgs%?3k>gK zB(gebM~qEL80*zo@~fD#-FTBOKWgasg0fidX5Y(s(YE5okBR}X8RX?q9O~8&cgAUr zD%Ne+HxpwChK47?lyJ~nqW&y6b=R!x(XNUr(Mn+q(GIG>E>$2D@53W(#0lldqJ7ab zn;BPkIN^aQPU18pp2)vj5^bnuh*(orPv7r`kIXBFT;$*pzJez0uOaM&DxfzdY}F{1 zP~mxELin6>c*~uV#8{hG*VL0ttbJ|f-ZX|rhT@T9vv4%QRxo|WRfZa_m-$W~3?)i1 zD5Ep-NpRygt#c$FxLX+$)IF5prk;vWaEZ5y!Ol$PUN zDHe_TY)WU~COvL#T|oY5qM}4Kn8>_xk=a`pbL|%1*vajSd8fh`#c#|- zTSt(6b)CXMc+QF(Y-yU&T&}< z{vXEb5=<*G&1m=Q2Hox^m7jlD4qaiij1HfZhAt$Bg$tXe=DoIlV3znoKQ|_Mme12& z7FOp2#h-}M%O2?ca)XZ_vZ@x@j0z?w!#~yG!V#fk2AsO*(9EIIR#{Z*tL0+bkh@`rcDt0>BHxg{ceReA>9n*^9Y`7qzl=xEylg;;(Q!B0Xu_+ zNheC-ruvThP!*0(cl^}8-}=DbQn%)6S8|R2IJ%Tnw=Q=(8(1fQ@(%y+RMMGbg!5B8y$Ot2_CW>qkjdzFf48 zhP-#38iR*l+FD8o3BTd}iH3XcCFf2~t~^Q!#?Xzxg6=Vu=yD9Ua-j)EqxR zMe_>Owd;l<9QskB5?jw0@)`6lzf7(C*i)AAh;CeCOMGyB^F%5!vBE3SU(Y%fPpjY4 zpL=c4N5eCgfe+o<{m!@p6%jODJ)p`X=JZ+ugcM$abj{dXuBTVnEc!Y>vVZkzE16!1 zr76}VvC%Ci(%B#Lls*z-Hr|9lx_%det+KG$@HA9O@!!pK*?|oC>-wFIdEJ@$x~ubT zWIhH0M(w^eng=oTUaew)Fz5l=ezf7tcO~P;=X9hl9*b#1qZP#+=0Ckt8RWw#tLdhl z5T0muxD^QwZF^bDg*RZS@p8YQzu644$TBCVW|;a0|2G~WJ6c8MqRORglGMj$FFJKXjXv(}xYA8^sS^Ro%A^n$ zUbi=yy>8rL8HJ#OcwOn?|6wceSwn9px2@8#M~t7pwsF7xFI_nO!~LdAH^mhFRM6Xq zRT{+h^5MJGJnHngrb3KT+Y}-|%$k%mT~50C=1Wk0ahJdI3asr5yg@JD;pC~EO%bis zh>}HI7|0^CM020=7FTVIpzr^(G(@GUfac}pQE6}Q9Ia1;y&B(})iNt}5hWK^`BGk2 zC^PnU)f+-nbR+0rQqFaGwy8w`KES$~Rqqx#EQ2Q z?9-c8hu+emh{4}Tal|YF!>d-zcp81=d*1Va*jIq0vlrr5U&Iy5zZ~6p>VW5y2wo~1 zhng!RBsCKqy6hlVtBTO`Sl;wG!u0_dggTfX>&mLkGy@nQK&^p|@4CfWwrh)xPCKky z&5L{X8WiAPhv+}ej4)2JE|7_eEPm5ur4*VsW*}3DfKP&!@XZY1OZ7`QSLs=b2F~1r zq6~eJQ2L6&L=K#wZ1N<|QQ8tSzb3=QHa=IsUsM{)e232|;%kC%CzX&*_3P@jGxvU+ z+r|*3oZj!4W)7`p``ml+AWSQSUH~h3Au$n|p@if}V_1rE64xAPHeh|2f(<>Dy{-Bb zMA5A_$v6HmeK$%uHjK*cL|y%HcJZ9y$?*Wt)S3FWo{hxz{$sJ8z2~nlM+-(=i_>ny zYk(^1&d*Z%9KnM2$^Q7ew3g0N0GyDK`8JA6C=!wwIlP+S*P#+R%&v^UfFOWj!dYy7sJm;X(BhKcKrF8; zQ_c_dj7m<+fNpTJqRJ0$&x(_tlrzp6ZW01^MNf;hLQQ0A;8nz=O8v+P4DyZ2^WhlI zD5LvsK*MSLm2j)jm6I#&2Zh4^X0b>Q-$eI?SWk^49lu-KmqlD3v~%Gaa46n4hl9G^yOqr>KXqlI_@pX(er@NY}t>#Q?U{XIhf`|X7&p6BgyR_uMbvbBfnbu# zbUyGzlFf>_mRGSNqR@yRmLKOQ>k;&83St7@ex#tRGxWe#qHWi(z*D+Me zId6q!7C{$)ek@PqqqQwe#A9mg`V(yFRXVGQMcLWO3x#0g8)-M1!rP*JWFUB$`7X#~ z?fG$a(U1XLj!TLFB28G&mc1t93sM{BY-d=suhn+4a7s5CKbYjfnb41XrUw5z8CasH z1VgVC1AxjU%!O-RC*y~PN6li#%jz9s9qs$f8HZu6Z_-nJTn6WIf=sU-XD7 z%rbeRKG?2RBAE>*ZsslMLCKI11^@tnQgRg%yH3m2QPMGpkU2H8;b1+>t7(wOSoE=UktG?1uv`mllC!@8IJ?&?f;8K8N?NfG%JT+qkr#0xY7oo9S^4nB%*b&pv*9~ju? z_p$8#Nam60O<-(tCA0<44(^G;Al>7Lsl3p{D2O6;< z0M51oFqvx&vGig$cVx^t3JOR~v`>5|cLMJ%9rK7kO~4+)&nH+)zg|kTyb<;jO|exm z1Kx^Q zKX(+ZWW8pJlhO6Fap;OFtDXHVsaA)e)yOfz3tn4$#kmcWG*naulJf>RSU40O_}RtU zWDX7{pH$AWK&(b-^%xUk2 zcz<@b8W{h=JL0YPTC>{q)KOQpO2ip@j;Z@E7z*5CLDf`tBqinM?=O&Ouhgx)lJISi zEfqaIV>VE5Y!<2JYbh?tu5G!h)fBpS$u{=XK+^s&f7V+%$N;Qv80MeupIAN0D|j!r zB_anktnH^(JY^Z|u2#%BU-x(*i3DSEqfwAmerv`>yCdW^tzH79b2JWGd@_>LljNDe zPK*M7sOEX+l&7f)Z8Ed)nPNM8;jZ5kzA?b@;LNJ%{ICjmsYF*F$~neH2{Vok3dg>j zC^8@0Qie3j0W^(HYl6?2bft<}1#IN85~bm8t7eI@&#tQHIYy2+cz>U~(`XyUvoIN? z!1f2@(VCLJn?kow8%aSSXx@pu&_dsMNH2F@SVSsz_8m|c z$d}v3d#%&L-fE<}`9ph&kCjjhiXyXErS4s5=3Q_ZXi5)Sh7INjqi#L*FH4diBNx=L z`6d+%t1I-)nw|S#&lqFX{vQ5@>Yaf4aYvDJ76Kuy$3u)b!E_$6B?BCnYy}NvJKG$D zDu}(st`0(aM@11&e=Fpf-$O}2Hr5|o)- zv+63t&^7}$#PVP;IX^sH@`88rF1g3wyPK$Z;an!4q`O09eht@o+s5}#i$C?v@n<&o7W0Ux%(eIqO2`z#pfdrY zsci=ZO@Ywi_8U(pd&G>SJ4W4<1Z7D8Jqjssl`$IgAend+wrV87#L=D|e@BK7J zFwx?np!5@Q_sv0bZPA#77!9PL zJRQ0S*6#46|JB}ghc%ULdmKj{3pjujL1Y9CC4d4F2vw1yR1rexNGLHRfDj2t)3MP( zq?f=LdVl~4p%;-REl3HyC{=3cNPn4mf7}`8&OG1!?)Sc#`}psiv-jC$?Y-Apd+)V= z3yb}R|6IlFV2m&xIe2)s{Nzt%WgU5e7|cSO12)d!ao{E5SoPi!Ju4px)&bT#WF?>l z*V5tfOfyJ_p<|QU6p;PAnomRLr4LPwopx9jiEB0&-*ORw3Hf@h+BbLR=8?H3KPs2) zypS+11L|HziKJ3B3bO>*_%+lL6XUQbQoEkXCsZi&oVt`ATW+;kKn(7uv;Q4y^lu=O zPnge|YsXYHOR}_?aLUTOU-IWyV9cP$_QHFo(a85z&~E4AoBT330TU7`iODAJg)i25 zM3s()bE|5Gp|nMWB48W&Ng?A(+*X7WS?giUSMEFf;)3x%oG9ymjY;W06`g+rn*Ohv z_&@0wUj+xRC`9{!-C|g}zG!fHN@Zj4+sFT_HvCO8E0b!_NvVlr*P?ybudV<7Y8>#k z^S5XWzsJr02cgUk2=$L)|0jepu7}@n@qVNJUib=!VkGmY-o=C|sjN&hs+qI)4x7R8 zpZ@Xpf6SAQ^H_D%v(GEcrcB(d9N^|Be20!}#%Mentlj3yc$Z5P*W!?3{dVmK)@-fs zPhVsBtwYIIA>bYwk+vX!R22VxvmWIOc;H*kI1nUuy=o9k0iJO?wcB#sX z8WA=sx-zGEUJodmn<$0{FEPwB^)1h|uO>M89lM8Vi#7~R8S}a(ccaj&w7^?{d-PC0 z4{3s$JS{WwJ=6R7B(WUcTlJqGV|-DYuJ^Wdq-zfs$ibVl)H|Fje>f=gfTjmi>R?43 zFq8v+dLT{?r0Ib&@z>L(#Qt0BJ9Oz*9-H>dcjSP19bAx^NjO@MFZq!&*x;0}PMA#! zpayyW&FK1iiakQ0*+x%LTevi6&=3D#ZLQy@ub_! zqtDCU9abJ1KlIvhnc;oWLxy{%#dANC#zvA@d zH@xIO>g7v=*Gv%mza1w3Z$q#zJOjSU;GKa{rdeG$5ONpUTAEda?eAN^<|_`#%x7-5 znr3Cc{yapG+FDJ)aBj4!)L6*vGhEyRTX_>Zot!D@@|hv6=4d4zJzkL5t|y-2&6Aip zkJ!!~-X5Ot7-1`_gYpw*^wP_n+h59S!~^T zTcvL`;+DLq|E5EFoz-~uo6?z(B@bXZ@J{7E8g0^-ov5Q zx`%HT4kRTc`I;{5EDeMXpRI~fOslD+seefSF~!uOjiOtVv*F`D-#|Mx(!zVtp@R|n zWp4i)rd-u6&z-8X_gX@kK0Dw_0s}5;%(bZPGnksKsqUWejmt9wWi&ai`fz^8wh&F; zNS_=o4_c1Qv6KS6ww^D0U03ixMl=RB%Nf*1Y?`B@i8#5LXHyJrJ~XBHXMpAUPMkWAy7&0Y%6|3Em@$Wb6f!fiY6#yRe4Xk-+m#I+ z3!Tmkt5KpPxy>>Kox>*lD*NB-@eA;wuR{X|TSiqa#TQ!6&^~+R_m^DS3%1@F>Crc@ z+Gm)9EbhF1+8X@*pOE1YnJ2_)X~=Xhpk<%IGvX@2e4im*Z^wC8NyE(90S8Z(DQ|;IT~NE13dR=qoyHNGf*JETQV__kzix%0YHYkNlCi}b41Btmp>w` zwYxhIQ}85c{;Qff)I2PcAlXOgB!r=~T1yZK@J@`F!G3zS2ltu4I&j z%Sz7tPdiLT_O(>;PQ9s%nH?R3U@k*#m=)vsZs20|xvp)En9p7P8Rh!EL#VyH0>3Ol z|JVc^T=XIQ)tPn>xeXV5Gr;^w;ab)$aqDSzja;xfnZjdh-j(TPckfuArbH;`CKFKqL}*Mk`Y!gW0NcgmWsTN2x4xZ zaZ{}aAoSE6Bp_sPu$yn&EuIko3{@v5=0YvYS6x?1Vv%bicZ`)ryp^x69@oq)p!w@m z1565fa}2>IJ;|;(xSUig#vWD-=3unZSSpvL2*u@0_fQE6BZC!n{r%{s*gL{!W}X8) z6vVRl#V>(@%Ev~nR8?$C$Hn8D&^Mm30XT5RxYi^d_hG!NSjdCskyp_^EdmTk z?qQ8@eE@-jA!ik56)RG&W%ZnywCDJU z=)fq4W=@K+X}`X1cFE?ro_bh&KsA;zaw=&iMItB3g-Y#vXyNRXOA_5 z?fA(}8L`>g?xQ^bPFReX;6q|9A!;3B+Sjkru4^`hR}`jNBeeCj>Pz5(UGMr8UY3|% zcFe{~ba&0G?0|1ZmYRM5@!ObeLhC|GK{wR%&oXCFHle5Po7<6IEaNX51?Th+-%F!N zPEwOBAS!Al3F$IDnG*eDEyT1^n4PEf2Wk!4diZsXKRdsbvUTDHUMj~PMt}^e=yLhT z_)%2Xtp&De5y`R@lCJEyGvUl;8;Y@P=j+qR=+_?;gKI2Y(s&;RrgPWW7pxi#bS70Q zDW*=BnW~`EVsD?qHSKB^bI;|po8Ch}O%VW_^R@m=K%ph^fI2O`7W*`GyS}qYzzc~^ zu9|EI1vX4qoyBJ=ZOsdsS=LWI_bJUq&o0%@#tzmXBE zEdQt~&8S5$Mp$?>g!TZ1K3Ca4GIrwa8gxLmbh%Bi&45m##L9OwJ+I2lvqzgd2VBJp zK`rB+ks1(Guy5WPH_ttrlx{l{+3TosZ7wi(3i3jom!Mw=nz_}7$-vE{jG*N@vlP1Q z6zlY`AW#F~64R#iqS!xVkVu_f6KhxP-oRc}yF)GGaG`qM@*!?q(8Kx)$@G`kfn2Qo z@ZBIw1#89gg0Vy60$I*A=S)ldOD&d=a+TaN!ORJ07))W)mURh>^2R>vargx9?gzdx z^LIR25N&*qP?&C(Az^W%P3NM5{LOpoxMVEbl2OK*eR8ylpI>0skuF5ESeP}&!y&Rj zUk4rG>gvfS0JSSr?o=sjOU5Tm1WFV&4%ZFkNc{M4!&qj5h>103I+r|JDtyiJBf?6N zbr`>K!>`um!y3dsZ6-O4J8qmJ=UxjeD6zEOF_H5iHMt~=O2JuYW5SU#hI&04hNE2=a8-k=(-u&2cj0zNiQ67leW_u!>)pqsDc!O)Uo|Ej z%3oWS{z>cprM)o|bRu`hZa#XeY>!t2L%8)Q*=V0ZdabL+X`f-6%}Yt}?uxYv!=dus z;E88Zn8j{6h}A{Jxm8{62hK}t=LVunwoEH6mv=r!*C^RlJ$bc_?4`9}Y~T0K&2gOG z)S}SRVmV7)a;uUjbiAvHxC!GsK9WtEW(xS@h0|90$-qAmw^cu;&RAyTph)e`UdMe%ZZsYiB45i zk+pP*`nX(Xu1Te;y#jTB$%J92t+s6rql@c`D_5im%-&vZvHl|TZNQYAVM=$If2O>; z*2Ixu4lnlZCW=H_9E?vP>`INfeH`r!RRJxs5>XE+%5mk`8vw)cEUlyhzdO!$LRrA$ z15&wkKOVYMVpcysq*WD0X8qQT&AGv7e4hcZAQQzgodff}WIO?;WRunOs4LnPmZBmK zB?W8BC4Ica0=z4UXjpmYuocB{+|D+Qn&F?)OYx1_a4G|V9D$=q{aVd&p2;%Jik#>A z_*nnY$u^}%CsrI^seMSdauaL`xCtDBj0_F6t3K862Qx|XD85?6$$`xCqWC5&@O56$ zhBkEGlGvV5!1!)`bA;PK-NHV@I1n(qLX@a-;--yxzS zQK^?K5%+XEZzP};*u^ijrhFHoLO0%hmKCU90sZ&{fV^LIpBTg%5yvwOZ|~{r>Cthf z(|xvibOkA^-PQr^IcJ%sW{7jFwTX2%tWUapRG!L`Zjkij36QLG?Q@+dPdz$o;KW%! z!F?Q4PRJ_j^lT8@@JJWL$H9E0s7LBcNM%q141Dtj#3Au^QL>}M1Yj9Yjrktg1px6V z%4es5K!;5>6qva(GUnJ8wvA?nYKI6hJWW%sgeg^UJJd{zQeo_a4RUph*VGv8fSXC5 z7AzYm*Hn1rIy}H7mEtdIRPY{g-=3uX<8ec)@H4&n{D2cwBs$Trn9O>b{piylt)ex( zT$bDll4S*_(AHpTq%-qC-}%BjD-H;H!23~Git$dus%-Li&6`5nCJ5lCRIT3GV?y>b zbI-Z0HCr3rS4Wx+Z`rnAF>thY?#?J3^usO}yjgw~&$YOPTS9^!%uryGvFo<BZ7ha-L^&c+KCdV23k~`>c*9&v|3!j7dfA;d(U>9A6e46d*aF z7{FK)YbjI=arQ4Yf+lD^@>kHO`NgIwB<+zvXe4`;W5&i+pN$`Sw~s{eC@bj1Got1d zyKZ#}q`qPS2Ogn3=aS&RQr$SBR9f=H&y~qg&6e$S-Yo3lBw_BMw8ZP!uc_AvA&gW2E=UZ9_TY5uP8}=D0TJ}iscfE$g zEx@~voG#b#rlBgP_8D}7Dw4u=P$F{Gf#zyM33AF?9oIc@w_nM&-Xp^w9UhrnupS+h z-X0%<(H-q1^wj_fg1iDV-+^R-@3l zg3ViSL+>Ozbd-h@hbJY=z3EZqun2D?Kr6U76Yt>DYKVi(r8+k=-MMOO-|!G)N#>Y}FMH86{ z47OeGiVnRo5p0@%SaOeaXTA;MQy*jZ#EN?-!n#05OG#Kb^T!G7bEq8kg^(@RC`@34 zHNk6b=K}CFUQ`fYpR(3^2TrtXG#N7OA3UnjZ`{~e{C&722S{EpP(Y+kbz2+deNlH( z0ZQW=Ei70LWRa<~t8+g1K=+G6+5`8yTPEX0MPQZDm#iC&<(69Yy3FNyQs>;?%dI6r0FEQS}k zF%B8UsRQ!@7X%R2kd6>HY5BEU58`Z$jU)RyM%=(e$G8F$+`G85Yebib_>qX+=h%9{1f(tWNRCrH*QL@*3fBtM5VlR@HS}Ne+Fj zj^KRxYBn`-X$}}`GF@KNO+$XS5MQEH=AF* z?FUlHg^t52F9z~68bzMV%HQG)lO!>>`hCjlr}|~bs8`)7Vo$chk=ZQLvXJk#Tj|kn z`e*5GSF8#3&gbXS=!Vy*NkW_=7ZQhZ|YH3y-;??Xtk=5WQxjFU}Vh&qZ zU(a>_<86-xeEt+k6n3Li;zn%MF%#~7GK9-K^>s~Tpp>qWo~3|deSYqV1njjLNUTP#jng6^)b}XE+mNQ`2?d7UobO!Ey6ee`Wv*1Sx08#OpTKZ3-a*j=*3%O*Lz(**;h;gZW5V*n?;3#zDrFpsP`zh-K* zod-@z#dW>Su*@yKF2Pv!d8qzGar1PUCeJJKsCh+lK^aSX4-CSi`>Gese6^8F`IO+J zAZvDRXU}ukmGD#IW~@gP#yGW?*Mf&=n5%HOx-^&~Woytf6WfOOyr5Z8f#tBDo57-! z#(&;*59DoA8w)p<;Uo^vhoIrt@EMGbjxmdg#VKuInyqcpWk$Vz&LGe8J@C76ooDLw z4`b`O13$Xg<;+aM**X!D1ea#vG=L`zW)->G&EDgV>u`zL<sU>Ttn zdg4+03`woRQ%>5?(`5;Sw8r;23o4is8vxp?BzITinRy@i+8R>oB(C|UtgJ#ijGyHj z1<6QA2?2^nupY%B9g`hgCV1AzIK44?XY`nP9@J8!jMcp6$gWdaS-Cxm@tUdF+|&Sa z+r|tMJ2&j@8{ZV*v6^v*2&!zU^tprR=zOK|GW~8!IY8~%C+EIQ=bOQ*XpSQ)F4h?X zS6XCObWUnFb=y%og@Q(1Yo{dXr$l8%y-yvGD#L&d?EN-_ZS9wH)UuQjg`}cLxzl=S zAd9Oeq7rHs6OMsPSNN_!7aPa8xaG63QVA;CRHzYw)}-M`{kUGJhtxLS&5ViNsBP7w z1&zsQ1uTS{nRG%y)Y724(EHFaF0`2G(#~Hbak1IxbEpx1q_M8u z%rBsLl8i4F#sjF0<-$GRm3VL?D4DT%mpKZb#L$XzrDEoY^B$!5%hw&cihBoY-;DqSr93q;Q6VIh=*2nWo2N|t7E>wG z!r%)js!y5DfPi$z^NltwDZT~HZE8crxz}+bP7(TWIBUXEGdMP(wDh%0plMF0Z1xlw zX**5cSM(uZBl~QdduZW#;MovxF?@8;x!PpV`G#ZF-EL^P^vn!g z(9h3*4ee32y>y*e4uLyN!?#=W>GS&~_H)1butIWC_mehAnw4_k?Qq|tBBGLU-DJu3 zyQ=n8QW@0uRTiAH^iv?Q2QJaa^4mpFnS_`&Au8CjvdeycTdx|_;pi4naS5WOyD_&D zA)iAG$cqkx&#h613{q@KAV)faOnl(+#SXrjCwV^+SuIDBxf;N17Jhhq92qv31h@}u zecRo!?VYp)oSU6Zn1qWOw`^7IkiE*=ky^%mYd6@(ryk}ly!wpV&Bw-g30w1Lr)ryo z81#neOYleLs6y>vVayo;_`J@;QXwB#{0BwFRvY@8WxbaOir$uIkmTIg&IYLbr$!2e`B|JP3XSCBv$zV5K$=Z>>CXA~`o6)t{?+G7+YbLb^!+eAKa zU{S3Lu}||k8J{^&EOd;eM2-IeVOGkP5=5G9bgA!bhq6s{g;jBP2YO2fD!4P>b4U@H zFt=Bub&%i^i`jp+aQF23LVr0vew}H1)pZ~=ptRk(OhZ8I(^H2W!dwlu=Q~K*H zZd|HFAB{<)s;DE2o7Q9(&0VgB%_>4#*`s}i-mH(CH;%<~{qQH<|9`bUzAoJRF+&ViS7h)#~vM3IaCd+e0xQ{)GHTm)7OQ;@Uf=fYK#KVSxg=NiX~Hc9D2$ z91=&oXq2P6^UO%gUd|a}{$ey?ELsK|nR?X>H6@FCNcEJ`Qt@1CzWkz>$_gS;yM{Uw zh;RO+s=s#9m$#E&XPf$&a;wSU67JS7(S3ucHaCBX?pt}ue#T%|0FT7`)9*Rb&cygr zh0z4i#Q6!&1&>-V#f%=vvq>1gV)MCPoO7le`gvxQoZN?MXyR>q19`Ux7g1VRO>YLS zQTR6k=-kFk`+NgT@wU%51oaiL)joq;s-n@>6!Ui7qYwfh)Hw(Z0A*Fr?+uOMeVi4xLos?ZG_4z@SW@e=WI2}QpWHjA-gNp`b@7^wD!Jz_ zQES`hF#Hq8J_wILl8oZr@X((KB{=i`(!zV|a?Hz57uj5tVw+EIR|qXP^vHw`19x1V zuX^(MZC?L;qJ+0)!gqH#V$*syL7m37CtDn+5K`;))6NmI&`Fn?J(`AB{z0D(#_8Wa z_in64m&nCN1U1H*Cgz`h(W}OExQiP}bl7^Ff5DUyb1x zYWg3mA$}xruErzj759UMnCCp9Zu7#pz2Li=kAGFxf7avS&v?HW{0F~pwa-pV-p*{R z={X^ycEh?{r^WF^#?Khk2BUwBXn*)4Hu+YY>MdW9o8nIvgy61MYCZ6%VAr|Amm`ts~{}xslko zo?+bkSQJGqZ?oLL`!@Pn?-i?L&f4l6m9EF03=Ag{bPN9=Y{q|<#`F8$9Mbpy!vLzk N&;6hEMQOk9zW`D*KhFRF literal 0 HcmV?d00001 diff --git a/env.example b/env.example new file mode 100644 index 0000000..2a2d814 --- /dev/null +++ b/env.example @@ -0,0 +1,25 @@ +ADMIN_GROUPS=modron-admins@example.com +COLLECTORS=gcp +LABEL_TO_EMAIL_REGEXP=(.*)_(.*?)_(.*?) +LABEL_TO_EMAIL_SUBSTITUTION=$1@$2.$3 +LOG_FORMAT=text +LOG_LEVEL=info +LOG_ALL_SQL_QUERIES=false +ORG_ID=111111111111 +ORG_SUFFIX=example.com +PERSISTENT_CACHE=true +PERSISTENT_CACHE_TIMEOUT=48h +PORT=4201 +PROJECT_ID=modron-project-id +RUN_AUTOMATED_SCANS=false +SKIP_IAP=true +SQL_CONNECT_STRING=postgres://modron:modron@127.0.0.1:5432/modron?sslmode=disable + +OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317 +OTEL_EXPORTER_OTLP_INSECURE=true +OTEL_LOG_LEVEL=debug +OTEL_SERVICE_NAME=modron + +TAG_CUSTOMER_DATA=111111111111/customer_data +TAG_EMPLOYEE_DATA=111111111111/employee_data +TAG_ENVIRONMENT=111111111111/environment diff --git a/go.mod b/go.mod index be939b4..77df434 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ // This file is required for gosec to work. module github.com/nianticlabs/modron -go 1.21 +go 1.23.2 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/otel/config/config.yaml b/otel/config/config.yaml new file mode 100644 index 0000000..ca414f1 --- /dev/null +++ b/otel/config/config.yaml @@ -0,0 +1,34 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 +processors: + batch: + +exporters: + otlp: + endpoint: "jaeger:4317" + tls: + insecure: true + prometheusremotewrite: + endpoint: "http://prometheus:9090/api/v1/write" + +extensions: + health_check: + pprof: + zpages: + +service: + extensions: [health_check, pprof, zpages] + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [otlp] + metrics: + receivers: [otlp] + processors: [] + exporters: [prometheusremotewrite] \ No newline at end of file diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..55f2d3a --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +/modron-lint.xml diff --git a/src/Dockerfile b/src/Dockerfile index 3b16f40..649d96a 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -1,20 +1,20 @@ -ARG GOVERSION=1.21 +ARG GOVERSION=1.23 -FROM alpine:latest as ca-certificates_builder +FROM alpine:latest AS ca-certificates_builder RUN apk add --no-cache ca-certificates -FROM golang:${GOVERSION} as modron_builder -ENV GOPATH /go -WORKDIR /app -COPY go.* ./ -COPY proto/go.* ./proto/ +FROM golang:${GOVERSION} AS modron_builder +ENV GOPATH=/go +WORKDIR /app/src +COPY src/go.* /app/src/ +COPY src/proto/generated /app/src/proto/generated/ RUN go mod download -COPY . ./ +COPY ./src/ /app/src/ RUN CGO_ENABLED=0 go build -v -o modron FROM scratch WORKDIR /app COPY --from=ca-certificates_builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -COPY --from=modron_builder /app/modron /app/modron +COPY --from=modron_builder /app/src/modron /app/modron USER 101:101 -ENTRYPOINT ["/app/modron", "--logtostderr"] +ENTRYPOINT ["/app/modron"] diff --git a/src/Dockerfile.e2e b/src/Dockerfile.e2e index ca3556b..8373822 100644 --- a/src/Dockerfile.e2e +++ b/src/Dockerfile.e2e @@ -1,24 +1,19 @@ # We have to keep this file here otherwise we can't depend on the shared proto. # Docker prevents including files above the Dockerfile directory (.. forbidden). -ARG GOVERSION=1.21 +ARG GOVERSION=1.23 -FROM golang:${GOVERSION} as builder -ENV GOPATH /go -WORKDIR /app -COPY test/go.* e2e_test_dir/ -COPY proto/ ./proto -WORKDIR /app/e2e_test_dir +FROM golang:${GOVERSION} AS builder +ENV GOPATH=/go +WORKDIR /app/src/test/ +COPY ./src/test/go.* /app/src/test/ +COPY ./src/proto/ /app/src/proto RUN go mod download -COPY test/* ./ -RUN mkdir certs -RUN openssl req -x509 -newkey rsa:4096 -keyout certs/key.pem -nodes -out certs/cert.pem -sha256 -days 1 -subj '/CN=modron_test' -addext "subjectAltName = DNS:modron_test" +COPY ./src/test/* /app/src/test/ RUN CGO_ENABLED=0 go test -c -v -o test FROM scratch WORKDIR /app/stats WORKDIR /app/secrets WORKDIR /app -COPY --from=builder /app/e2e_test_dir/test /app/test -COPY --from=builder /app/e2e_test_dir/certs/cert.pem /app/cert.pem -COPY --from=builder /app/e2e_test_dir/certs/key.pem /app/key.pem +COPY --from=builder /app/src/test/test /app/test ENTRYPOINT ["/app/test", "--test.short"] diff --git a/src/README.md b/src/README.md index f7d673d..4e64e95 100644 --- a/src/README.md +++ b/src/README.md @@ -2,8 +2,8 @@ ## Build modron and push to Google Cloud Registry -``` -gcloud builds submit . --tag gcr.io/modron-dev/modron:dev --timeout=900 +```bash +gcloud builds submit . --tag us-central1.docker.pkg.dev/$PROJECT_ID/modron/modron:dev --timeout=900 ``` This applies the label `dev` on the image you're pushing. @@ -11,26 +11,33 @@ This image is expected to run on modron-dev environment. Deploy to cloud run dev: -``` -DEV_RUNNER_SA_NAME=$PROJECT-runner@$PROJECT.iam.gserviceaccount.com -gcloud run deploy modron-grpc-web-dev --platform=managed --image=gcr.io/modron-dev/modron:dev --region=us-central1 --service-account=$DEV_RUNNER_SA_NAME -gcloud run services update-traffic modron-ui --to-revisions LATEST=100 --region=us-central1 +```bash +DEV_RUNNER_SA_NAME=$PROJECT_ID-runner@$PROJECT_ID.iam.gserviceaccount.com +REGION=us-central1 +gcloud run deploy \ + modron-grpc-web-dev \ + --platform=managed \ + --image="$REGION.docker.pkg.dev/$PROJECT_ID/modron/modron:dev" \ + --region="$REGION" \ + --service-account="$DEV_RUNNER_SA_NAME" +gcloud run services update-traffic modron-ui --to-revisions LATEST=100 --region="$REGION" ``` ## Debug To debug RPC issues, set the two following environment variables: -``` +```bash export GRPC_GO_LOG_VERBOSITY_LEVEL=99 export GRPC_GO_LOG_SEVERITY_LEVEL=info ``` ## Update libraries -``` -CYPRESS_CACHE_FOLDER=/tmp npm upgrade -CYPRESS_CACHE_FOLDER=/tmp npm install +```bash +export CYPRESS_CACHE_FOLDER=/tmp +npm upgrade +npm install ``` -Note: Cypress tries to write to /root/.cache which doesn't work. This is why we need to set the environment variable. +Note: Cypress tries to write to `/root/.cache` which doesn't work. This is why we need to set the environment variable. diff --git a/src/acl/fakeacl/checkerFake.go b/src/acl/fakeacl/checkerFake.go index 9853052..fc57889 100644 --- a/src/acl/fakeacl/checkerFake.go +++ b/src/acl/fakeacl/checkerFake.go @@ -1,29 +1,31 @@ package fakeacl import ( - "os" - - "github.com/golang/glog" + "github.com/sirupsen/logrus" "golang.org/x/net/context" + "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" ) type GcpCheckerFake struct{} +var log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "fakeacl") +var _ model.Checker = (*GcpCheckerFake)(nil) + func New() model.Checker { - glog.Warningf("If you see this on production, contact security%s", os.Getenv(constants.OrgSuffixEnvVar)) + log.Warnf("If you see this on production, contact security") return &GcpCheckerFake{} } -func (checker *GcpCheckerFake) GetAcl() map[string]map[string]struct{} { +func (checker *GcpCheckerFake) GetACL() model.ACLCache { return nil } -func (checker *GcpCheckerFake) GetValidatedUser(ctx context.Context) (string, error) { +func (checker *GcpCheckerFake) GetValidatedUser(_ context.Context) (string, error) { return "", nil } -func (checker *GcpCheckerFake) ListResourceGroupNamesOwned(ctx context.Context) (map[string]struct{}, error) { +func (checker *GcpCheckerFake) ListResourceGroupNamesOwned(_ context.Context) (map[string]struct{}, error) { return map[string]struct{}{"projects/modron-test": {}}, nil } diff --git a/src/acl/gcpacl/cache.go b/src/acl/gcpacl/cache.go new file mode 100644 index 0000000..fe0a42d --- /dev/null +++ b/src/acl/gcpacl/cache.go @@ -0,0 +1,55 @@ +package gcpacl + +import ( + "encoding/json" + "errors" + "os" + "time" + + "github.com/nianticlabs/modron/src/model" +) + +type FSACLCache struct { + LastUpdate time.Time `json:"last_update"` + Content model.ACLCache `json:"content"` +} + +var localACLCacheFile = os.TempDir() + "/modron-acl-cache.json" + +const ownerRWPermissions = 0600 + +func (checker *GcpChecker) getLocalACLCache() (*FSACLCache, error) { + log.Tracef("getting ACL cache from %s", localACLCacheFile) + f, err := os.Open(localACLCacheFile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, nil //nolint:nilnil + } + return nil, err + } + defer f.Close() + + var fsACLCache FSACLCache + if err := json.NewDecoder(f).Decode(&fsACLCache); err != nil { + return nil, err + } + return &fsACLCache, nil +} + +func (checker *GcpChecker) saveLocalACLCache(res model.ACLCache) error { + log.Tracef("saving ACL cache to %s", localACLCacheFile) + fsACLCache := FSACLCache{ + LastUpdate: time.Now(), + Content: res, + } + f, err := os.OpenFile(localACLCacheFile, os.O_CREATE|os.O_WRONLY, ownerRWPermissions) + if err != nil { + return err + } + defer f.Close() + return json.NewEncoder(f).Encode(fsACLCache) +} + +func (checker *GcpChecker) deleteLocalACLCache() error { + return os.Remove(localACLCacheFile) +} diff --git a/src/acl/gcpacl/cache_test.go b/src/acl/gcpacl/cache_test.go new file mode 100644 index 0000000..9c49363 --- /dev/null +++ b/src/acl/gcpacl/cache_test.go @@ -0,0 +1,80 @@ +package gcpacl + +import ( + "context" + "errors" + "os" + "testing" + "time" + + "github.com/nianticlabs/modron/src/collector/testcollector" + "github.com/nianticlabs/modron/src/model" + + "github.com/google/go-cmp/cmp" +) + +func clearCache(t *testing.T) { + t.Helper() + if err := os.Remove(localACLCacheFile); err != nil { + if !errors.Is(err, os.ErrNotExist) { + t.Fatalf("cannot delete cache: %v", err) + } + } +} + +func TestCache(t *testing.T) { + clearCache(t) + defer clearCache(t) + + checker := GcpChecker{ + cfg: Config{PersistentCache: true, PersistentCacheTimeout: time.Second * 10}, + } + collector := testcollector.TestCollector{} + checker.collector = &collector + fsACLCache, err := checker.getLocalACLCache() + if err != nil { + t.Fatalf("getLocalACLCache: %v", err) + } + if fsACLCache != nil { + t.Fatalf("getLocalACLCache should be empty") + } + ctx := context.Background() + aclStoreTime := time.Now() + if err := checker.loadACLCache(ctx); err != nil { + t.Fatalf("loadACLCache: %v", err) + } + if checker.aclCache == nil { + t.Fatalf("checker.aclCache should be initialized") + } + + expectedCache := model.ACLCache{ + "*": { + "projects/modron-test": {}, + "projects/super-secret": {}, + }, + "user@example.com": { + "projects/modron-test": {}, + }, + } + if !cmp.Equal(expectedCache, checker.aclCache) { + t.Errorf("aclCache mismatch: %v", cmp.Diff(expectedCache, checker.aclCache)) + } + + fsACLCache, err = checker.getLocalACLCache() + if err != nil { + t.Fatalf("getLocalACLCache: %v", err) + } + if fsACLCache == nil { + t.Fatalf("getLocalACLCache should be initialized, but it's nil") + } + + if diff := cmp.Diff(checker.aclCache, fsACLCache.Content); diff != "" { + t.Errorf("aclCache mismatch (-want +got):\n%s", diff) + } + if !aclStoreTime.Before(fsACLCache.LastUpdate) { + t.Errorf("the filesystem cache should have been updated, its last update was %v", fsACLCache.LastUpdate) + } + if aclStoreTime.Add(time.Second * 10).Before(fsACLCache.LastUpdate) { + t.Fatalf("the filesystem cache should not be older than 10 seconds from the moment we started (lastUpdate: %v)", fsACLCache.LastUpdate) + } +} diff --git a/src/acl/gcpacl/checker.go b/src/acl/gcpacl/checker.go index 92206f9..8a3ad51 100644 --- a/src/acl/gcpacl/checker.go +++ b/src/acl/gcpacl/checker.go @@ -2,13 +2,13 @@ package gcpacl import ( "fmt" - "os" - "strconv" + "strings" "time" + "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/golang/glog" + "github.com/sirupsen/logrus" "golang.org/x/net/context" "google.golang.org/api/cloudidentity/v1" "google.golang.org/api/idtoken" @@ -16,20 +16,22 @@ import ( ) const ( - AclUpdateIntervalSecEnvVar = "ACL_UPDATE_INTERVAL_SEC" + defaultACLUpdateInterval = 5 * time.Minute ) -var aclUpdateIntervalSec = func() int { - intVar, err := strconv.Atoi(os.Getenv(AclUpdateIntervalSecEnvVar)) - if err != nil { - return 5 * 60 - } - return intVar -}() +var ( + log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "gcpacl") +) type Config struct { - CacheTimeout time.Duration AdminGroups []string + CacheTimeout time.Duration + // PersistentCache is a flag to enable/disable the local ACL cache. + PersistentCache bool + // PersistentCacheTimeout is the amount of time we keep the ACLs on the filesystem before we fetch them again. + PersistentCacheTimeout time.Duration + // SkipIap enables or disables the IAP check - when this is enabled, all users are considered admins. Of course this should be always disabled in prod. + SkipIap bool } type GcpChecker struct { @@ -37,45 +39,57 @@ type GcpChecker struct { collector model.Collector cloudIdentitySvc *cloudidentity.Service - aclCache map[string]map[string]struct{} + aclCache model.ACLCache adminsCache map[string]ACLCacheEntry - adminGroupsIds []string + adminGroupsIDs []string } -func (checker *GcpChecker) GetAcl() map[string]map[string]struct{} { +func (checker *GcpChecker) GetACL() model.ACLCache { return checker.aclCache } func New(ctx context.Context, collector model.Collector, cfg Config) (model.Checker, error) { - cisvc, err := cloudidentity.NewService(ctx) + cloudIdentitySvc, err := cloudidentity.NewService(ctx) if err != nil { return nil, err } gcpChecker := GcpChecker{ collector: collector, cfg: cfg, - cloudIdentitySvc: cisvc, - aclCache: make(map[string]map[string]struct{}), + cloudIdentitySvc: cloudIdentitySvc, + aclCache: make(model.ACLCache), adminsCache: make(map[string]ACLCacheEntry), - adminGroupsIds: []string{}, + adminGroupsIDs: []string{}, } for _, ag := range cfg.AdminGroups { - group, err := cisvc.Groups.Lookup().GroupKeyId(ag).Do() + groupEmail := strings.TrimPrefix(ag, "group:") + group, err := cloudIdentitySvc.Groups.Lookup().GroupKeyId(groupEmail).Do() if err != nil { - glog.Errorf("cannot fetch group %q: %v", ag, err) + log.Errorf("cannot fetch group %q: %v", ag, err) continue } - gcpChecker.adminGroupsIds = append(gcpChecker.adminGroupsIds, group.Name) + gcpChecker.adminGroupsIDs = append(gcpChecker.adminGroupsIDs, group.Name) } - if err := gcpChecker.loadAclCache(ctx); err != nil { - return nil, err + if cfg.PersistentCache { + log.Debugf("using on-disk ACL cache") + if err := gcpChecker.loadACLCache(ctx); err != nil { + return nil, err + } + } else { + log.Debugf("using in-memory ACL cache") + var res model.ACLCache + res, err = gcpChecker.getACLAndStore(ctx) + if err != nil { + return nil, err + } + gcpChecker.aclCache = res } gcpChecker.updateACLs(ctx) return &gcpChecker, nil } func (checker *GcpChecker) updateACLs(ctx context.Context) { - ticker := time.NewTicker(time.Duration(aclUpdateIntervalSec) * time.Second) + ticker := time.NewTicker(defaultACLUpdateInterval) go func() { for { select { @@ -83,8 +97,8 @@ func (checker *GcpChecker) updateACLs(ctx context.Context) { ticker.Stop() return case <-ticker.C: - if err := checker.loadAclCache(ctx); err != nil { - glog.Warningf("cannot update ACLs: %v", err) + if err := checker.loadACLCache(ctx); err != nil { + log.Warnf("cannot update ACLs: %v", err) } } } @@ -92,12 +106,18 @@ func (checker *GcpChecker) updateACLs(ctx context.Context) { } func (checker *GcpChecker) ListResourceGroupNamesOwned(ctx context.Context) (map[string]struct{}, error) { + adminResources := checker.aclCache["*"] + if checker.cfg.SkipIap { + // When we decide to skip the IAP check (insecure, only for local development), we return all resources as admin. + log.Warnf("IAP check is disabled, users are all admins. If you see this in production, reach out to the security team.") + return adminResources, nil + } user, err := checker.GetValidatedUser(ctx) if err != nil { return nil, err } if checker.isAdmin(user) { - return checker.aclCache["*"], nil + return adminResources, nil } if _, ok := checker.aclCache[user]; !ok { return map[string]struct{}{}, nil @@ -105,15 +125,42 @@ func (checker *GcpChecker) ListResourceGroupNamesOwned(ctx context.Context) (map return checker.aclCache[user], nil } -func (checker *GcpChecker) loadAclCache(ctx context.Context) error { +func (checker *GcpChecker) loadACLCache(ctx context.Context) error { + aclFsCache, err := checker.getLocalACLCache() + if err != nil { + return err + } + if aclFsCache != nil { + if time.Since(aclFsCache.LastUpdate) < checker.cfg.PersistentCacheTimeout { + log.Tracef("ACL cache hit") + checker.aclCache = aclFsCache.Content + return nil + } + if err := checker.deleteLocalACLCache(); err != nil { + return fmt.Errorf("ACL cache: %w", err) + } + } + log.Tracef("ACL cache miss") res, err := checker.collector.ListResourceGroupAdmins(ctx) if err != nil { return err } checker.aclCache = res + if err := checker.saveLocalACLCache(res); err != nil { + return fmt.Errorf("save ACL cache: %w", err) + } return nil } +func (checker *GcpChecker) getACLAndStore(ctx context.Context) (model.ACLCache, error) { + res, err := checker.collector.ListResourceGroupAdmins(ctx) + if err != nil { + return nil, err + } + checker.aclCache = res + return res, nil +} + func (checker *GcpChecker) GetValidatedUser(ctx context.Context) (string, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { @@ -135,25 +182,25 @@ func (checker *GcpChecker) GetValidatedUser(ctx context.Context) (string, error) return user, nil } -func (c *GcpChecker) isAdmin(user string) bool { +func (checker *GcpChecker) isAdmin(user string) bool { // We do some memoizing here as this might be called a lot over a short period of time. - if v, ok := c.adminsCache[user]; ok { - if v.time.Add(c.cfg.CacheTimeout).After(time.Now()) { + if v, ok := checker.adminsCache[user]; ok { + if v.time.Add(checker.cfg.CacheTimeout).After(time.Now()) { return v.access } } - for _, g := range c.adminGroupsIds { - resp, err := c.cloudIdentitySvc.Groups.Memberships.CheckTransitiveMembership(g).Query(fmt.Sprintf("member_key_id == '%s'", user)).Do() + for _, g := range checker.adminGroupsIDs { + resp, err := checker.cloudIdentitySvc.Groups.Memberships.CheckTransitiveMembership(g).Query(fmt.Sprintf("member_key_id == '%s'", user)).Do() if err != nil { - glog.Warningf("cannot check membership: %v", err) - } else { - if resp.HasMembership { - c.adminsCache[user] = ACLCacheEntry{time: time.Now(), access: true} - return true - } + log.Warnf("cannot check membership: %v", err) + continue + } + if resp.HasMembership { + checker.adminsCache[user] = ACLCacheEntry{time: time.Now(), access: true} + return true } } - c.adminsCache[user] = ACLCacheEntry{time: time.Now(), access: false} + checker.adminsCache[user] = ACLCacheEntry{time: time.Now(), access: false} return false } diff --git a/src/acl/gcpacl/checker_real_test.go b/src/acl/gcpacl/checker_real_test.go new file mode 100644 index 0000000..5f7f6a2 --- /dev/null +++ b/src/acl/gcpacl/checker_real_test.go @@ -0,0 +1,37 @@ +//go:build integration + +package gcpacl + +import ( + "context" + "os" + "testing" + + "github.com/nianticlabs/modron/src/collector/gcpcollector" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/storage/memstorage" +) + +func TestCheckerReal(t *testing.T) { + orgID := os.Getenv("ORG_ID") + orgSuffix := os.Getenv("ORG_SUFFIX") + if orgID == "" || orgSuffix == "" { + t.Fatalf("ORG_ID and ORG_SUFFIX are required, orgID=%q, orgSuffix=%q", orgID, orgSuffix) + } + + ctx := context.Background() + storage := memstorage.New() + gcpCollector, err := gcpcollector.New(ctx, storage, orgID, orgSuffix, []string{}, risk.TagConfig{}, []string{}) + if err != nil { + t.Error(err) + } + + checker, err := New(ctx, gcpCollector, Config{}) + if err != nil { + t.Fatal(err) + } + + if _, err := checker.ListResourceGroupNamesOwned(ctx); err != nil { + t.Error(err) + } +} diff --git a/src/acl/gcpacl/checker_test.go b/src/acl/gcpacl/checker_test.go index 8e6ad0b..651fa20 100644 --- a/src/acl/gcpacl/checker_test.go +++ b/src/acl/gcpacl/checker_test.go @@ -2,26 +2,17 @@ package gcpacl import ( "context" - "fmt" "os" "testing" "github.com/nianticlabs/modron/src/collector/gcpcollector" - "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/risk" "github.com/nianticlabs/modron/src/storage/memstorage" "google.golang.org/grpc/metadata" ) func TestMain(m *testing.M) { - if orgIdEnv := os.Getenv(constants.OrgIdEnvVar); orgIdEnv == "" { - os.Setenv(constants.OrgIdEnvVar, fmt.Sprintf("%s%s", constants.GCPOrgIdPrefix, "012345678912")) - defer os.Unsetenv(constants.OrgIdEnvVar) - } - if orgSuffixEnv := os.Getenv(constants.OrgSuffixEnvVar); orgSuffixEnv == "" { - os.Setenv(constants.OrgSuffixEnvVar, "example.com") - defer os.Unsetenv(constants.OrgSuffixEnvVar) - } os.Exit(m.Run()) } @@ -32,7 +23,7 @@ func TestInvalidNoToken(t *testing.T) { ctx := context.Background() storage := memstorage.New() - gcpCollector := gcpcollector.NewFake(ctx, storage) + gcpCollector := gcpcollector.NewFake(ctx, storage, risk.TagConfig{}) checker, err := New(ctx, gcpCollector, Config{}) if err != nil { @@ -45,14 +36,10 @@ func TestInvalidNoToken(t *testing.T) { } func TestInvalidParseToken(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode: need GCP credentials") - } - ctx := context.Background() - ctx = metadata.NewIncomingContext(ctx, metadata.New( - map[string]string{"Authorization": "QxMzZjMjAyYjhkMjkwNTgwYjE2NWMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiIzMjU1NTk0MDU1OS5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImF6cCI6InNlcnZpY2VhY2NvdW50bmFtZUBzZWMtZXNhbGltYmVuaS1hcGkta2V5LXRlc3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbCI6InNlcnZpY2VhY2NvdW50bmFtZUBzZWMtZXNhbGltYmVuaS1hcGkta2V5LXRlc3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZXhwIjoxNjU5NTIxODcxLCJpYXQiOjE2NTk1MTgyNzEsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbSIsInN1YiI6IjEwNTM5OTA4NzA5OTk1NDg5MDA0MyJ9.LHwLeuAa6jZc7pFPhtFLvsKMg56vPHrm83dfVukLxycopz6CzkpFZoF_DXFRKQ-myQs4KMMd44loi29te5vfnAL9aMXwvySFEcjESOIE_SXPND3Q5FBlfRfWoSLFjGsGhqLhNwKQn-tvjynkdxmtopL4qVmhAFpgTqNA4u8b7l7cWsl3zoudPZMy8mi5pIUetWH5jpj7OaPyv9pVaQ-LaXaLUQkD8bx0bL3Tjhu9yu2IP2Z06jFR9mN-fJ60F05kMJ6Y4HquDXNjm8HCNrXfMGHBcKMUzE3wAaOIG4PoGI81t43dPWpUIUg07RS5tG5uxuWIrgJddxJYpYCWOhGjog"})) + ctx := metadata.NewIncomingContext(context.Background(), metadata.New( + map[string]string{"Authorization": "Bearer xyz.abc.123"})) storage := memstorage.New() - gcpCollector := gcpcollector.NewFake(ctx, storage) + gcpCollector := gcpcollector.NewFake(ctx, storage, risk.TagConfig{}) checker, err := New(ctx, gcpCollector, Config{}) if err != nil { @@ -63,25 +50,3 @@ func TestInvalidParseToken(t *testing.T) { t.Error("expected error: checker parsed a jwt tokenId that is invalid") } } - -func TestCheckerReal(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode: need GCP credentials") - } - - ctx := context.Background() - storage := memstorage.New() - gcpCollector, err := gcpcollector.New(ctx, storage) - if err != nil { - t.Error(err) - } - - checker, err := New(ctx, gcpCollector, Config{}) - if err != nil { - t.Fatal(err) - } - - if _, err := checker.ListResourceGroupNamesOwned(ctx); err != nil { - t.Error(err) - } -} diff --git a/src/collector/collector.go b/src/collector/collector.go new file mode 100644 index 0000000..ac0e2f5 --- /dev/null +++ b/src/collector/collector.go @@ -0,0 +1,18 @@ +package collector + +type Type string + +const ( + Gcp Type = "gcp" + Fake Type = "fake" +) + +var validCollectors = []Type{Gcp, Fake} + +func ValidCollectors() []string { + var collectors []string + for _, c := range validCollectors { + collectors = append(collectors, string(c)) + } + return collectors +} diff --git a/src/collector/gcpcollector/api_fake.go b/src/collector/gcpcollector/api_fake.go new file mode 100644 index 0000000..34a660a --- /dev/null +++ b/src/collector/gcpcollector/api_fake.go @@ -0,0 +1,1295 @@ +package gcpcollector + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "golang.org/x/net/context" + "google.golang.org/api/apikeys/v2" + "google.golang.org/api/cloudasset/v1" + "google.golang.org/api/cloudresourcemanager/v3" + "google.golang.org/api/compute/v1" + "google.golang.org/api/container/v1" + "google.golang.org/api/googleapi" + "google.golang.org/api/iam/v1" + "google.golang.org/api/monitoring/v3" + "google.golang.org/api/securitycenter/v1" + "google.golang.org/api/spanner/v1" + sqladmin "google.golang.org/api/sqladmin/v1beta4" + "google.golang.org/api/storage/v1" + k8s_io_api_core_v1 "k8s.io/api/core/v1" + + "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/risk" +) + +const oneYear = time.Hour * 24 * 365 + +func NewFake(_ context.Context, storage model.Storage, tagConfig risk.TagConfig) *GCPCollector { + fakeAPI := GCPAPIFake{} + m := initMetrics() + return &GCPCollector{ + allowedSccCategories: map[string]struct{}{ + "SQL_PUBLIC_IP": {}, + // We intentionally exclude GKE_RUN_AS_NONROOT to test that we don't include it in the ListSccFindings + // "GKE_RUN_AS_NONROOT": {}, + "GKE_RUNTIME_OS_VULNERABILITY": {}, + }, + api: &fakeAPI, + storage: storage, + metrics: m, + tagConfig: tagConfig, + } +} + +type GCPAPIFake struct{} + +func (api *GCPAPIFake) GetServiceAccountIAMPolicy(_ context.Context, _ string) (*iam.Policy, error) { + return &iam.Policy{ + Bindings: []*iam.Binding{ + { + Members: []string{"user:user-1@example.com"}, + Role: "roles/iam.serviceAccountUser", + }, + }, + }, nil +} +func (api *GCPAPIFake) ListAPIKeys(_ context.Context, _ string) ([]*apikeys.V2Key, error) { + return []*apikeys.V2Key{ + { + Name: "api-key-unrestricted-0", + Restrictions: nil, + }, + { + Name: "api-key-unrestricted-1", + Restrictions: &apikeys.V2Restrictions{ + ApiTargets: nil, + }, + }, + { + Name: "api-key-with-overbroad-scope-1", + Restrictions: &apikeys.V2Restrictions{ + ApiTargets: []*apikeys.V2ApiTarget{ + { + Service: "iamcredentials.googleapis.com", + }, + { + Service: "storage_api", + }, + { + Service: "apikeys", + }, + }, + }, + }, + { + Name: "api-key-without-overbroad-scope", + Restrictions: &apikeys.V2Restrictions{ + ApiTargets: []*apikeys.V2ApiTarget{ + { + Service: "bigquerystorage.googleapis.com", + }, + }, + }, + }, + }, nil +} + +func (api *GCPAPIFake) ListBackendServices(_ context.Context, _ string) ([]*compute.BackendService, error) { + return []*compute.BackendService{ + { + Name: "backend-svc-internal", + LoadBalancingScheme: "INTERNAL", + SelfLink: "/links/backend-svc-internal", + Iap: &compute.BackendServiceIAP{ + Enabled: true, + Oauth2ClientId: "client-id@client.example.com", + }, + }, + { + Name: "backend-svc-external-no-modern", + LoadBalancingScheme: "EXTERNAL", + SelfLink: "/links/backend-svc-external-no-modern", + Iap: &compute.BackendServiceIAP{ + Enabled: true, + Oauth2ClientId: "client-id@client.example.com", + }, + }, + { + Name: "backend-svc-external-modern", + LoadBalancingScheme: "EXTERNAL", + SelfLink: "/links/backend-svc-external-modern", + Iap: &compute.BackendServiceIAP{ + Enabled: true, + Oauth2ClientId: "client-id@client.example.com", + }, + }, + { + Name: "backend-svc-no-iap", + LoadBalancingScheme: "EXTERNAL", + SelfLink: "/links/backend-svc-no-iap", + Iap: &compute.BackendServiceIAP{ + Enabled: false, + }, + }, + { + Name: "backend-svc-iap", + LoadBalancingScheme: "EXTERNAL", + SelfLink: "/links/backend-svc-iap", + Iap: &compute.BackendServiceIAP{ + Enabled: true, + Oauth2ClientId: "client-id@client.example.com", + }, + }, + { + Name: "backend-svc-no-fe", + LoadBalancingScheme: "EXTERNAL", + SelfLink: "/links/backend-svc-no-fe", + Iap: &compute.BackendServiceIAP{ + Enabled: true, + Oauth2ClientId: "client-id@client.example.com", + }, + }, + }, nil +} + +func (api *GCPAPIFake) ListBuckets(_ context.Context, _ string) ([]*storage.Bucket, error) { + creationTimestamp := time.Now().Format(time.RFC3339) + return []*storage.Bucket{ + { + Name: "bucket-public", + Id: "bucket-public", + TimeCreated: creationTimestamp, + Encryption: &storage.BucketEncryption{}, + RetentionPolicy: &storage.BucketRetentionPolicy{}, + IamConfiguration: &storage.BucketIamConfiguration{ + UniformBucketLevelAccess: &storage.BucketIamConfigurationUniformBucketLevelAccess{}, + }, + }, + { + Name: "bucket-2", + Id: "bucket-2", + TimeCreated: creationTimestamp, + Encryption: &storage.BucketEncryption{}, + RetentionPolicy: &storage.BucketRetentionPolicy{}, + IamConfiguration: &storage.BucketIamConfiguration{ + UniformBucketLevelAccess: &storage.BucketIamConfigurationUniformBucketLevelAccess{}, + }, + }, + { + Name: "bucket-public-allusers", + Id: "bucket-public-allusers", + TimeCreated: creationTimestamp, + Encryption: &storage.BucketEncryption{}, + RetentionPolicy: &storage.BucketRetentionPolicy{}, + IamConfiguration: &storage.BucketIamConfiguration{ + UniformBucketLevelAccess: &storage.BucketIamConfigurationUniformBucketLevelAccess{}, + }, + }, + { + Name: "bucket-accessible-from-other-project", + Id: "bucket-accessible-from-other-project", + TimeCreated: creationTimestamp, + Encryption: &storage.BucketEncryption{}, + RetentionPolicy: &storage.BucketRetentionPolicy{}, + IamConfiguration: &storage.BucketIamConfiguration{ + UniformBucketLevelAccess: &storage.BucketIamConfigurationUniformBucketLevelAccess{}, + }, + }, + }, nil +} + +func (api *GCPAPIFake) ListBucketsIamPolicy(bucketID string) (*storage.Policy, error) { + iamPolicies := map[string]*storage.Policy{ + "bucket-public": { + Bindings: []*storage.PolicyBindings{ + { + Role: "roles/storage.objectViewer", + Members: []string{ + "allAuthenticatedUsers", + }, + }, + }, + }, + "bucket-2": { + Bindings: []*storage.PolicyBindings{ + { + Role: "roles/storage.objectViewer", + Members: []string{ + "serviceAccount:account-1@modron-test.iam.gserviceaccount.com", + }, + }, + { + Role: "roles/storage.objectViewer", + Members: []string{ + "serviceAccount:account-2@modron-test.iam.gserviceaccount.com", + }, + }, + }, + }, + "bucket-public-allusers": { + Bindings: []*storage.PolicyBindings{ + { + Role: "roles/storage.objectViewer", + Members: []string{ + "allUsers", + }, + }, + }, + }, + "bucket-accessible-from-other-project": { + Bindings: []*storage.PolicyBindings{ + { + Role: "roles/storage.legacyBucketOwner", + Members: []string{ + "serviceAccount:account-3@modron-other-test.iam.gserviceaccount.com", + }, + }, + }, + }, + } + if iamPolicy, ok := iamPolicies[bucketID]; ok { + return iamPolicy, nil + } + return nil, fmt.Errorf("invalid bucket id %q", bucketID) +} + +func (api *GCPAPIFake) ListCertificates(_ context.Context, _ string) ([]*compute.SslCertificate, error) { + creationTimestamp := time.Now() + sslCertificatesList := []*compute.SslCertificate{ + { + Name: "cert-managed", + Type: "MANAGED", + CreationTimestamp: creationTimestamp.Format(time.RFC3339), + ExpireTime: creationTimestamp.Add(oneYear).Format(time.RFC3339), + SelfLink: "/links/cert-managed", + Certificate: strings.ReplaceAll(` + -----BEGIN CERTIFICATE----- + MIIFTTCCAzUCCQDBvVCMwjjyWjANBgkqhkiG9w0BAQUFADBVMRAwDgYDVQQLDAdV + bmtub3duMRAwDgYDVQQKDAdVbmtub3duMRAwDgYDVQQHDAdVbmtub3duMRAwDgYD + VQQIDAd1bmtub3duMQswCQYDVQQGEwJDSDAeFw0yMjA3MTkwOTEwMDRaFw0yMzA3 + MTkwOTEwMDRaMHwxJTAjBgNVBAMMHGRvbWFpbi0xLm1vZHJvbi5uaWFudGljLnRl + YW0xEDAOBgNVBAsMB1Vua25vd24xEDAOBgNVBAoMB1Vua25vd24xEDAOBgNVBAcM + B1Vua25vd24xEDAOBgNVBAgMB3Vua25vd24xCzAJBgNVBAYTAkNIMIICIjANBgkq + hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7b8cRiT7Wy7/uyY3sowwEBpWd8V6tzQP + 00ISr6K8Yi5boIbJ8sO17DQttOckfmRl6/itzDrZnaaJ/0Jxh7WfifVvxACyDdEZ + QxVUf/FOoyw9qUeI4PH7tPogW6Zkc10L2i7v6fvz6FzT1QTRRlX74X8AD3F/9I8X + w5SGfvfCnWJHGiY3cp29hezUkqhfuPUDhN+vRnQuyoxjib2BBtiWTwr4+t2kfWep + db6LuIZ6fLcfFN9CCow6YfK2Q0kw9VdPsVr8YkDMdCEKoyJKQcKIB4B5BfMGTH0F + 8op9nxIgNJ6K6LgpUtOWQBvAKIzRpnJQfq7wfqWLEp8P4F7VWq/ysLP7tJnMc643 + c397n7y+DpGGHCB/jfrg/Uu6rzpLiDwZaFeNSU9MQ074oZ+bEpJRFb40FEKK+Qov + ytXE/f7oC+5hPnDKPN1DDYZAMw2cMzyL0W3/lOi+X3HuxWCDieNgVbvfWea9ejfC + NuA86OrzELyHqqTXw7jr1rIdNlPjcU4G0mAuqsfHBD42wc406OBL45zKe+Icu7dt + 3ps/dx58ZroYOVqWEo+lfAG9F9hktX9HJUhVGzTLFjsd0UzeGvPHLgL2Y/GlHyK1 + kym4tDFzDLuk4jG7G20ctaIdjbhh0UDp0uVmCZY5r78h1mQzObXFkeup2VdI+yIe + bN1o6Po8nAkCAwEAATANBgkqhkiG9w0BAQUFAAOCAgEAtPEBotLk5ucJ70wpnPX2 + agRWJ8MpJvqnUP5iEVv9iJlD2EnUSU+E9YuuaMipw+F7g6BUFx39/ZQmzqR3Jh1c + gPaNU5YdVWqHPnukCMXKWfvN8WJPLyrZaJenjn/nFwFnEBsle6JtCQJ6okEXwD1V + LQoopVfqkXyYVICupOZhcTa/6MB59tUOUzOy5LnBZj3XGEQXE67eA+eDg1vfivDS + ux1H1eopE978RtGArmnZCkuUxxv39aEDWbN58tFb7MRcy43GuK3GdPlP9bUh02+d + f9dmpLWgrnxyub8tqK9bV3A/POHk3KLY1bUc5ZZFJVM3rR0Y87P38bPcOfcvb/H2 + SI9H7UjWMI5+K+DwZNL00h0N9EgHxcewslav7JTWAm1SSmMrUOLLHWhlAOsKWpAt + f67dWGWem4df0hrAk4kyyWlBDssDNgh9zN2VXewTZd4j8S5Sr9pTzVMGlTaIpCWn + bRKfJpVEKgEAzmVBnmLEyKcX32LFeDIt+JfZcIjEzzxkMQhtcrYfDZOJGs9J5rh1 + M0ovQVnQiVfVRIyt/TiRkuRIDAcOcwO1np3IPTz63oO3iEkMqWbh4z6+ho/3j3cm + gFNrdll08NWC5hmcCIwv6hHk7DlLVXzrDP3ZLNm7JcW2gwygn8BgQSu1SLAlaM3c + R1UzrGiyiwwbtyAKYwrn+A0= + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIFJjCCAw4CCQCEKQM+pKbyBTANBgkqhkiG9w0BAQsFADBVMRAwDgYDVQQLDAdV + bmtub3duMRAwDgYDVQQKDAdVbmtub3duMRAwDgYDVQQHDAdVbmtub3duMRAwDgYD + VQQIDAd1bmtub3duMQswCQYDVQQGEwJDSDAeFw0yMjA3MTkwOTA5MzZaFw0yMzA3 + MTkwOTA5MzZaMFUxEDAOBgNVBAsMB1Vua25vd24xEDAOBgNVBAoMB1Vua25vd24x + EDAOBgNVBAcMB1Vua25vd24xEDAOBgNVBAgMB3Vua25vd24xCzAJBgNVBAYTAkNI + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA8fqY2Tdvu+fBAlUhMvPu + 3KPSjUMS2Oe1rfhRGHT/oIIp+oQenAKkUnlkJoct+UKiDijg4ovFKUjenxWqu66n + mCEUFGtBWZrIPlczhMm+vuQipMy6VTV+8wgMFd40wkupxwPNhTN1tk7wsOqu2bQy + FMquqJGU4LAUCuUuu95p2OaKxBzKcK2zhJKUEPuqZJED9UPdurwUfoNMbekh4YlO + WDmbVGJDOcs8o9fxeAgS7uGL7Y1G53BZ3ZVj3kZ4puAbvRd7g3mxiFKgY0V/6IOb + Cj4x3L4HJry2KNnaRj1/MxELAv5STRo5Sa7CTXUmKaxyEsgK2+ek5ispo8w1Efvk + Q7oQiKoCmtmgRWIroRHPOahBldk82CCiQHGJV5gCEL+n62OJb51uM51Amwr1pBLm + 6p3PflLUp8nIfUGUw845T8KfdZPz7nOif+IGfch1nZZJN1tuw9YcfKbjsh62xi+/ + nN4B2KFQl89MgCjWm+TQDoP4ToIS1+BlB+DXPy8zLwa+sUSNMawXf9LIYV7wWjD+ + Snk/8IceCaxF3SW5EjausKQ+cYXN6LecOlL5Aw9I1++PuZ4VTfbtC+BFvRUP/apb + FzzLziMwxlhC2LV6lMJN6V6T+pM1HnNDPv0SuUCO/lzI/NKC42llmS3xUSMMoZNo + QrU3ClZk2Fs0z8/qc0ycb/cCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEASL9Td1DU + NdtfPpW39Uwia5hnG6rnRz37EcqWup+V9zzvzOFc1FnRPItNPRJLmJoQ9CLAWJVG + yrE/bcODLbyGeGC6vvRhTcpriij99kEjy36cxm+XqkSBUYRqUs87jLvZrdhPaSKq + P1gj3LK7LE7nCYuP6zVLrnrVxlVbeKXIs5Pcaa9sYR9oi+hGDRn6bcDj4y7qixxW + LuVYnjyHs/pKb+75DRyaDFF7u4VlXcqGH2t8F+ZipNzMT2mx7sr+xQkpsbJQRVSL + Cx4ih2TbcyqApLU50JgjtbQYvMOngB+NI2LgJ/VOzokSLGG9YbblfYMPggQYaUXC + bDQuPvQCG0hQpqBKgluk65AmCba4BCRLLUy01U1i7ScxtmtWyn1HSLyJmxGkCGxc + OWL4qMDIGtgE32Es2WW8VSfFpH7n85hFx6Z93tVTgjxWQn6t2cAu17qbgVuI81mp + gpKRYgexWtC/K2bftPGrjajWSsRTIM1JZd6awtjBdbgvLeu02MERQQ5wZ7a9Ee1X + EjKOG1vj2c95sMiuwebY+evXZ5najnNsdYwfSXyX1hULt1R59hxPcuYVig1qM+ch + oRU0QKlNW30K+RQPb/ZGMFODsaYNOxvgAQSSeOQjyoVVHm5ZBZoU3LY98M9X2kFx + FbGm99HuLTXv1ReyURGzjxZIAqHd6hnX5wk= + -----END CERTIFICATE----- + `, "\t", ""), + }, + { + Name: "cert-self-managed", + Type: "SELF_MANAGED", + CreationTimestamp: creationTimestamp.Format(time.RFC3339), + ExpireTime: creationTimestamp.Add(oneYear).Format(time.RFC3339), + SelfLink: "/links/cert-self-managed", + Certificate: strings.ReplaceAll(` + -----BEGIN CERTIFICATE----- + MIIFTTCCAzUCCQD9AMCeW12GEDANBgkqhkiG9w0BAQUFADBVMRAwDgYDVQQLDAdV + bmtub3duMRAwDgYDVQQKDAdVbmtub3duMRAwDgYDVQQHDAdVbmtub3duMRAwDgYD + VQQIDAd1bmtub3duMQswCQYDVQQGEwJDSDAeFw0yMjA3MTkwOTAwNDBaFw0yMzA3 + MTkwOTAwNDBaMHwxJTAjBgNVBAMMHGRvbWFpbi0wLm1vZHJvbi5uaWFudGljLnRl + YW0xEDAOBgNVBAsMB1Vua25vd24xEDAOBgNVBAoMB1Vua25vd24xEDAOBgNVBAcM + B1Vua25vd24xEDAOBgNVBAgMB3Vua25vd24xCzAJBgNVBAYTAkNIMIICIjANBgkq + hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0no57IbQwzRNJ61/FoBPknTqa24oNATG + DU/oY4bTQCOPC7bEc/5IAPQ8Fhd2u3HsI5rkkUGAeQCAnEymB5z+RMG4uGZk7CKS + mozKFhf2asSSehWENYCORc2sM5DRMC+7mOHl1+Gs3uIpZhq0C2L+HAbyXi9TE8bu + lyyD/bk65ocGfSsajHJr6VzlKI/YZFzty6VXlSafxcEVbUrZ//YqtogCHQHN5d9Y + sw+5tYehGqi0O1XPQ+KkJ4PmP9yD46MAtPtiQwBRClA7G68jJ3j50stG/A59Otvj + COUApdQeL9VwHhNh9t4TQZaZcGLFb9qyNKW2hhJP63cQTPQ7asTagKemdSB0zwoT + LKZia67bG4PXExLwPuxI12oFug/g0AGdrmlJiIjXGvZPyEJ7bc/6hUxWfEpfo77v + brkndn2aJTS98HI3R+eBCbCkYK6yWBJmAV1RjcNNGlPOIHxWeJtnG90L/n7vNIL1 + LtzK/W9cTLAH1PkexeSmOEJqYC0FihjkhXZM0KUZFjjT5z5wdZsDX1f+q/6Yvo/R + 2OphVI5GfY/W5Jc2mzsGzkiHi3epow0MEtbEJ4c12afxKuUGqwKyO8T92DOVZ2BS + iew/DZ673hbek3C4sVq+c4fCIxYOLL/fqeL5r+yh7a+tAGlIxU1fpSodanu+ksQU + T7TqJFH0LXECAwEAATANBgkqhkiG9w0BAQUFAAOCAgEANm6bCAju+jm9sI/Pry1u + KS6SRMNX0ne0knjDRXO0lHjMz095xmEQA/Wu6dpPkhMo7QdYsZ0bntIgt4Go/1qx + 3rZUnP9B77BqPsLxqgBfMxryZVADQMEvsUkr0yK9g6crEQ/aSu5h9hEiSX1FA+9y + Jy00TwxS2NcTPd1AuFa5/lXZaw2Iu9nwZ4/+2QuZrjmZfE28gcUGb1GDcBMzcqZM + 8O4J4Xogi5DSzQLucPkBX8uD1ibEn1Cs16Kzq9X+45M3zPWwNnV4yM+38ZxU20Do + gDTKR2Md5JByhKt+8TSe1S+fEg5cZwGj8P3LCUFUAPjloHu59sjmHcc21MHhS3JQ + TpqQJgLpo3bdwbhUsvenXSUsk08e0PnvaIym6ALgDku/ZWYLmkGKHDSWE4otFDkF + BUbDHHxSuH8Pk5eNSOf7rfFmDk7r9Hj3ryqMZf8xq+kIHmzNESAQskFScBPj3iY3 + mCb7p2/gEmSddYR7TtDG+J1au4+sVDkFd9dIrMVhwZuY19m0S6TqpJ3pp9p6OBoq + d8ZyTuiP6LTehRiFBQrFA7LGhU14pPIVbOS5PuP2W91DzL9ZfKwsQ/Tr08ZhOH58 + ocnbZWQWQ6NEZzsnrwuyNa7DxLUfDc3Itfg94oYy4YSO7SdrifJWAvHlqV69CZ8K + G67Se2laEbw8sNaehw/0mpE= + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIFJjCCAw4CCQDlQR4bAQ4wBzANBgkqhkiG9w0BAQsFADBVMRAwDgYDVQQLDAdV + bmtub3duMRAwDgYDVQQKDAdVbmtub3duMRAwDgYDVQQHDAdVbmtub3duMRAwDgYD + VQQIDAd1bmtub3duMQswCQYDVQQGEwJDSDAeFw0yMjA3MTkwOTAwMTZaFw0yMzA3 + MTkwOTAwMTZaMFUxEDAOBgNVBAsMB1Vua25vd24xEDAOBgNVBAoMB1Vua25vd24x + EDAOBgNVBAcMB1Vua25vd24xEDAOBgNVBAgMB3Vua25vd24xCzAJBgNVBAYTAkNI + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx1GS+PP1If+ShSuJeIHm + jCTAr7d8nExf3hZ1x41+MaJfDx6XSFsyqDi9rad2KGnI7x3OFmLP7AfIhVfN+mYB + HcCST5YfPMuyaLa2roLZHaz0ZzFYhexMsK5Lq/QnaW/Exoo/lY2Rxu8uYLHOX28i + 6lQPka52Wi8D8sWHfUMUMxjoaV97LYFYsz8ngqeN11bvXkDoo5Tp0CWRb1o4LoBt + cf8ub5PJH8YW5LBglC6rTR1Q9H9YX6gqnSrP3vqqfkVdqPLTlSILqCgqxvKqbjIb + RkMlkzaiqNf3rz5iC8br0ZfKWgs/Jzvhea+K3J5Y3YbwExjttUekhXIUZqbzAfNQ + TIAUKfLlmmJfywbmI+xgUfSxNAPwgASjnxmkbAXsVW8SjUvnnLsbla6SyS1a+bwe + 1OP0gmwjtfeGN7QSCtU+GSZ/3RP1mfluog2acHR7HfRi2dzVyabGPKe1FbU/QFIu + dtl6YSXvKUFM1mC5lIj0s05vTaszw7JKAEQaVizaDCFt/d3xI9oaQFi1Z6W/DxmB + 6GS98iQ9ydLatEXCipfmTrJhxf9mbRE8Z4NVTg4kEllNwcV9W1yRqdVnfLcGe6TB + lpfk63PYBsLfuB6KVEyu3hGwOfP4UVNE/A/BfyYYVKWobR7L4GzOxmm24ikSo6fq + HhNOTKogByaSoXBfdm8g1WcCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAWrVESQoE + UXRxoUOxEKhS9UgsQrf70k8wHgwgEGFUATPFTfMInWhrQ+VMj4ImSxDOT5tDLADq + 0hU/h0oQ2XkC14YVpcF835Vt2mCPaRugPzjxzU/Ky9Ie39izZeBvG2orCthEglof + FtVZGc3vCmWEXs7zPhSwx2BsZNw0xVMLg6lTok7wVcf66lW/1PWQp8dKwwZlSvHg + VgRLfmH2yisEak0euw1zMYRs4GdwMxUC69ImremBG1MrAQdyvp1ZM7XamyLFZivg + UTnVMDLduHub+IpITnl2IYgqvMdATpL9h4n036WvYvu2qP765j0ZW0yvBMFSVS3/ + eKVEn3NqK3nFZGlo4W/i3mElbFtd+q8mxQiI1S9hF1W1yTuVPDfsVYzO+wWOQHdk + b4XoWikC2eq98zMp9wWPFrnbNFiTLTllWKUYWZQgx7UrkA+wmtKOAwxiY0tADMHg + IwLHGUhHpIG5ErJX7AKFUShb3jSqujOkU8Bmtr0W2jd+uYGp8MWT8d5drrO2aVAW + CdBMmRly672Sy1Y7MTZjLykrMEdsnmXosvIvzPWzbqAjsTJQR3OKSFMaBO+lxCXs + n+WngS5fO6hiTKqf1fjDSeBhOlpywVV8h0ONMNF0TIHyydJEYbIlZBajER3dUIZs + muOKyutYtJqW5tqke8N7Yy9oDUlqtt6gnFE= + -----END CERTIFICATE----- + `, "\t", ""), + }, + { + Name: "cert-managed-2", + Type: "MANAGED", + CreationTimestamp: creationTimestamp.Format(time.RFC3339), + ExpireTime: creationTimestamp.Add(oneYear).Format(time.RFC3339), + SelfLink: "/links/cert-managed-2", + Certificate: strings.ReplaceAll(` + -----BEGIN CERTIFICATE----- + MIIFTTCCAzUCCQCUhTr1JbteOjANBgkqhkiG9w0BAQUFADBVMRAwDgYDVQQLDAdV + bmtub3duMRAwDgYDVQQKDAdVbmtub3duMRAwDgYDVQQHDAdVbmtub3duMRAwDgYD + VQQIDAd1bmtub3duMQswCQYDVQQGEwJDSDAeFw0yMjA3MTkwOTExMjlaFw0yMzA3 + MTkwOTExMjlaMHwxJTAjBgNVBAMMHGRvbWFpbi0yLm1vZHJvbi5uaWFudGljLnRl + YW0xEDAOBgNVBAsMB1Vua25vd24xEDAOBgNVBAoMB1Vua25vd24xEDAOBgNVBAcM + B1Vua25vd24xEDAOBgNVBAgMB3Vua25vd24xCzAJBgNVBAYTAkNIMIICIjANBgkq + hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuiFSKID/n0ozLBdVrxCjyyHq9jEsbOWw + CmcJ7nbrsiP9w1qceljMg1/IvqaqHySoPvxurp8Vxq9OXgWVJzmCuMyoCUMsXEyt + K9+CcCz0AMoaxY5uO2+ehpm2SPnx+p8zfKqH6n9Heto+YU2IQYJTkbAIEKXSJib4 + iDutY7AlCAzlMqFXZpxyPBQfgFvM744WE/FOhv3ASdJ7TTqK8vuulSk46zgYe/hz + jBevt8MUIyE9dsQ/eIL4HsS+OofkDU4onsTJVrMpetZD0KwB6lBrdVLkIbn5mWHD + 2ONRLuuncQPLkyc1LEjY5+RYj2KUrXX+3jByco7e4pFDDJjFKXDl/MujCNtWtb9z + SaR362Ic6/93CkvmCiuS9IeqrMv8ZniM7HOtSs4Zq/e9Ym4/YJ5BbzrbSK8pMvhE + dsSENgxlSGCVBrs1DLBSmJ89qX3Q+Y4ejJq97Tzs9yR6MEycFYKOGX4FdwkfcZb6 + Fi+v13D+9x8WUtehTOcap9jXbiACSzGbkVbD0Q/MlEPhj9erkyQQFwhaLTZCoEo9 + SpMH/pJt8n4AYtfl1Xrw8yLjv93n54MNjMDOELMJluPAP4GfkAOtS/ck3gT/Cm53 + SW76ocPTXrN0cvybl5ShM0coC+jukbTbBUQ7eiv37LEcsZCj5CNW4/ee/Vrn1XlI + TtBrN9lZVjUCAwEAATANBgkqhkiG9w0BAQUFAAOCAgEAfKrlr72uUmD0CNmQctP4 + dAN0vuIkNC5aRW8P3p8Ba7urgWo53r2f25gxbpW/ESRT0vgMChHhoKzgP65o67n2 + 8neOIedfJPTxZRlKqsc1Yhlp+FtVgnrgk0oTZupYqIHT4L98uFzNkhZo7FFHOLzA + rrbMABXgRs8umW7OMsjBw2akbRwxbPwalM8NGvgzH1zAUz5oMq9s1AqFHITdLDvu + /CWM/vBJGXqkQ/+uHYrOP1E8hrAZtQFEQ9rJVR3fKLAqAcSLBmB0q94zbXoBq/W8 + uZyXdrItA1COb8oeHSzJejcGEPl6VhfsgKTHoSwWCvplPZ8SOP4g8XohqXT9aakR + ism2Fmc9yewA7GOzU4vw/6Oqaka6MwoyIUUbb7Mt6rdcE+gJiqfujwyilt+DIkx+ + D7Ex+meotSTP+xWIJbaeoNxJ/M19gGH0M9FxlIoYr/flYCSkUNlGy5EFSclI7VTz + GlOCJmBICrj3VDP3Q4iHv8O7DErAv+oHYf7j53/jg2mIDeIVJRjihzwYBaZN6dLd + sVmCwzd/ZntJlu2II2PnrR5UxsfmpevMSgMrChSOKh/mfPGVNF78r7QXawkrndZa + nymkGGSoll+Shlv5MpKB9PR4XfMM14dyuE568AmSbMnGPhYqSmauXDSSXnNi7KtT + kNOsaHc3Uaw1jIi2BOwpJfY= + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIFJjCCAw4CCQDCXGx9MFOr3zANBgkqhkiG9w0BAQsFADBVMRAwDgYDVQQLDAdV + bmtub3duMRAwDgYDVQQKDAdVbmtub3duMRAwDgYDVQQHDAdVbmtub3duMRAwDgYD + VQQIDAd1bmtub3duMQswCQYDVQQGEwJDSDAeFw0yMjA3MTkwOTExMjBaFw0yMzA3 + MTkwOTExMjBaMFUxEDAOBgNVBAsMB1Vua25vd24xEDAOBgNVBAoMB1Vua25vd24x + EDAOBgNVBAcMB1Vua25vd24xEDAOBgNVBAgMB3Vua25vd24xCzAJBgNVBAYTAkNI + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6HkB1jv4AjGmAgkwlozD + zmkTQiomPS6jWiQBec58j8s0WhLlWwvPKkv090kpY13lGk8FF8OR+wb7J7x6AZl6 + 03+PzacBGsXDiRkQ08HYSZ0pW/hRZFQ7UEq00luUZut7xzebbnCDqvSz/C8sGuZk + V4o9wWiOkDRD2szHRqmiHjZeHePCy/3xEPDb/OMuwXmjnS9qkMYtsCLtub54QmJZ + 3fdol86wSobkydhQ8vvIJqfxmNX3bhJUx6PDNAjoyOgxX+YBMLyDDHRuXFH7mj5H + Nv0ZDmuVgMGrgzAmWoGIfFRP/R8rGvpscPX0GrhQdoroyLAkvDZiAYnK/Fx8N8Kz + AHpfxQQIR62X9vuLWtUCdvV0qo0GG2+QCAws4n3BM6TwXkCkQcpg6pIJgtvk/avN + hVAQcLlul5ohnAVPlMV6/cs+UnOTn8pkCvE3G1JuaSHjsHELropXMeYbrUX5Q+Kd + 9JGxtC/sUIAmAL2YrFxI6tC9RFCCK2HJxZMh012wcwz/HSSrT4yXv+P61OUK0mSo + cvttUjgpGE5Z0hvyRMEq/UIwuWjNymcO+8f62Cn0v4EM1bh5XS5F0d8ILZjKU4JX + Bqi7eOWd5MFCS3Q8pdCOvWtxefMDgZb1MYwLyVcsuaHIdrVtzeAEieqD/rbVoXe7 + 0oQ8bfjDyWOGbpusqwCxCnECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAvGLv5q7w + vZl/UPxIhs1HWEbGaNiOXsiRXYbs6tgViABcsjjZkErlv8oo3KSkf6yfXI55y6tn + wKqP2RgKuTrWaTp63h6EDGHnLl0X+Nq6FGnxwiRaJ2iYzwvC2mEDVtEgod7DtRdI + 7xRqufLdHJEm8uUm+EfCjWaamnAqc2RCGU50Dezn7Tif88eAMCMMu0Nqg+HCr3zM + G+/a2OyFxUNZRWGRkthQjQTjPcTu/uBuxtLMzG8BqfpAM8XCH26Q0zxnD4g4NLuo + 03932aEaNFDHfyQJ4sFbH65+1EzudAt2emWS3g519+/0UU9Lthb6Y/aMvnR37Q9x + dh9GLl+PkDpE8GOZGuavwkNCyvKTGNwYRpQrK8fal6e2sTyKObzmn+s0tkvputy1 + mp/DbMIIXykFRZ8Y2Aps6pgSjJBuI1HBR3nAX+J1fTAjUghEMkbt/N9MheUDQfzh + hO9Qgo27PltMyUPOuhclLKHIZLsJrgSf8dFfWHpzSYFhtPr2gNTaSWuD6UxLKPHr + bz9GjrScMtDzB5n8alcySomoATWP5wnPArb6wJyg8pfyrb/43VZeaWCDhPYDVj1i + A/klwbs/a2YF0w72ZTd1aydFct5ONPcYhcY/4Zip5JZT5SCzWNaNTp8UL5TTv4zn + y4rzKfl2JQSqXBbOdR4KUDN0uhXFqPDEyK4= + -----END CERTIFICATE----- + `, "\t", ""), + }, + } + return sslCertificatesList, nil +} + +func (api *GCPAPIFake) ListCloudSQLDatabases(_ context.Context, _ string) ([]*sqladmin.DatabaseInstance, error) { + autoResize := true + return []*sqladmin.DatabaseInstance{ + { + Name: "cloudsql-test-db-ok", + InstanceType: "CLOUD_SQL_INSTANCE", + ConnectionName: "test-connection", + DatabaseVersion: "TEST_VERSION", + Settings: &sqladmin.Settings{ + IpConfiguration: &sqladmin.IpConfiguration{ + RequireSsl: true, + AuthorizedNetworks: []*sqladmin.AclEntry{ + { + Value: "127.0.0.1/32", + }, + }, + }, + StorageAutoResize: &autoResize, + }, + }, + { + Name: "cloudsql-test-db-public-and-authorized-networks", + InstanceType: "CLOUD_SQL_INSTANCE", + ConnectionName: "test-connection", + DatabaseVersion: "TEST_VERSION", + Settings: &sqladmin.Settings{ + IpConfiguration: &sqladmin.IpConfiguration{ + RequireSsl: true, + Ipv4Enabled: true, + AuthorizedNetworks: []*sqladmin.AclEntry{ + { + Value: "127.0.0.1/32", + }, + }, + }, + StorageAutoResize: &autoResize, + }, + }, + { + Name: "cloudsql-test-db-public-and-no-authorized-networks", + InstanceType: "CLOUD_SQL_INSTANCE", + ConnectionName: "test-connection", + DatabaseVersion: "TEST_VERSION", + Settings: &sqladmin.Settings{ + IpConfiguration: &sqladmin.IpConfiguration{ + RequireSsl: true, + Ipv4Enabled: true, + }, + StorageAutoResize: &autoResize, + }, + }, + { + Name: "cloudsql-report-not-enforcing-tls", + InstanceType: "CLOUD_SQL_INSTANCE", + ConnectionName: "test-connection", + DatabaseVersion: "TEST_VERSION", + Settings: &sqladmin.Settings{ + IpConfiguration: &sqladmin.IpConfiguration{ + RequireSsl: false, + AuthorizedNetworks: []*sqladmin.AclEntry{ + { + Value: "127.0.0.1/32", + }, + }, + }, + StorageAutoResize: nil, + }, + }, + }, nil +} + +func (api *GCPAPIFake) ListClustersByZone(context.Context, string, string) ([]*container.Cluster, error) { + return []*container.Cluster{}, nil +} + +func (api *GCPAPIFake) ListFoldersIamPolicy(context.Context, string) (*cloudresourcemanager.Policy, error) { + return &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "roles/owner", + Members: []string{ + "user:account-1@example.com", + "user:account-2@example.com", + }, + }, + { + Role: "roles/test2", + Members: []string{ + "account-2@example.com", + }, + }, + { + Role: "roles/iam.serviceAccountAdmin", + Members: []string{ + "account-1@example.com", + }, + }, + { + Role: "roles/dataflow.admin", + Members: []string{ + "account-1@example.com", + }, + }, + { + Role: "roles/viewer", + Members: []string{ + "account-2@example.com", + }, + }, + }, + }, nil +} + +func (api *GCPAPIFake) ListInstances(_ context.Context, _ string) ([]*compute.Instance, error) { + return []*compute.Instance{ + { + Name: "instance-1", + Id: 0, + NetworkInterfaces: []*compute.NetworkInterface{ + { + NetworkIP: "192.168.0.1", + AccessConfigs: []*compute.AccessConfig{ + { + NatIP: "240.241.242.243", + }, + }, + }, + }, + ServiceAccounts: []*compute.ServiceAccount{ + { + Email: "account-1@modron-test", + }, + }, + }, + }, nil +} + +func (api *GCPAPIFake) ListNamespaces(_ context.Context, _ string) ([]*cloudasset.ResourceSearchResult, error) { + return []*cloudasset.ResourceSearchResult{ + { + AssetType: "k8s.io/Namespace", + CreateTime: time.Now().UTC().Format(time.RFC3339), + Name: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace", + ParentFullResourceName: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster", + }, + { + AssetType: "k8s.io/Namespace", + CreateTime: time.Now().UTC().Format(time.RFC3339), + Name: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace-other", + ParentFullResourceName: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster", + }, + }, nil +} + +func (api *GCPAPIFake) ListPods(_ context.Context, _ string) ([]*cloudasset.ResourceSearchResult, error) { + podspec := &k8s_io_api_core_v1.PodSpec{ + Containers: []k8s_io_api_core_v1.Container{ + {Name: "fake-1"}, + }, + } + podspecBytes, err := json.Marshal(podspec) + if err != nil { + return nil, err + } + return []*cloudasset.ResourceSearchResult{ + { + AssetType: "k8s.io/Pod", + CreateTime: time.Now().UTC().Format(time.RFC3339), + Name: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace/pods/modron-pod-test-name-1", + ParentFullResourceName: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace", + State: "Running", + VersionedResources: []*cloudasset.VersionedResource{{Resource: podspecBytes}}, + }, + { + AssetType: "k8s.io/Pod", + CreateTime: time.Now().UTC().Format(time.RFC3339), + Name: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace/pods/modron-pod-test-name-2", + ParentFullResourceName: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace", + State: "Pending", + VersionedResources: []*cloudasset.VersionedResource{{Resource: podspecBytes}}, + }, + }, nil + +} + +func (api *GCPAPIFake) ListOrganizationsIamPolicy(_ context.Context, _ string) (*cloudresourcemanager.Policy, error) { + return &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "roles/owner", + Members: []string{ + "user:account-1@example.com", + "user:account-2@example.com", + }, + }, + { + Role: "roles/test2", + Members: []string{ + "account-2@example.com", + }, + }, + { + Role: "roles/iam.serviceAccountAdmin", + Members: []string{ + "account-1@example.com", + }, + }, + { + Role: "roles/dataflow.admin", + Members: []string{ + "account-1@example.com", + }, + }, + { + Role: "roles/viewer", + Members: []string{ + "account-2@example.com", + }, + }, + }, + }, nil +} + +func (api *GCPAPIFake) ListProjectIamPolicy(_ context.Context, name string) (*cloudresourcemanager.Policy, error) { + iamPolicies := map[string]*cloudresourcemanager.Policy{ + "modron-test": { + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "roles/owner", + Members: []string{ + "user:owner1@example.com", + "user:owner2@example.com", + }, + }, + { + Role: "roles/test2", + Members: []string{ + "serviceAccount:account-2@modron-test.iam.gserviceaccount.com", + }, + }, + { + Role: "roles/iam.serviceAccountAdmin", + Members: []string{ + "serviceAccount:account-1@modron-test.iam.gserviceaccount.com", + }, + }, + { + Role: "roles/dataflow.admin", + Members: []string{ + "serviceAccount:account-3@modron-other-test.iam.gserviceaccount.com", + }, + }, + { + Role: "roles/iam.serviceAccountAdmin", + Members: []string{ + "serviceAccount:account-3@modron-other-test.iam.gserviceaccount.com", + }, + }, + { + Role: "roles/viewer", + Members: []string{ + "serviceAccount:account-2@modron-test.iam.gserviceaccount.com", + }, + }, + }, + }, + "modron-other-test": { + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "roles/owner", + Members: []string{ + "user:owner1@example.com", + "user:owner2@example.com", + }, + }, + { + Role: "roles/test3", + Members: []string{ + "serviceAccount:account-2@modron-other-test.iam.gserviceaccount.com", + }, + }, + { + Role: "roles/iam.serviceAccountAdmin", + Members: []string{ + "serviceAccount:account-1@modron-other-test.iam.gserviceaccount.com", + }, + }, + { + Role: "roles/dataflow.admin", + Members: []string{ + "serviceAccount:account-3@modron-test.iam.gserviceaccount.com", + }, + }, + { + Role: "roles/viewer", + Members: []string{ + "serviceAccount:account-2@modron-test.iam.gserviceaccount.com", + }, + }, + }, + }, + } + + iamPolicy, ok := iamPolicies[constants.ResourceWithoutProjectsPrefix(name)] + if ok { + return iamPolicy, nil + } + return nil, fmt.Errorf("invalid project id %q", name) +} + +func (api *GCPAPIFake) ListRegions(_ context.Context, _ string) ([]*compute.Region, error) { + return []*compute.Region{ + {Name: "region-1"}, + {Name: "region-2"}, + {Name: "region-3"}, + }, nil +} + +func (api *GCPAPIFake) ListResourceGroups(_ context.Context, rgNames []string) ([]*cloudasset.ResourceSearchResult, error) { + inSearchQuery := make(map[string]struct{}) + for _, rgName := range rgNames { + inSearchQuery[rgName] = struct{}{} + } + var ret []*cloudasset.ResourceSearchResult + for _, rg := range []*cloudasset.ResourceSearchResult{ + { + Name: "projects/modron-test", + Project: "projects/modron-test", + State: "ACTIVE", + Organization: "organizations/1111", + ParentFullResourceName: "//cloudresourcemanager.googleapis.com/folders/234", + ParentAssetType: "cloudresourcemanager.googleapis.com/folder", + Folders: []string{ + "folders/234", + "folders/123", + }, + Labels: map[string]string{ + "contact1": "user-1_example_com", + "contact2": "user-2_example_com", + }, + }, + { + Name: "projects/modron-other-test", + Project: "projects/modron-other-test", + State: "ACTIVE", + Organization: "organizations/1111", + ParentFullResourceName: "//cloudresourcemanager.googleapis.com/folders/123", + ParentAssetType: "cloudresourcemanager.googleapis.com/folder", + Folders: []string{ + "folders/123", + }, + }, + { + Name: "projects/pending-deletion", + Project: "projects/pending-deletion", + State: "PENDING_DELETION", + }, + { + Name: "folders/123", + DisplayName: "General", + Project: "folders/123", + State: "ACTIVE", + Organization: "organizations/1111", + ParentFullResourceName: "//cloudresourcemanager.googleapis.com/organizations/1111", + ParentAssetType: "cloudresourcemanager.googleapis.com/organization", + }, + { + Name: "folders/234", + DisplayName: "Production", + Project: "folders/234", + State: "ACTIVE", + Organization: "organizations/1111", + ParentFullResourceName: "//cloudresourcemanager.googleapis.com/folders/123", + ParentAssetType: "cloudresourcemanager.googleapis.com/folder", + Folders: []string{ + "folders/123", + }, + Tags: []*cloudasset.Tag{ + { + TagKey: "111111111111/environment", + TagValue: "111111111111/environment/prod", + }, + }, + Labels: map[string]string{ + "environment": "prod", + }, + }, + { + Name: "organizations/1111", + Project: "organizations/1111", + State: "ACTIVE", + }, + } { + if rgNames == nil { + ret = append(ret, rg) + continue + } + if _, ok := inSearchQuery[rg.Name]; ok { + ret = append(ret, rg) + } + } + return ret, nil +} + +func (api *GCPAPIFake) ListSccFindings(_ context.Context, _ string) ([]*securitycenter.Finding, error) { + return []*securitycenter.Finding{ + { + CanonicalName: "projects/12345/sources/123/findings/0cb62f16f7a1469e86e851b5d36648f2", + CreateTime: "2024-05-23T08:36:06.385Z", + EventTime: "2024-05-23T08:36:06.245Z", + FindingClass: "THREAT", + LogEntries: []*securitycenter.LogEntry{ + { + CloudLoggingEntry: &securitycenter.CloudLoggingEntry{ + InsertId: "d000000", + LogId: "requests", + ResourceContainer: "projects/project-id", + Timestamp: "2024-05-23T08:36:01.719346Z", + ForceSendFields: nil, + NullFields: nil, + }, + }, + }, + MitreAttack: &securitycenter.MitreAttack{ + PrimaryTactic: "INITIAL_ACCESS", + }, + Mute: "UNDEFINED", + MuteUpdateTime: "1970-01-01T00:00:00Z", + Category: "Initial Access: Log4j Compromise Attempt", + Name: "projects/12345/sources/123/findings/0cb62f16f7a1469e86e851b5d36648f2", + Parent: "organizations/0000000/sources/123", + ParentDisplayName: "Event Threat Detection", + ResourceName: "//compute.googleapis.com/projects/project-id/global/urlMaps/my-url-map", + Severity: "LOW", + SourceProperties: googleapi.RawMessage("{}"), + State: "ACTIVE", + }, + { + CanonicalName: "projects/12345/sources/123/findings/30ecf06efe6604a2d6f38ccba56d339d", + Contacts: map[string]securitycenter.ContactDetails{ + "security": { + Contacts: []*securitycenter.Contact{ + {Email: "user-1@example.com"}, + {Email: "user-2@example.com"}, + }, + }, + "technical": { + Contacts: []*securitycenter.Contact{ + {Email: "tech-1@example.com"}, + {Email: "tech-2@example.com"}, + {Email: "tech-3@example.com"}, + }, + }, + }, + CreateTime: "2024-05-23T08:36:06.385Z", + Description: "You can explicitly allow a container to run as the root user if the runAsUser or the USER directive in the image specifies the root user. The lack of preventive security controls when running as the root user increases the risk of container escape.", + NextSteps: "**Apply the following steps to your affected workloads:**\n1. Open the manifest for each affected workload.\n2. Set the following restricted fields to one of the allowed values:\n\n**Restricted Fields**\n- spec.securityContext.runAsNonRoot\n- spec.containers[*].securityContext.runAsNonRoot\n- spec.initContainers[*].securityContext.runAsNonRoot\n- spec.ephemeralContainers[*].securityContext.runAsNonRoot\n\n**Allowed Values**\n- true\n", + EventTime: "2024-05-23T08:36:06.245Z", + ExternalUri: "https://console.cloud.google.com/welcome?project=project-id", + FindingClass: "MISCONFIGURATION", + Mute: "UNDEFINED", + MuteUpdateTime: "1970-01-01T00:00:00Z", + Category: "GKE_RUN_AS_NONROOT", + Name: "projects/12345/sources/123/findings/30ecf06efe6604a2d6f38ccba56d339d", + Parent: "organizations/0000000/sources/456", + ParentDisplayName: "GKE Security Posture", + ResourceName: "//container.googleapis.com/projects/modron-test/locations/us-central1-b/clusters/app", + Severity: "MEDIUM", + SourceProperties: googleapi.RawMessage("{}"), + State: "ACTIVE", + }, + { + CanonicalName: "projects/12345/sources/123/findings/48230f1978594ffb9d09a3cb1fe5e1b3", + Contacts: map[string]securitycenter.ContactDetails{ + "security": { + Contacts: []*securitycenter.Contact{ + {Email: "user-1@example.com"}, + {Email: "user-2@example.com"}, + }, + }, + "technical": { + Contacts: []*securitycenter.Contact{ + {Email: "tech-1@example.com"}, + {Email: "tech-2@example.com"}, + {Email: "tech-3@example.com"}, + }, + }, + }, + CreateTime: "2024-07-16T05:28:00.395Z", + EventTime: time.Now().Format(time.RFC3339), + Description: "To lower your attack surface, Cloud SQL databases should not have public IPs. Private IPs provide improved network security and lower latency for your application.", + NextSteps: "Go to https://console.cloud.google.com/sql/instances/xyz/connections?project=project-id and click the \"Networking\" tab. Uncheck the \"Public IP\" checkbox and click \"SAVE\". If your instance is not configured to use a private IP, you will first have to enable private IP by following the instructions here: https://cloud.google.com/sql/docs/mysql/configure-private-ip#existing-private-instance", + ExternalUri: "https://console.cloud.google.com/welcome?project=project-id", + FindingClass: "MISCONFIGURATION", + Mute: "UNDEFINED", + MuteUpdateTime: "1970-01-01T00:00:00Z", + Category: "SQL_PUBLIC_IP", + Name: "projects/12345/sources/123/findings/48230f1978594ffb9d09a3cb1fe5e1b3", + Parent: "organizations/0000000/sources/456", + ParentDisplayName: "Security Health Analytics", + ResourceName: "//cloudsql.googleapis.com/projects/project-id/instances/xyz", + Severity: "MEDIUM", + SourceProperties: googleapi.RawMessage("{\"ScannerName\": \"SQL_SCANNER\"}"), + State: "ACTIVE", + }, + { + CanonicalName: "projects/1234/sources/5678/locations/global/findings/0000", + Name: "projects/1234/sources/5678/locations/global/findings/0000", + Category: "GKE_RUNTIME_OS_VULNERABILITY", + FindingClass: "VULNERABILITY", + ResourceName: "//container.googleapis.com/projects/modron0test/locations/us-west1/clusters/my-cluster", + Severity: "SEVERITY_UNSPECIFIED", + State: "ACTIVE", + CreateTime: "2024-05-19T13:20:02.112Z", + EventTime: "2024-07-04T15:47:51.202Z", + Vulnerability: &securitycenter.Vulnerability{ + Cve: &securitycenter.Cve{ + Cvssv3: &securitycenter.Cvssv3{ + BaseScore: 5.5, //nolint:mnd + AttackComplexity: "ATTACK_COMPLEXITY_LOW", + AttackVector: "ATTACK_VECTOR_LOCAL", + AvailabilityImpact: "IMPACT_HIGH", + ConfidentialityImpact: "IMPACT_NONE", + IntegrityImpact: "IMPACT_NONE", + PrivilegesRequired: "PRIVILEGES_REQUIRED_LOW", + Scope: "SCOPE_UNCHANGED", + UserInteraction: "USER_INTERACTION_NONE", + }, + ExploitationActivity: "NO_KNOWN", + Id: "CVE-2024-35863", + Impact: "LOW", + ObservedInTheWild: false, + References: nil, + UpstreamFixAvailable: true, + ZeroDay: false, + }, + OffendingPackage: &securitycenter.Package{ + CpeUri: "cpe:/o:debian:debian_linux:12", + PackageName: "linux", + PackageType: "OS", + PackageVersion: "6.1.76-1", + }, + FixedPackage: &securitycenter.Package{ + CpeUri: "cpe:/o:debian:debian_linux:12", + PackageName: "linux", + PackageType: "OS", + PackageVersion: "6.1.85-1", + }, + SecurityBulletin: nil, + }, + Description: "In the Linux kernel, the following vulnerability has been resolved: smb: client: fix potential UAF in is_valid_oplock_break() Skip sessions that are being teared down (status == SES_EXITING) to avoid UAF.", + NextSteps: "Use the following resources to help you mitigate CVE-2024-35863.\n\n**More information about CVE-2024-35863**\n* https://security-tracker.debian.org/tracker/CVE-2024-35863\n* https://access.redhat.com/security/cve/CVE-2024-35863\n* https://www.suse.com/security/cve/CVE-2024-35863\n* http://people.ubuntu.com/~ubuntu-security/cve/CVE-2024-35863\n\n**Fixed location**\ncpe:/o:debian:debian_linux:12\n\n**Fixed package**\nlinux\n\n**Fixed version**\n6.1.85-1\n", + Kubernetes: &securitycenter.Kubernetes{ + Objects: []*securitycenter.Object{ + { + Kind: "Deployment", + Ns: "default", + Name: "my-deployment", + Containers: []*securitycenter.Container{ + { + Name: "us-west1-docker.pkg.dev/project-id/my-image:latest", + ImageId: "us-west1-docker.pkg.dev/project-id/my-image:latest", + }, + }, + }, + }, + }, + }, + }, nil +} + +func (api *GCPAPIFake) ListServiceAccount(_ context.Context, name string) ([]*iam.ServiceAccount, error) { + if name == "modron-other-test" { + return []*iam.ServiceAccount{ + { + Email: "account-3@modron-other-test", + }, + }, nil + } + return []*iam.ServiceAccount{ + { + Email: "user:account-1@modron-test", + }, + { + Email: "user:account-2@modron-test", + }, + }, nil +} + +func (api *GCPAPIFake) ListServiceAccountKeys(context.Context, string) ([]*iam.ServiceAccountKey, error) { + return []*iam.ServiceAccountKey{}, nil +} + +func (api *GCPAPIFake) ListServiceAccountKeyUsage(_ context.Context, _ string, _ *monitoring.QueryTimeSeriesRequest) *monitoring.ProjectsTimeSeriesQueryCall { + return &monitoring.ProjectsTimeSeriesQueryCall{} +} + +func (api *GCPAPIFake) ListSpannerDatabases(_ context.Context, _ string) ([]*spanner.Database, error) { + return []*spanner.Database{ + { + Name: "spanner-test-db-1", + }, + }, nil +} + +func (api *GCPAPIFake) ListSslPolicies(_ context.Context, _ string) ([]*compute.SslPolicy, error) { + return []*compute.SslPolicy{ + { + Kind: "compute#sslPolicy", + CreationTimestamp: "2021-10-04T02:19:20.925-07:00", + SelfLink: "https://www.googleapis.com/compute/v1/projects/modron-test/global/sslPolicies/modern-ssl-policy", + Name: "modern-ssl-policy", + Profile: "MODERN", + MinTlsVersion: "TLS_1_2", + }, + }, nil +} + +func (api *GCPAPIFake) ListSubNetworksByRegion(_ context.Context, _ string, region string) ([]*compute.Subnetwork, error) { + if region == "region-1" { + return []*compute.Subnetwork{ + { + Name: "subnetwork-private-access-should-not-be-reported", + IpCidrRange: "IpCdrRange1", + Purpose: "PRIVATE", + PrivateIpGoogleAccess: true, + }, + { + Name: "subnetwork-no-private-access-should-be-reported", + IpCidrRange: "IpCdrRange1", + Purpose: "PRIVATE", + PrivateIpGoogleAccess: false, + }, + { + Name: "psc-network-should-not-be-reported", + IpCidrRange: "IpCdrRange1", + Purpose: "PRIVATE_SERVICE_CONNECT", + PrivateIpGoogleAccess: false, + }, + }, nil + } + return []*compute.Subnetwork{}, nil +} + +func (api *GCPAPIFake) ListTargetHTTPSProxies(_ context.Context, _ string) ([]*compute.TargetHttpsProxy, error) { + return []*compute.TargetHttpsProxy{ + { + Name: "proxy-internal", + SslCertificates: []string{"/links/cert-managed"}, + UrlMap: "/links/url-map-internal", // TODO: Update all these links to match the value from the GCP API + }, { + Name: "proxy-external-no-modern", + SslCertificates: []string{"/links/cert-managed"}, + UrlMap: "/links/url-map-external-no-modern", // TODO: Update all these links to match the value from the GCP API + }, { + Name: "proxy-one-cert", + SslCertificates: []string{"/links/cert-managed"}, + UrlMap: "/links/url-map-no-modern", // TODO: Update all these links to match the value from the GCP API + }, + { + Name: "proxy-one-cert-modern-ssl", + SslCertificates: []string{"/links/cert-managed"}, + UrlMap: "/links/url-map-external-modern", + SslPolicy: "https://www.googleapis.com/compute/v1/projects/modron-test/global/sslPolicies/modern-ssl-policy", + }, + { + Name: "proxy-one-cert-modern-ssl", + SslCertificates: []string{"/links/cert-managed"}, + UrlMap: "/links/url-map-no-iap-modern", + SslPolicy: "https://www.googleapis.com/compute/v1/projects/modron-test/global/sslPolicies/modern-ssl-policy", + }, + { + Name: "proxy-one-cert-modern-ssl", + SslCertificates: []string{"/links/cert-managed"}, + UrlMap: "/links/url-map-iap-modern", + SslPolicy: "https://www.googleapis.com/compute/v1/projects/modron-test/global/sslPolicies/modern-ssl-policy", + }, + { + Name: "proxy-multi-cert", + SslCertificates: []string{"/links/cert-self-managed", "/links/cert-managed-2"}, + UrlMap: "/links/url-map-2", + SslPolicy: "https://www.googleapis.com/compute/v1/projects/modron-test/global/sslPolicies/modern-ssl-policy", + }, + }, nil +} + +func (api *GCPAPIFake) ListTargetSslProxies(_ context.Context, _ string) ([]*compute.TargetSslProxy, error) { + return []*compute.TargetSslProxy{ + { + Name: "sslproxy-0", + Service: "/links/backend-svc-external-modern", + SslCertificates: []string{"/links/cert-managed"}, + SslPolicy: "https://www.googleapis.com/compute/v1/projects/modron-test/global/sslPolicies/modern-ssl-policy", + }, + }, nil +} + +func (api *GCPAPIFake) ListURLMaps(_ context.Context, _ string) ([]*compute.UrlMap, error) { + return []*compute.UrlMap{ + { + Name: "url-map-external-modern", + DefaultService: "/links/backend-svc-external-modern", + SelfLink: "/links/url-map-external-modern", + }, + { + Name: "url-map-iap-modern", + DefaultService: "/links/backend-svc-iap", + SelfLink: "/links/url-map-iap-modern", + }, + { + Name: "url-map-no-iap-modern", + DefaultService: "/links/backend-svc-no-iap", + SelfLink: "/links/url-map-no-iap-modern", + }, + { + Name: "url-map-external-no-modern", + DefaultService: "/links/backend-svc-external-no-modern", + SelfLink: "/links/url-map-external-no-modern", + }, + { + Name: "url-map-internal", + DefaultService: "/links/backend-svc-internal", + SelfLink: "/links/url-map-internal", + }, + { + Name: "url-map-2", + DefaultService: "/links/backend-svc-external", + SelfLink: "/links/url-map-external", + PathMatchers: []*compute.PathMatcher{ + { + Name: "url-map-2-path", + DefaultService: "/links/backend-svc-external", + PathRules: []*compute.PathRule{ + { + Service: "/links/backend-svc-external", + Paths: []string{ + "some/where/*", + }, + }, + }, + }, + { + Name: "another-path-0", + DefaultService: "/links/backend-svc-internal", + }, + }, + }, + }, nil +} + +func (api *GCPAPIFake) ListUsersInGroup(_ context.Context, group string) ([]string, error) { + groups := map[string][]string{ + "groups/emptyGroup": {}, + "groups/group1": {"groups/group1/memberships/user1", "groups/group1/memberships/group2"}, + "groups/group2": {"groups/group2/memberships/user2"}, + } + g, ok := groups[group] + if ok { + return g, nil + } + return nil, fmt.Errorf("group %q doesn't exist", group) +} + +func (api *GCPAPIFake) ListZones(_ context.Context, _ string) ([]*compute.Zone, error) { + return []*compute.Zone{ + {Name: "zone-1"}, + {Name: "zone-2"}, + {Name: "zone-3"}, + }, nil +} + +func (api *GCPAPIFake) SearchIamPolicy(_ context.Context, _ string, query string) ([]*cloudasset.IamPolicySearchResult, error) { + switch { + case strings.Contains(query, "//cloudresourcemanager.googleapis.com/projects/"): + return []*cloudasset.IamPolicySearchResult{ + { + Policy: &cloudasset.Policy{ + Bindings: []*cloudasset.Binding{ + { + Members: []string{"owner@example.com"}, + Role: "roles/owner", + }, + }, + }, + Resource: "//cloudresourcemanager.googleapis.com/projects/project-1", + }, + }, nil + case strings.Contains(query, "//cloudresourcemanager.googleapis.com/folders/"): + return []*cloudasset.IamPolicySearchResult{ + { + Policy: &cloudasset.Policy{ + Bindings: []*cloudasset.Binding{ + { + Members: []string{"owner@example.com"}, + Role: "roles/owner", + }, + }, + }, + Resource: "//cloudresourcemanager.googleapis.com/folders/123", + }, + }, nil + case strings.Contains(query, "//cloudresourcemanager.googleapis.com/organizations/"): + return []*cloudasset.IamPolicySearchResult{ + { + Policy: &cloudasset.Policy{ + Bindings: []*cloudasset.Binding{ + { + Members: []string{"owner@example.com"}, + Role: "roles/owner", + }, + }, + }, + Resource: "//cloudresourcemanager.googleapis.com/organizations/11111", + }, + }, nil + default: + return nil, fmt.Errorf("invalid query %q", query) + } +} diff --git a/src/collector/gcpcollector/api_gcp.go b/src/collector/gcpcollector/api_gcp.go new file mode 100644 index 0000000..74df8c7 --- /dev/null +++ b/src/collector/gcpcollector/api_gcp.go @@ -0,0 +1,608 @@ +package gcpcollector + +import ( + "errors" + "fmt" + "math" + "math/rand" + "strings" + "time" + + "go.opentelemetry.io/otel/attribute" + "golang.org/x/net/context" + + // TODO: Use cloud.google.com packages + "google.golang.org/api/apikeys/v2" + "google.golang.org/api/cloudasset/v1" + "google.golang.org/api/cloudidentity/v1" + "google.golang.org/api/cloudresourcemanager/v3" + "google.golang.org/api/compute/v1" + "google.golang.org/api/container/v1" + "google.golang.org/api/googleapi" + "google.golang.org/api/iam/v1" + "google.golang.org/api/monitoring/v3" + "google.golang.org/api/securitycenter/v1" + "google.golang.org/api/spanner/v1" + sqladmin "google.golang.org/api/sqladmin/v1beta4" + "google.golang.org/api/storage/v1" + + "github.com/nianticlabs/modron/src/constants" +) + +const ( + projectAssetType = "cloudresourcemanager.googleapis.com/Project" + folderAssetType = "cloudresourcemanager.googleapis.com/Folder" + organizationAssetType = "cloudresourcemanager.googleapis.com/Organization" + k8sPodAssetType = "k8s.io/Pod" + k8sNamespaceAssetType = "k8s.io/Namespace" + excludeSysProjectsQuery = "NOT additionalAttributes.projectId:sys-" + searchForProject = `name="//cloudresourcemanager.googleapis.com/%s"` +) + +type GroupCacheEntry struct { + Creation time.Time + Members []string +} + +var ( + listGroupCache = map[string]GroupCacheEntry{} +) + +type GCPApi interface { + GetServiceAccountIAMPolicy(ctx context.Context, name string) (*iam.Policy, error) + ListAPIKeys(ctx context.Context, name string) ([]*apikeys.V2Key, error) + ListBackendServices(ctx context.Context, name string) ([]*compute.BackendService, error) + ListBuckets(ctx context.Context, name string) ([]*storage.Bucket, error) + ListBucketsIamPolicy(bucketID string) (*storage.Policy, error) + ListCertificates(ctx context.Context, name string) ([]*compute.SslCertificate, error) + ListCloudSQLDatabases(ctx context.Context, name string) ([]*sqladmin.DatabaseInstance, error) + ListClustersByZone(ctx context.Context, name string, zone string) ([]*container.Cluster, error) + ListFoldersIamPolicy(ctx context.Context, name string) (*cloudresourcemanager.Policy, error) + ListInstances(ctx context.Context, name string) ([]*compute.Instance, error) + ListNamespaces(ctx context.Context, name string) ([]*cloudasset.ResourceSearchResult, error) + ListPods(ctx context.Context, name string) ([]*cloudasset.ResourceSearchResult, error) + ListOrganizationsIamPolicy(ctx context.Context, name string) (*cloudresourcemanager.Policy, error) + ListProjectIamPolicy(ctx context.Context, name string) (*cloudresourcemanager.Policy, error) + ListRegions(ctx context.Context, name string) ([]*compute.Region, error) + ListResourceGroups(ctx context.Context, rgNames []string) ([]*cloudasset.ResourceSearchResult, error) + ListServiceAccount(ctx context.Context, name string) ([]*iam.ServiceAccount, error) + ListServiceAccountKeys(ctx context.Context, name string) ([]*iam.ServiceAccountKey, error) + ListServiceAccountKeyUsage(ctx context.Context, resourceGroup string, request *monitoring.QueryTimeSeriesRequest) *monitoring.ProjectsTimeSeriesQueryCall + ListSccFindings(ctx context.Context, name string) ([]*securitycenter.Finding, error) + ListSpannerDatabases(ctx context.Context, name string) ([]*spanner.Database, error) + ListSslPolicies(ctx context.Context, name string) ([]*compute.SslPolicy, error) + ListSubNetworksByRegion(ctx context.Context, name string, region string) ([]*compute.Subnetwork, error) + ListTargetHTTPSProxies(ctx context.Context, name string) ([]*compute.TargetHttpsProxy, error) + ListTargetSslProxies(ctx context.Context, name string) ([]*compute.TargetSslProxy, error) + ListURLMaps(ctx context.Context, name string) ([]*compute.UrlMap, error) + ListUsersInGroup(ctx context.Context, group string) ([]string, error) + ListZones(ctx context.Context, name string) ([]*compute.Zone, error) + // TODO: Get rid of the scope argument since it's not used + SearchIamPolicy(ctx context.Context, scope string, query string) ([]*cloudasset.IamPolicySearchResult, error) +} + +type GCPApiReal struct { + // TODO: Support multiple orgID in the future + orgID string + scope string + apiKeyService *apikeys.Service + cloudAssetService rateLimitedCloudAssetV1Service + cloudIdentityService *cloudidentity.Service + cloudResourceManagerService *cloudresourcemanager.Service + computeService *compute.Service + containerService *container.Service + iamService *iam.Service + monitoringService *monitoring.Service + securityCenterService *securitycenter.Service + spannerService *spanner.Service + sqlAdminService *sqladmin.Service + storageService *storage.Service +} + +func detailedGoogleError(e error, apiDetail string) error { + if e == nil { + return nil + } + var gErr *googleapi.Error + if errors.As(e, &gErr) { + gErr.Message = fmt.Sprintf("%s: %s", apiDetail, gErr.Message) + return gErr + } + return e +} + +func NewGCPApiReal(ctx context.Context, orgID string) (GCPApi, error) { + apiKeyService, err := apikeys.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("apikeys.NewService : %w", err) + } + cloudAssetService, err := cloudasset.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("cloudasset.NewService : %w", err) + } + cloudIdentityService, err := cloudidentity.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("cloudidentity.NewService : %w", err) + } + cloudresourcemanagerService, err := cloudresourcemanager.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("cloudresourcemanager.NewService : %w", err) + } + computeService, err := compute.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("compute.NewService : %w", err) + } + containerService, err := container.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("container.NewService : %w", err) + } + iamService, err := iam.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("iam.NewService : %w", err) + } + monitoringService, err := monitoring.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("monitoring.NewService: %w", err) + } + securitycenterService, err := securitycenter.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("securitycenter.NewService: %w", err) + } + spannerService, err := spanner.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("spanner.NewService: %w", err) + } + sqladminService, err := sqladmin.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("sqladmin.NewService: %w", err) + } + storageService, err := storage.NewService(ctx) + if err != nil { + return nil, fmt.Errorf("storage.NewService : %w", err) + } + + return &GCPApiReal{ + orgID: orgID, + scope: constants.GCPOrgIDPrefix + orgID, + apiKeyService: apiKeyService, + cloudAssetService: newRateLimitedCloudAssetInventoryV1(cloudAssetService.V1), + cloudIdentityService: cloudIdentityService, + cloudResourceManagerService: cloudresourcemanagerService, + computeService: computeService, + containerService: containerService, + iamService: iamService, + monitoringService: monitoringService, + securityCenterService: securitycenterService, + spannerService: spannerService, + sqlAdminService: sqladminService, + storageService: storageService, + }, nil +} + +func (api *GCPApiReal) GetServiceAccountIAMPolicy(ctx context.Context, name string) (policy *iam.Policy, err error) { + policy, err = api.iamService.Projects.ServiceAccounts.GetIamPolicy(name).Context(ctx).Do() + return policy, detailedGoogleError(err, "ServiceAccounts.GetIamPolicy") +} + +func (api *GCPApiReal) ListAPIKeys(ctx context.Context, name string) (apiKeys []*apikeys.V2Key, err error) { + err = api.apiKeyService.Projects.Locations.Keys.List(constants.ResourceWithProjectsPrefix(name)). + Context(ctx). + PageSize(apiKeysPageSize). + Pages(ctx, func(vlkr *apikeys.V2ListKeysResponse) error { + apiKeys = append(apiKeys, vlkr.Keys...) + return nil + }) + log.Debugf("%s fetched %d api keys", name, len(apiKeys)) + return apiKeys, detailedGoogleError(err, "ApiKey.List") +} + +func (api *GCPApiReal) ListBackendServices(ctx context.Context, name string) (backendSvcs []*compute.BackendService, err error) { + err = api.computeService.BackendServices.AggregatedList(constants.ResourceWithoutProjectsPrefix(name)).Context(ctx).Pages(ctx, func(bsal *compute.BackendServiceAggregatedList) error { + for _, be := range bsal.Items { + backendSvcs = append(backendSvcs, be.BackendServices...) + } + return nil + }) + log.Debugf("%s fetched %d backend services", name, len(backendSvcs)) + return backendSvcs, detailedGoogleError(err, "BackendServices.AggregatedList") +} + +func (api *GCPApiReal) ListBuckets(ctx context.Context, name string) (buckets []*storage.Bucket, err error) { + err = api.storageService.Buckets.List(constants.ResourceWithoutProjectsPrefix(name)).Context(ctx).Pages(ctx, func(b *storage.Buckets) error { + buckets = append(buckets, b.Items...) + return nil + }) + log.Debugf("%s fetched %d buckets", name, len(buckets)) + return buckets, detailedGoogleError(err, "Buckets.List") +} + +func (api *GCPApiReal) ListBucketsIamPolicy(bucketID string) (*storage.Policy, error) { + iamPolicies, err := api.storageService.Buckets.GetIamPolicy(bucketID).Do() + log.Debugf("fetched bucket %s iampolicy", bucketID) + return iamPolicies, detailedGoogleError(err, "Buckets.GetIamPolicy") +} + +func (api *GCPApiReal) ListCertificates(ctx context.Context, name string) (certs []*compute.SslCertificate, err error) { + err = api.computeService.SslCertificates.AggregatedList(constants.ResourceWithoutProjectsPrefix(name)).Context(ctx).Pages(ctx, func(scal *compute.SslCertificateAggregatedList) error { + for _, c := range scal.Items { + certs = append(certs, c.SslCertificates...) + } + return nil + }) + log.Debugf("%s fetched %d certificates", name, len(certs)) + return certs, detailedGoogleError(err, "SslCertificates.AggregatedList") +} + +func (api *GCPApiReal) ListCloudSQLDatabases(ctx context.Context, name string) (instances []*sqladmin.DatabaseInstance, err error) { + instanceSvc := sqladmin.NewInstancesService(api.sqlAdminService) + err = instanceSvc.List(constants.ResourceWithoutProjectsPrefix(name)).Context(ctx).Pages(ctx, func(ilr *sqladmin.InstancesListResponse) error { + instances = append(instances, ilr.Items...) + return nil + }) + log.Debugf("%s fetched %d cloudSql databases", name, len(instances)) + return instances, detailedGoogleError(err, "SqlAdmin.Instances.List") +} + +func (api *GCPApiReal) ListClustersByZone(ctx context.Context, name string, zone string) (clusters []*container.Cluster, err error) { + resp, err := api.containerService.Projects.Zones.Clusters.List(constants.ResourceWithoutProjectsPrefix(name), zone).Context(ctx).Do() + if err != nil { + return nil, detailedGoogleError(err, "Clusters.List") + } + log.Debugf("%s fetched %d clusters", name, len(clusters)) + return resp.Clusters, nil +} + +func (api *GCPApiReal) ListFoldersIamPolicy(ctx context.Context, name string) (*cloudresourcemanager.Policy, error) { + resp, err := api.cloudResourceManagerService.Folders. + GetIamPolicy(name, new(cloudresourcemanager.GetIamPolicyRequest)). + Context(ctx). + Do() + log.Debugf("fetched folder %s iam policy", name) + return resp, detailedGoogleError(err, "Folders.GetIamPolicy") +} + +func (api *GCPApiReal) ListInstances(ctx context.Context, name string) (instances []*compute.Instance, err error) { + err = api.computeService.Instances.AggregatedList(constants.ResourceWithoutProjectsPrefix(name)).Context(ctx).Pages(ctx, func(ial *compute.InstanceAggregatedList) error { + for _, ia := range ial.Items { + instances = append(instances, ia.Instances...) + } + return nil + }) + log.Debugf("%s fetched %d instances", name, len(instances)) + return instances, detailedGoogleError(err, "Instances.AggregatedList") +} + +func (api *GCPApiReal) ListNamespaces(ctx context.Context, scope string) (namespaces []*cloudasset.ResourceSearchResult, err error) { + if scope == "" { + // We don't list namespaces for the whole org, this is a too large amount of data. + return nil, fmt.Errorf("ListNamespaces: name is empty") + } + searchAllResources, err := api.cloudAssetService.SearchAllResources(ctx, scope) + if err != nil { + return nil, fmt.Errorf("SearchAllResources: %w", err) + } + err = searchAllResources.AssetTypes(k8sNamespaceAssetType). + PageSize(pageSize). + Pages(ctx, func(sarr *cloudasset.SearchAllResourcesResponse) error { + namespaces = append(namespaces, sarr.Results...) + return nil + }) + log.Debugf("%q fetched %d namespaces", scope, len(namespaces)) + return namespaces, detailedGoogleError(err, "ListNamespaces") +} + +func (api *GCPApiReal) ListPods(ctx context.Context, scope string) (pods []*cloudasset.ResourceSearchResult, err error) { + if scope == "" { + // We don't list namespaces for the whole org, this is a too large amount of data. + return nil, fmt.Errorf("ListPods: name is empty") + } + searchAllResources, err := api.cloudAssetService.SearchAllResources(ctx, scope) + if err != nil { + return nil, fmt.Errorf("SearchAllResources: %w", err) + } + err = searchAllResources.ReadMask("*").AssetTypes(k8sPodAssetType).Context(ctx).Pages(ctx, func(sarr *cloudasset.SearchAllResourcesResponse) error { + for _, r := range sarr.Results { + if r.State == "Running" { + pods = append(pods, r) + } + } + return nil + }) + log.Debugf("%q fetched %d pods", scope, len(pods)) + return pods, detailedGoogleError(err, "ListPods") +} + +func (api *GCPApiReal) ListOrganizationsIamPolicy(ctx context.Context, name string) (*cloudresourcemanager.Policy, error) { + resp, err := api.cloudResourceManagerService.Organizations. + GetIamPolicy(name, new(cloudresourcemanager.GetIamPolicyRequest)). + Context(ctx). + Do() + log.Debugf("fetched organization %s iam policy", name) + return resp, detailedGoogleError(err, "Organizations.GetIamPolicy") +} + +func (api *GCPApiReal) ListProjectIamPolicy(ctx context.Context, name string) (*cloudresourcemanager.Policy, error) { + resp, err := api.cloudResourceManagerService.Projects. + GetIamPolicy(constants.ResourceWithProjectsPrefix(name), &cloudresourcemanager.GetIamPolicyRequest{}). + Context(ctx). + Do() + log.Debugf("fetched project %s iam policy", name) + return resp, detailedGoogleError(err, "Project.GetIamPolicy") +} + +func (api *GCPApiReal) ListRegions(ctx context.Context, name string) (regions []*compute.Region, err error) { + ctx, span := tracer.Start(ctx, "ListRegions") + span.SetAttributes( + attribute.String(constants.TraceKeyName, name), + ) + defer span.End() + err = api.computeService.Regions. + List(constants.ResourceWithoutProjectsPrefix(name)). + Context(ctx). + Pages(ctx, func(rl *compute.RegionList) error { + regions = append(regions, rl.Items...) + return nil + }) + log.Debugf("%s fetched %d regions", name, len(regions)) + return regions, detailedGoogleError(err, "Regions.List") +} + +func (api *GCPApiReal) ListResourceGroups(ctx context.Context, rgNames []string) (resourceGroups []*cloudasset.ResourceSearchResult, err error) { + searchAllResources, err := api.cloudAssetService.SearchAllResources(ctx, api.scope) + if err != nil { + return nil, fmt.Errorf("SearchAllResources: %w", err) + } + call := searchAllResources. + AssetTypes(projectAssetType, folderAssetType, organizationAssetType). + Context(ctx) + rgNamesMap := make(map[string]struct{}) + for _, rgName := range rgNames { + rgNamesMap[rgName] = struct{}{} + } + var query string + if len(rgNames) == 1 { + query = fmt.Sprintf(searchForProject, rgNames[0]) + } else { + query = excludeSysProjectsQuery + } + err = call.Query(query). + Context(ctx). + Pages(ctx, func(sarr *cloudasset.SearchAllResourcesResponse) error { + for _, r := range sarr.Results { + if r.State == "ACTIVE" { + if len(rgNames) > 0 { + if _, ok := rgNamesMap[strings.TrimPrefix(r.Name, cloudResourceManagerPrefix)]; ok { + resourceGroups = append(resourceGroups, r) + } + } else { + resourceGroups = append(resourceGroups, r) + } + } + } + return nil + }) + log.WithField("rg_names", rgNames).Debugf("fetched %d resourcegroups", len(resourceGroups)) + return resourceGroups, detailedGoogleError(err, "ListResourceGroups") +} + +func (api *GCPApiReal) ListSccFindings(ctx context.Context, name string) (findings []*securitycenter.Finding, err error) { + ctx, span := tracer.Start(ctx, "ListSccFindings") + defer span.End() + err = api.securityCenterService.Projects.Sources.Findings.List(name+"/sources/-"). + Context(ctx). + Filter("state=\"ACTIVE\" AND NOT finding_class=\"THREAT\""). + PageSize(sccPageSize). + Pages(ctx, func(flr *securitycenter.ListFindingsResponse) error { + ctx, span := tracer.Start(ctx, "ListSccFindingsPage") + defer span.End() + if err := rlSCC.Wait(ctx); err != nil { + return err + } + for _, fr := range flr.ListFindingsResults { + findings = append(findings, fr.Finding) + } + return nil + }) + return +} + +func (api *GCPApiReal) ListServiceAccount(ctx context.Context, name string) (serviceaccounts []*iam.ServiceAccount, err error) { + err = api.iamService.Projects.ServiceAccounts.List(constants.ResourceWithProjectsPrefix(name)).Context(ctx). + Pages(ctx, func(lsar *iam.ListServiceAccountsResponse) error { + serviceaccounts = append(serviceaccounts, lsar.Accounts...) + return nil + }) + log.Debugf("%s fetched %d service accounts", name, len(serviceaccounts)) + return serviceaccounts, detailedGoogleError(err, "ServiceAccounts.List") +} + +func (api *GCPApiReal) ListServiceAccountKeys(ctx context.Context, name string) (keys []*iam.ServiceAccountKey, err error) { + resp, err := api.iamService.Projects.ServiceAccounts.Keys. + List(constants.ResourceWithProjectsPrefix(name)). + Context(ctx). + Do() + if err != nil { + return nil, detailedGoogleError(err, "ServiceAccounts.Keys.List") + } + keys = append(keys, resp.Keys...) + log.Debugf("%s fetched %d service account keys", name, len(keys)) + return keys, nil +} + +func (api *GCPApiReal) ListServiceAccountKeyUsage(ctx context.Context, resourceGroup string, request *monitoring.QueryTimeSeriesRequest) *monitoring.ProjectsTimeSeriesQueryCall { + return api.monitoringService.Projects.TimeSeries. + Query(constants.ResourceWithProjectsPrefix(resourceGroup), request). + Context(ctx) +} + +func (api *GCPApiReal) ListSpannerDatabases(ctx context.Context, name string) (dbs []*spanner.Database, err error) { + instanceSvc := spanner.NewProjectsInstancesService(api.spannerService) + err = instanceSvc.List(constants.ResourceWithoutProjectsPrefix(name)). + Context(ctx). + Pages(ctx, func(lir *spanner.ListInstancesResponse) error { + for _, instance := range lir.Instances { + databaseSvc := spanner.NewProjectsInstancesDatabasesService(api.spannerService) + return databaseSvc.List(instance.Name).Pages(ctx, func(ldr *spanner.ListDatabasesResponse) error { + dbs = append(dbs, ldr.Databases...) + return nil + }) + } + return nil + }) + log.Debugf("%s fetched %d spanner databases", name, len(dbs)) + return dbs, detailedGoogleError(err, "Spanner.Instances.List") +} + +func (api *GCPApiReal) ListSslPolicies(ctx context.Context, name string) (policies []*compute.SslPolicy, err error) { + err = api.computeService.SslPolicies. + AggregatedList(constants.ResourceWithoutProjectsPrefix(name)). + Context(ctx). + Pages(ctx, func(spal *compute.SslPoliciesAggregatedList) error { + for _, i := range spal.Items { + policies = append(policies, i.SslPolicies...) + } + return nil + }) + log.Debugf("%s fetched %d ssl policies", name, len(policies)) + return policies, detailedGoogleError(err, "SslPolicies,AggregatedList") +} + +func (api *GCPApiReal) ListSubNetworksByRegion(ctx context.Context, name string, region string) (subnetworks []*compute.Subnetwork, err error) { + if err := rlSubnetRanges.Wait(ctx); err != nil { + return nil, fmt.Errorf("snRangesLimiter.Wait: %w", err) + } + err = api.computeService.Subnetworks. + List(constants.ResourceWithoutProjectsPrefix(name), region). + Context(ctx). + Pages(ctx, func(sl *compute.SubnetworkList) error { + subnetworks = append(subnetworks, sl.Items...) + return nil + }) + log.Debugf("%s region %s fetched %d subnetworks", name, region, len(subnetworks)) + return subnetworks, detailedGoogleError(err, "Subnetworks.List") +} + +func (api *GCPApiReal) ListTargetHTTPSProxies(ctx context.Context, name string) (proxies []*compute.TargetHttpsProxy, err error) { + err = api.computeService.TargetHttpsProxies. + AggregatedList(constants.ResourceWithoutProjectsPrefix(name)). + Context(ctx). + Pages(ctx, func(thpal *compute.TargetHttpsProxyAggregatedList) error { + for _, p := range thpal.Items { + proxies = append(proxies, p.TargetHttpsProxies...) + } + return nil + }) + log.Debugf("%s fetched %d https proxies", name, len(proxies)) + return proxies, detailedGoogleError(err, "TargetHttpsProxies.AggregatedList") +} + +func (api *GCPApiReal) ListTargetSslProxies(ctx context.Context, name string) (proxies []*compute.TargetSslProxy, err error) { + err = api.computeService.TargetSslProxies. + List(constants.ResourceWithoutProjectsPrefix(name)). + Context(ctx). + Pages(ctx, func(tspl *compute.TargetSslProxyList) error { + proxies = append(proxies, tspl.Items...) + return nil + }) + log.Debugf("%s fetched %d ssl proxies", name, len(proxies)) + return proxies, detailedGoogleError(err, "TargetSslProxies.List") +} + +func (api *GCPApiReal) ListURLMaps(ctx context.Context, name string) (maps []*compute.UrlMap, err error) { + err = api.computeService.UrlMaps. + AggregatedList(constants.ResourceWithoutProjectsPrefix(name)). + Context(ctx). + Pages(ctx, func(umal *compute.UrlMapsAggregatedList) error { + for _, m := range umal.Items { + maps = append(maps, m.UrlMaps...) + } + return nil + }) + log.Debugf("%s fetched %d url maps", name, len(maps)) + return maps, detailedGoogleError(err, "UrlMaps.AggregatedList") +} + +func (api *GCPApiReal) ListUsersInGroup(ctx context.Context, group string) (groupMembers []string, err error) { + group = strings.TrimPrefix(group, "group:") + if e, ok := listGroupCache[group]; ok && time.Since(e.Creation) < time.Second*300 { + return e.Members, nil + } + groupID, err := api.cloudIdentityService.Groups.Lookup(). + Context(ctx). + GroupKeyId(group). + Do() + if err != nil { + return []string{}, fmt.Errorf("group lookup: %w", err) + } + err = api.cloudIdentityService.Groups.Memberships. + List(groupID.Name). + Context(ctx). + PageSize(pageSize). + Pages(ctx, func(lmr *cloudidentity.ListMembershipsResponse) error { + for _, m := range lmr.Memberships { + switch m.Type { + case "GROUP": + transitiveMembers, err := api.ListUsersInGroup(ctx, m.PreferredMemberKey.Id) + if err != nil { + return err + } + groupMembers = append(groupMembers, transitiveMembers...) + default: + groupMembers = append(groupMembers, m.PreferredMemberKey.Id) + } + } + return nil + }) + log.Debugf("%s fetched %d group members", group, len(groupMembers)) + listGroupCache[group] = GroupCacheEntry{Creation: time.Now(), Members: groupMembers} + return groupMembers, detailedGoogleError(err, "CloudIdentity.Groups.Memberships.List") +} + +func (api *GCPApiReal) ListZones(ctx context.Context, name string) (zones []*compute.Zone, err error) { + err = api.computeService.Zones. + List(constants.ResourceWithoutProjectsPrefix(name)). + Context(ctx). + Pages(ctx, func(zl *compute.ZoneList) error { + zones = append(zones, zl.Items...) + return nil + }) + log.Debugf("%s fetched %d zones", name, len(zones)) + return zones, detailedGoogleError(err, "Zones.List") +} + +func (api *GCPApiReal) SearchIamPolicy(ctx context.Context, scope string, query string) (iamPolicies []*cloudasset.IamPolicySearchResult, err error) { + attemptNumber := 1 + var waitSec float64 + start := time.Now() + for { + // TODO: Change scope to api.scope and get rid of the scope parameter + searchAllIamPolicies, err := api.cloudAssetService.SearchAllIamPolicies(ctx, scope) + if err != nil { + return nil, fmt.Errorf("SearchAllIamPolicies: %w", err) + } + err = searchAllIamPolicies. + Query(query). + Context(ctx). + Pages(ctx, func(resp *cloudasset.SearchAllIamPoliciesResponse) error { + iamPolicies = append(iamPolicies, resp.Results...) + return nil + }) + if err == nil { + break + } + if time.Since(start) > maxSecAcrossAttempts { + return nil, fmt.Errorf("SearchIamPolicy.tooLong: after %v seconds %w", time.Since(start), detailedGoogleError(err, fmt.Sprintf("IamPolicies.SearchAll.Query %q", query))) + } + if attemptNumber >= maxAttemptNumber { + return nil, fmt.Errorf("SearchIamPolicy.maxAttempt: after %v attempts %w", attemptNumber, detailedGoogleError(err, fmt.Sprintf("IamPolicies.SearchAll.Query %q", query))) + } + if !isErrorCodeRetryable(getErrorCode(err)) { + return nil, fmt.Errorf("SearchIamPolicy.notRetryableCode: %w", detailedGoogleError(err, fmt.Sprintf("IamPolicies.SearchAll.Query %q", query))) + } + waitSec = math.Min(math.Pow(2, float64(attemptNumber))+rand.Float64(), maxSecBtwAttempts) //nolint:mnd,gosec + time.Sleep(time.Duration(waitSec) * time.Second) + attemptNumber++ + } + log.Debugf("fetched %d iam policies", len(iamPolicies)) + return iamPolicies, nil +} diff --git a/src/collector/gcpcollector/api_key.go b/src/collector/gcpcollector/api_key.go new file mode 100644 index 0000000..13d88c3 --- /dev/null +++ b/src/collector/gcpcollector/api_key.go @@ -0,0 +1,52 @@ +package gcpcollector + +import ( + "fmt" + + "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" + pb "github.com/nianticlabs/modron/src/proto/generated" + + "golang.org/x/net/context" + "google.golang.org/api/apikeys/v2" +) + +const ( + globalProjectResourceID = "%s/locations/global" +) + +func (collector *GCPCollector) ListAPIKeys(ctx context.Context, rgName string) (apiKeys []*pb.Resource, err error) { + name := fmt.Sprintf(globalProjectResourceID, constants.ResourceWithProjectsPrefix(rgName)) + + keys, err := collector.api.ListAPIKeys(ctx, name) + if err != nil { + return nil, err + } + for _, key := range keys { + // TODO : handle other types of GCP API keys restrictions + // example : BrowserKeyRestrictions , AndroidKeyRestrictions , etc.. + scopes := getAPIKeyScopes(key) + apiKeys = append(apiKeys, &pb.Resource{ + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Name: key.Name, + Parent: rgName, + Type: &pb.Resource_ApiKey{ + ApiKey: &pb.APIKey{ + Scopes: scopes, + }, + }, + }) + } + return apiKeys, err +} + +func getAPIKeyScopes(key *apikeys.V2Key) (scopes []string) { + if key.Restrictions == nil || key.Restrictions.ApiTargets == nil { + return nil + } + for _, apiTarget := range key.Restrictions.ApiTargets { + scopes = append(scopes, apiTarget.Service) + } + return scopes +} diff --git a/src/collector/gcpcollector/bucket.go b/src/collector/gcpcollector/bucket.go index 29ec108..9138cfd 100644 --- a/src/collector/gcpcollector/bucket.go +++ b/src/collector/gcpcollector/bucket.go @@ -7,7 +7,7 @@ import ( "time" "github.com/nianticlabs/modron/src/common" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" "golang.org/x/net/context" "google.golang.org/protobuf/types/known/durationpb" @@ -29,13 +29,12 @@ func getAccessType(members []string) pb.Bucket_AccessType { } // TODO: Check the ACL to detect if the bucket is public if uniform bucket-level access is disabled. -func (collector *GCPCollector) ListBuckets(ctx context.Context, resourceGroup *pb.Resource) ([]*pb.Resource, error) { - res, err := collector.api.ListBuckets(ctx, resourceGroup.Name) +func (collector *GCPCollector) ListBuckets(ctx context.Context, rgName string) (buckets []*pb.Resource, err error) { + res, err := collector.api.ListBuckets(ctx, rgName) if err != nil { return nil, err } - buckets := []*pb.Resource{} removeDefaultBindings := func(members []string) (filteredList []string) { for _, member := range members { if strings.HasPrefix(member, "projectViewer:") || strings.HasPrefix(member, "projectOwner:") || strings.HasPrefix(member, "projectEditor:") { @@ -52,7 +51,7 @@ func (collector *GCPCollector) ListBuckets(ctx context.Context, resourceGroup *p } accessType := pb.Bucket_ACCESS_UNKNOWN - permissions := []*pb.Permission{} + var permissions []*pb.Permission for _, binding := range iamPolicy.Bindings { bindingMembers := removeDefaultBindings(binding.Members) permissions = append(permissions, &pb.Permission{ @@ -91,10 +90,10 @@ func (collector *GCPCollector) ListBuckets(ctx context.Context, resourceGroup *p } } buckets = append(buckets, &pb.Resource{ - Uid: common.GetUUID(3), - ResourceGroupName: resourceGroup.Name, - Name: formatResourceName(bucket.Name, bucket.Id), - Parent: resourceGroup.Name, + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Name: bucket.Name, + Parent: rgName, IamPolicy: &pb.IamPolicy{ Permissions: permissions, }, diff --git a/src/collector/gcpcollector/cloudsql.go b/src/collector/gcpcollector/cloudsql.go index a720444..97db54d 100644 --- a/src/collector/gcpcollector/cloudsql.go +++ b/src/collector/gcpcollector/cloudsql.go @@ -1,24 +1,25 @@ package gcpcollector import ( + sqladmin "google.golang.org/api/sqladmin/v1beta4" + "github.com/nianticlabs/modron/src/common" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" "golang.org/x/net/context" ) -func (collector *GCPCollector) ListCloudSqlDatabases(ctx context.Context, resourceGroup *pb.Resource) ([]*pb.Resource, error) { - dbs, err := collector.api.ListCloudSqlDatabases(ctx, resourceGroup.Name) +func (collector *GCPCollector) ListCloudSQLDatabases(ctx context.Context, rgName string) (resources []*pb.Resource, err error) { + dbs, err := collector.api.ListCloudSQLDatabases(ctx, rgName) if err != nil { return nil, err } - resources := []*pb.Resource{} for _, instance := range dbs { dbResource := &pb.Resource{ - Uid: common.GetUUID(3), - ResourceGroupName: resourceGroup.Name, + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, Name: instance.Name, - Parent: resourceGroup.Name, + Parent: rgName, Type: &pb.Resource_Database{ Database: &pb.Database{ Type: "cloudsql", @@ -28,47 +29,53 @@ func (collector *GCPCollector) ListCloudSqlDatabases(ctx context.Context, resour }, } if instance.Settings != nil { - if instance.Settings.IpConfiguration != nil { - dbResource.GetDatabase().TlsRequired = instance.Settings.IpConfiguration.RequireSsl - if instance.Settings.IpConfiguration.AuthorizedNetworks != nil { - dbResource.GetDatabase().AuthorizedNetworksSettingAvailable = pb.Database_AUTHORIZED_NETWORKS_SET - authorizedNetworks := []string{} - for _, n := range instance.Settings.IpConfiguration.AuthorizedNetworks { - authorizedNetworks = append(authorizedNetworks, n.Value) - } - dbResource.GetDatabase().AuthorizedNetworks = authorizedNetworks - } else { - dbResource.GetDatabase().AuthorizedNetworksSettingAvailable = pb.Database_AUTHORIZED_NETWORKS_NOT_SET - } - if instance.Settings.IpConfiguration.Ipv4Enabled { - dbResource.GetDatabase().IsPublic = true - } - } - if instance.Settings.StorageAutoResize != nil { - dbResource.GetDatabase().AutoResize = *instance.Settings.StorageAutoResize - } - if instance.Settings.BackupConfiguration != nil { - dbResource.GetDatabase().BackupConfig = pb.Database_BACKUP_CONFIG_MANAGED - } else { - dbResource.GetDatabase().BackupConfig = pb.Database_BACKUP_CONFIG_DISABLED - } - switch instance.Settings.AvailabilityType { - case "ZONAL": - dbResource.GetDatabase().AvailabilityType = pb.Database_HA_ZONAL - case "REGIONAL": - dbResource.GetDatabase().AvailabilityType = pb.Database_HA_REGIONAL - default: - dbResource.GetDatabase().AvailabilityType = pb.Database_HA_UNKNOWN - } + setDbResourceSettings(instance, dbResource) } if instance.DiskEncryptionStatus != nil { dbResource.GetDatabase().Encryption = pb.Database_ENCRYPTION_USER_MANAGED } else { dbResource.GetDatabase().Encryption = pb.Database_ENCRYPTION_MANAGED } - resources = append(resources, dbResource) } return resources, nil } + +func setDbResourceSettings(instance *sqladmin.DatabaseInstance, dbResource *pb.Resource) { + db := dbResource.GetDatabase() + settings := instance.Settings + ipConfig := settings.IpConfiguration + if ipConfig != nil { + db.TlsRequired = ipConfig.RequireSsl + if ipConfig.AuthorizedNetworks == nil { + db.AuthorizedNetworksSettingAvailable = pb.Database_AUTHORIZED_NETWORKS_NOT_SET + } else { + db.AuthorizedNetworksSettingAvailable = pb.Database_AUTHORIZED_NETWORKS_SET + var authorizedNetworks []string + for _, n := range ipConfig.AuthorizedNetworks { + authorizedNetworks = append(authorizedNetworks, n.Value) + } + db.AuthorizedNetworks = authorizedNetworks + } + if ipConfig.Ipv4Enabled { + db.IsPublic = true + } + } + if settings.StorageAutoResize != nil { + db.AutoResize = *settings.StorageAutoResize + } + if settings.BackupConfiguration != nil { + db.BackupConfig = pb.Database_BACKUP_CONFIG_MANAGED + } else { + db.BackupConfig = pb.Database_BACKUP_CONFIG_DISABLED + } + switch settings.AvailabilityType { + case "ZONAL": + db.AvailabilityType = pb.Database_HA_ZONAL + case "REGIONAL": + db.AvailabilityType = pb.Database_HA_REGIONAL + default: + db.AvailabilityType = pb.Database_HA_UNKNOWN + } +} diff --git a/src/collector/gcpcollector/collector.go b/src/collector/gcpcollector/collector.go new file mode 100644 index 0000000..ec1f3b7 --- /dev/null +++ b/src/collector/gcpcollector/collector.go @@ -0,0 +1,367 @@ +package gcpcollector + +import ( + "errors" + "fmt" + "reflect" + "regexp" + "runtime" + "strings" + "sync" + "time" + + "github.com/sirupsen/logrus" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "golang.org/x/net/context" + "golang.org/x/sync/errgroup" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/utils" +) + +const ( + maxParallelCollections = 50 + uuidGenRetries = 3 +) + +var ( + log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "gcpcollector") + sysGcpProjectRegex = regexp.MustCompile("^sys-[0-9]+") + tracer = otel.Tracer("github.com/nianticlabs/modron/src/collector/gcpcollector") + validGcpResourceGroupRegex = regexp.MustCompile(`((organizations|folders)/\d+|(projects/[-a-z0-9.:]+))`) +) + +type GCPCollector struct { + allowedSccCategories map[string]struct{} + additionalAdminRolesMap map[constants.Role]struct{} + api GCPApi + orgID string + orgSuffix string + storage model.Storage + tagConfig risk.TagConfig + + metrics metrics +} + +func (collector *GCPCollector) CollectAndStoreAll(ctx context.Context, collectID string, resourceGroupNames []string, preCollectedRgs []*pb.Resource) error { + ctx, span := tracer.Start(ctx, "CollectAndStoreAll") + defer span.End() + resourceGroupNames = filterValidResourceGroupNames(resourceGroupNames) + collectAndStoreSemaphore := make(chan struct{}, maxParallelCollections) + errGroup := new(errgroup.Group) + for _, rgName := range resourceGroupNames { + collectAndStoreSemaphore <- struct{}{} + errGroup.Go(func() error { + ctx, span := tracer.Start(ctx, "CollectAndStoreAllInRg", + trace.WithNewRoot(), + trace.WithLinks(trace.Link{SpanContext: trace.SpanContextFromContext(ctx)}), + trace.WithAttributes(attribute.String(constants.TraceKeyResourceGroup, rgName)), + ) + defer span.End() + defer func() { <-collectAndStoreSemaphore }() + log := log.WithField("resource_group", rgName) + if err := collector.collectAndStoreAllInRg(ctx, collectID, rgName, preCollectedRgs); err != nil { + span.RecordError(err) + log.WithError(err).Errorf("Failed to collect and store for resource group %s: %v", rgName, err) + return err + } + return nil + }) + } + return errGroup.Wait() +} + +func New( + ctx context.Context, + storage model.Storage, + orgID string, + orgSuffix string, + additionalAdminRoles []string, + tagConfig risk.TagConfig, + allowedSccCategories []string, +) (model.Collector, error) { + if strings.HasPrefix(orgID, constants.GCPOrgIDPrefix) { + strippedOrgID := strings.TrimPrefix(orgID, constants.GCPOrgIDPrefix) + log.Warnf("orgID \"%s\" is deprecated, use \"%s\" instead", orgID, strippedOrgID) + orgID = strippedOrgID + } + api, err := NewGCPApiReal(ctx, orgID) + if err != nil { + return nil, fmt.Errorf("container.NewService: %w", err) + } + m := initMetrics() + additionalAdminRolesMap := map[constants.Role]struct{}{} + for _, role := range additionalAdminRoles { + additionalAdminRolesMap[constants.ToRole(role)] = struct{}{} + } + allowedSccCategoriesMap := map[string]struct{}{} + for _, category := range allowedSccCategories { + allowedSccCategoriesMap[category] = struct{}{} + } + return &GCPCollector{ + allowedSccCategories: allowedSccCategoriesMap, + api: api, + additionalAdminRolesMap: additionalAdminRolesMap, + storage: storage, + orgID: orgID, + orgSuffix: orgSuffix, + metrics: m, + tagConfig: tagConfig, + }, nil +} + +func filterValidResourceGroupNames(resourceGroupNames []string) (filteredNames []string) { + for _, name := range resourceGroupNames { + if sysGcpProjectRegex.MatchString(name) { + continue + } + if !validGcpResourceGroupRegex.MatchString(name) { + log.Warnf("invalid resource group name: %q", name) + continue + } + filteredNames = append(filteredNames, name) + } + return filteredNames +} + +// collectAndStoreResources should not be used directly, use collectAndStoreAllInRg instead +func (collector *GCPCollector) collectAndStoreResources(ctx context.Context, collectID string, rgName string) []error { + ctx, span := tracer.Start(ctx, "collectAndStoreResources") + span.SetAttributes( + attribute.String(constants.TraceKeyCollectID, collectID), + attribute.String(constants.TraceKeyResourceGroup, rgName), + ) + defer span.End() + rg, err := collector.GetResourceGroupWithIamPolicy(ctx, collectID, rgName) + if err != nil { + return []error{err} + } + resources, errArr := collector.ListResourceGroupResources(ctx, collectID, rgName) + resources = append(resources, rg) + if _, err := collector.storage.BatchCreateResources(ctx, resources); err != nil { + errArr = append(errArr, err) + } + log.Infof("%s found %+v resources", rgName, len(resources)) + return errArr +} + +func (collector *GCPCollector) collectAndStoreObservations(ctx context.Context, collectID string, rgName string, preCollectedRgs []*pb.Resource) []error { + ctx, span := tracer.Start(ctx, "collectAndStoreObservations") + span.SetAttributes( + attribute.String(constants.TraceKeyCollectID, collectID), + attribute.String(constants.TraceKeyResourceGroup, rgName), + ) + defer span.End() + rg, err := collector.GetResourceGroupWithIamPolicy(ctx, collectID, rgName) + if err != nil { + return []error{err} + } + obs, errArr := collector.ListResourceGroupObservations(ctx, collectID, rgName) + if len(errArr) > 0 { + return errArr + } + + rgHierarchy, err := utils.ComputeRgHierarchy(preCollectedRgs) + if err != nil { + return []error{fmt.Errorf("computeRgHierarchy: %w", err)} + } + + impact, reason := risk.GetImpact(collector.tagConfig, rgHierarchy, rgName) + for k, o := range obs { + obs[k].Impact = impact + obs[k].ImpactReason = reason + obs[k].RiskScore = risk.GetRiskScore(impact, o.Severity) + } + if _, err = collector.storage.BatchCreateObservations(ctx, obs); err != nil { + errArr = append(errArr, err) + } + log.Infof("%s found %+v observations", rg.Name, len(obs)) + collector.logCollectionStatus(ctx, collectID, rgName, pb.Operation_COMPLETED, "") + return errArr +} + +func (collector *GCPCollector) ListResourceGroupResources(ctx context.Context, collectID string, rgName string) ([]*pb.Resource, []error) { + ctx, span := tracer.Start(ctx, "ListResourceGroupResources") + defer span.End() + span.SetAttributes( + attribute.String(constants.TraceKeyCollectID, collectID), + attribute.String(constants.TraceKeyResourceGroup, rgName), + ) + projectCollectors := []GenericCollector[*pb.Resource]{ + collector.ListAPIKeys, + collector.ListBuckets, + collector.ListCloudSQLDatabases, + collector.ListKubernetesClusters, + collector.ListKubernetesNamespaces, + collector.ListKubernetesPods, + collector.ListLoadBalancers, + collector.ListNetworks, + collector.ListServiceAccounts, + collector.ListSpannerDatabases, + collector.ListVMInstances, + } + // TODO: Add organization collectors, if needed + var organizationCollectors []GenericCollector[*pb.Resource] + + collectors, errArr := chooseCollectors(rgName, projectCollectors, organizationCollectors) + var res []*pb.Resource + resMutex := sync.Mutex{} + errArrMutex := sync.Mutex{} + wg := sync.WaitGroup{} + for _, collector := range collectors { + wg.Add(1) + go func() { + ctx, span := tracer.Start(ctx, "RunCollector") + defer wg.Done() + defer span.End() + collValue := reflect.ValueOf(collector) + functionName := runtime.FuncForPC(collValue.Pointer()).Name() + log := log.WithField(constants.LogKeyCollector, functionName) + span.SetAttributes( + attribute.String(constants.TraceKeyCollector, functionName), + ) + collectedResources, err := collector.ExponentialBackoffRun(ctx, rgName) + if err != nil { + log.WithError(err).Errorf("ExponentialBackoffRun: %v", err) + span.RecordError(err) + span.SetStatus(codes.Error, "ExponentialBackoffRun failed") + errArrMutex.Lock() + errArr = append(errArr, err) + errArrMutex.Unlock() + } + for _, r := range collectedResources { + r.CollectionUid = collectID + r.Timestamp = timestamppb.Now() + resMutex.Lock() + res = append(res, r) + resMutex.Unlock() + } + }() + } + wg.Wait() + return res, errArr +} + +func (collector *GCPCollector) ListResourceGroupObservations(ctx context.Context, collectID string, rgName string) (obs []*pb.Observation, errArr []error) { + projectCollectors := []GenericCollector[*pb.Observation]{ + collector.ListSccFindings, + } + var organizationCollectors []GenericCollector[*pb.Observation] + var collectors []GenericCollector[*pb.Observation] + collectors, errArr = chooseCollectors(rgName, projectCollectors, organizationCollectors) + for _, collector := range collectors { + cLogger := log. + WithFields(logrus.Fields{ + constants.LogKeyCollector: fmt.Sprintf("%T", collector), + constants.LogKeyResourceGroup: rgName, + }) + cLogger.Info("Collecting observations") + collectedObs, err := collector.ExponentialBackoffRun(ctx, rgName) + if err != nil { + errArr = append(errArr, err) + cLogger.WithError(err).Errorf("Failed to collect some observations") + } else { + for _, o := range collectedObs { + o.CollectionId = utils.RefOrNull(collectID) + if o.Timestamp == nil { + o.Timestamp = timestamppb.Now() + } + obs = append(obs, o) + } + } + cLogger.Infof("Collection complete") + } + return +} + +func chooseCollectors[T any]( + rgName string, + projectCollectors []GenericCollector[T], + orgCollectors []GenericCollector[T], +) (collectors []GenericCollector[T], errors []error) { + switch { + case strings.HasPrefix(rgName, constants.GCPFolderIDPrefix): + collectors = []GenericCollector[T]{} + case strings.HasPrefix(rgName, constants.GCPOrgIDPrefix): + collectors = orgCollectors + case strings.HasPrefix(rgName, constants.GCPProjectsNamePrefix): + collectors = projectCollectors + default: + errors = append(errors, fmt.Errorf("no collectors for %q", rgName)) + return nil, errors + } + return +} + +func (collector *GCPCollector) logCollectionStatus(ctx context.Context, collectID, resourceGroupName string, status pb.Operation_Status, reason string) { + ctx, span := tracer.Start(ctx, "logCollectionStatus") + defer span.End() + log. + WithField(constants.LogKeyCollectID, collectID). + WithField(constants.LogKeyResourceGroup, resourceGroupName). + Infof("Logging collection status: %s", status.String()) + if err := collector.storage.AddOperationLog(ctx, + []*pb.Operation{{ + Id: collectID, + ResourceGroup: resourceGroupName, + Type: "collection", + StatusTime: timestamppb.New(time.Now()), + Status: status, + Reason: reason, + }}); err != nil { + span.RecordError(err) + log.Warnf("add operation log: %v", err) + } +} + +func (collector *GCPCollector) collectAndStoreAllInRg(ctx context.Context, collectID string, rgName string, preCollectedRgs []*pb.Resource) error { + ctx, span := tracer.Start(ctx, "collectAndStoreAllInRg") + defer span.End() + span.SetAttributes( + attribute.String(constants.TraceKeyCollectID, collectID), + attribute.String(constants.TraceKeyResourceGroup, rgName), + ) + collector.logCollectionStatus(ctx, collectID, rgName, pb.Operation_STARTED, "") + collectLogger := log. + WithFields(logrus.Fields{ + constants.LogKeyCollectID: collectID, + constants.LogKeyResourceGroup: rgName, + }) + errGroup := new(errgroup.Group) + errGroup.Go(func() error { + collectLogger.Infof("Starting collect resources") + collectErrs := collector.collectAndStoreResources(ctx, collectID, rgName) + collectLogger.WithError(errors.Join(collectErrs...)).Infof("Done collecting resources") + return errors.Join(collectErrs...) + }) + + errGroup.Go(func() error { + collectLogger.Infof("Starting collect observations") + collectErrs := collector.collectAndStoreObservations(ctx, collectID, rgName, preCollectedRgs) + collectLogger.WithError(errors.Join(collectErrs...)).Infof("Done collecting observations") + return errors.Join(collectErrs...) + }) + flushOps := func() { + // Flush the ops log after the collection is done + if err := collector.storage.FlushOpsLog(ctx); err != nil { + // If we cannot flush the ops log, it's not a big deal: + // the next time Modron starts, the pending operations are marked as complete. + collectLogger.Warnf("flush ops log: %v", err) + } + } + defer flushOps() + if err := errGroup.Wait(); err != nil { + collectLogger.Errorf("collectAndStoreAllInRg: %v", err) + collector.logCollectionStatus(ctx, collectID, rgName, pb.Operation_FAILED, err.Error()) + return err + } + collector.logCollectionStatus(ctx, collectID, rgName, pb.Operation_COMPLETED, "") + return nil +} diff --git a/src/collector/gcpcollector/collector_call.go b/src/collector/gcpcollector/collector_call.go new file mode 100644 index 0000000..8a2dadf --- /dev/null +++ b/src/collector/gcpcollector/collector_call.go @@ -0,0 +1,52 @@ +package gcpcollector + +import ( + "errors" + "time" + + "google.golang.org/api/googleapi" +) + +const ( + maxAttemptNumber = 100 + maxSecBtwAttempts = 30. + maxSecAcrossAttempts = time.Duration(3600 * time.Second) +) + +var ( + // Retry following errors: + // * 408: Request timeout + // * 429: Too many requests + // * 5XX: Server errors + retryableErrorCode = []int{408, 429, 500, 502, 503, 504} + // We are not interested in the following codes: + // * 403: Sometimes returned for non-existing resources. + // * 404: A resource can be tracked by modron and then deleted. + skippableErrorCodes = []int{403, 404} +) + +func getErrorCode(err error) int { + var e *googleapi.Error + if errors.As(err, &e) { + return e.Code + } + return 0 +} + +func isErrorCodeRetryable(errorCode int) bool { + for _, code := range retryableErrorCode { + if code == errorCode { + return true + } + } + return false +} + +func isErrorCodeSkippable(errorCode int) bool { + for _, code := range skippableErrorCodes { + if code == errorCode { + return true + } + } + return false +} diff --git a/src/collector/gcpcollector/collector_generic.go b/src/collector/gcpcollector/collector_generic.go new file mode 100644 index 0000000..41808dc --- /dev/null +++ b/src/collector/gcpcollector/collector_generic.go @@ -0,0 +1,43 @@ +package gcpcollector + +import ( + "context" + "fmt" + "math" + "math/rand" + "time" +) + +type GenericCollector[T any] func(ctx context.Context, rgName string) ([]T, error) + +func (call GenericCollector[T]) ExponentialBackoffRun(ctx context.Context, rgName string) ([]T, error) { + attemptNumber := 1 + var waitSec float64 + start := time.Now() + for { + collected, err := call.Run(ctx, rgName) + if err == nil { + return collected, nil + } + if !isErrorCodeRetryable(getErrorCode(err)) { + return nil, fmt.Errorf("ExponentialBackoffRun.notRetryableCode: %w ", err) + } + if time.Since(start) > maxSecAcrossAttempts { + return nil, fmt.Errorf("ExponentialBackoffRun.tooLong: after %v seconds %w ", time.Since(start), err) + } + if attemptNumber >= maxAttemptNumber { + return nil, fmt.Errorf("ExponentialBackoffRun.maxAttempt: after %v attempts %w ", attemptNumber, err) + } + waitSec = math.Min(math.Pow(2, float64(attemptNumber))+rand.Float64(), maxSecBtwAttempts) //nolint:mnd,gosec + time.Sleep(time.Duration(waitSec) * time.Second) + attemptNumber++ + } +} + +func (call GenericCollector[T]) Run(ctx context.Context, rgName string) ([]T, error) { + resources, err := call(ctx, rgName) + if isErrorCodeSkippable(getErrorCode(err)) { + return []T{}, nil + } + return resources, err +} diff --git a/src/collector/gcpcollector/collector_integration_test.go b/src/collector/gcpcollector/collector_integration_test.go new file mode 100644 index 0000000..4c53589 --- /dev/null +++ b/src/collector/gcpcollector/collector_integration_test.go @@ -0,0 +1,94 @@ +//go:build integration + +package gcpcollector + +import ( + "context" + "encoding/json" + "os" + "testing" + + "github.com/google/uuid" + "github.com/sirupsen/logrus" + "google.golang.org/protobuf/proto" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/storage/memstorage" +) + +func getCollector(ctx context.Context, t *testing.T) (model.Collector, model.Storage) { + storage := memstorage.New() + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + logrus.StandardLogger().SetFormatter(&logrus.TextFormatter{ + ForceColors: true, + }) + + orgID := os.Getenv("ORG_ID") + if orgID == "" { + t.Fatalf("ORG_ID is empty") + } + orgSuffix := os.Getenv("ORG_SUFFIX") + if orgSuffix == "" { + t.Fatalf("ORG_SUFFIX is empty") + } + tagConfig := risk.TagConfig{} + coll, err := New(ctx, storage, orgID, orgSuffix, []string{}, tagConfig, []string{}) + if err != nil { + t.Fatalf("failed to create collector: %v", err) + } + return coll, storage +} + +func TestGetResourceGroups(t *testing.T) { + ctx := context.Background() + coll, _ := getCollector(ctx, t) + rgs, err := coll.ListResourceGroups(ctx, nil) + if err != nil { + t.Fatalf("failed to list resource groups: %v", err) + } + if len(rgs) == 0 { + t.Fatalf("no resource groups found") + } + t.Logf("rg=%+v", rgs) +} + +func TestGetSpecificResourceGroups(t *testing.T) { + ctx := context.Background() + coll, _ := getCollector(ctx, t) + rgs, err := coll.ListResourceGroups(ctx, []string{ + "projects/modron-dev", + "projects/modron", + }) + if err != nil { + t.Fatalf("failed to list resource groups: %v", err) + } + if len(rgs) != 2 { + t.Fatalf("expected 2 resource groups, got %d", len(rgs)) + } + t.Logf("rg=%+v", rgs) +} + +func TestCollect(t *testing.T) { + ctx := context.Background() + coll, storage := getCollector(ctx, t) + collectID := uuid.NewString() + err := coll.CollectAndStoreAll(ctx, collectID, []string{"projects/modron-dev"}, []*pb.Resource{}) + if err != nil { + t.Fatalf("failed to collect: %v", err) + } + + resources, err := storage.GetChildrenOfResource( + ctx, collectID, "", proto.String("ResourceGroup"), + ) + if err != nil { + t.Fatalf("GetChildrenOfResource: %v", err) + } + encoded, err := json.Marshal(resources) + if err != nil { + t.Fatalf("failed to marshal resources: %v", err) + } + + t.Logf("resources=%s", encoded) +} diff --git a/src/collector/gcpcollector/collector_rg_call.go b/src/collector/gcpcollector/collector_rg_call.go new file mode 100644 index 0000000..a24ace4 --- /dev/null +++ b/src/collector/gcpcollector/collector_rg_call.go @@ -0,0 +1,43 @@ +package gcpcollector + +import ( + "context" + "fmt" + "math" + "math/rand" + "time" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +// TODO: find a way to avoid code duplication: +// (the implementation of CollectorResourceGroupCall.ExponentialBackoffRun is very similar to GenericCollector.ExponentialBackoffRun) +type CollectorResourceGroupCall func(ctx context.Context, collectID string, rgName string) (*pb.Resource, error) + +func (call CollectorResourceGroupCall) ExponentialBackoffRun(ctx context.Context, collectID string, rgName string) (*pb.Resource, error) { + attemptNumber := 0 + var waitSec float64 + start := time.Now() + for { + resources, err := call.Run(ctx, collectID, rgName) + if err == nil { + return resources, nil + } + if !isErrorCodeRetryable(getErrorCode(err)) { + return nil, fmt.Errorf("ExponentialBackoffRun.notRetryableCode: %w ", err) + } + if time.Since(start) > maxSecAcrossAttempts { + return nil, fmt.Errorf("ExponentialBackoffRun.tooLong: after %v seconds %w", time.Since(start), err) + } + if attemptNumber >= maxAttemptNumber { + return nil, fmt.Errorf("ExponentialBackoffRun.maxAttempt: after %v attempts %w", attemptNumber, err) + } + waitSec = math.Min(math.Pow(2, float64(attemptNumber))+rand.Float64(), maxSecBtwAttempts) //nolint:mnd,gosec + time.Sleep(time.Duration(waitSec) * time.Second) + attemptNumber++ + } +} + +func (call CollectorResourceGroupCall) Run(ctx context.Context, collectID string, rgName string) (*pb.Resource, error) { + return call(ctx, collectID, rgName) +} diff --git a/src/collector/gcpcollector/collector_test.go b/src/collector/gcpcollector/collector_test.go new file mode 100644 index 0000000..30b799b --- /dev/null +++ b/src/collector/gcpcollector/collector_test.go @@ -0,0 +1,482 @@ +package gcpcollector + +import ( + "context" + "flag" + "reflect" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/sirupsen/logrus" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/storage/memstorage" +) + +var ( + collectorTestProjectID string + projectListFile string +) + +func init() { + flag.StringVar(&collectorTestProjectID, "projectId", testProjectID, "GCP project Id") + flag.StringVar(&projectListFile, "projectIdList", "resourceGroupList.txt", "GCP project Id list") +} + +const ( + testProjectID = "projects/modron-test" + collectID = "collectID-1" +) + +func TestResourceGroupResources(t *testing.T) { + ctx := context.Background() + storage := memstorage.New() + gcpCollector := NewFake(ctx, storage, risk.TagConfig{}) + + resourceGroup, err := gcpCollector.GetResourceGroupWithIamPolicy(ctx, collectID, testProjectID) + if err != nil { + t.Fatalf("No resourceGroup found: %v", err) + } + if resourceGroup.CollectionUid != collectID { + t.Errorf("wrong collectUid, want %v got %v", collectID, resourceGroup.CollectionUid) + } + + resourcesCollected, errArr := gcpCollector.ListResourceGroupResources(ctx, collectID, resourceGroup.Name) + for _, err := range errArr { + t.Errorf("%v", err) + } + + for _, r := range resourcesCollected { + if r.CollectionUid != collectID { + t.Errorf("wrong collectUid, want %v got %v", collectID, r.CollectionUid) + } + } + + wantResourcesCollected := 28 // TODO: Create a better test for this functionality + if len(resourcesCollected) != wantResourcesCollected { + t.Errorf("resources collected: got %d, want %d", len(resourcesCollected), wantResourcesCollected) + } +} + +func TestResourceGroup(t *testing.T) { + ctx := context.Background() + storage := memstorage.New() + gcpCollector := NewFake(ctx, storage, risk.TagConfig{}) + resourceGroup, err := gcpCollector.GetResourceGroupWithIamPolicy(ctx, collectID, testProjectID) + if err != nil { + t.Fatalf("No resourceGroup found: %v", err) + } + + if resourceGroup.Name != testProjectID { + t.Errorf("wrong resourceGroup Name: %v", resourceGroup.Name) + } + if len(resourceGroup.IamPolicy.Permissions) != 6 { + t.Errorf("iam policy count: got %d, want %d", len(resourceGroup.IamPolicy.Permissions), 5) + } + if resourceGroup.CollectionUid != collectID { + t.Errorf("wrong collectUid, want %v got %v", collectID, resourceGroup.CollectionUid) + } +} + +func modronTestResource(name string) *pb.Resource { + return &pb.Resource{ + Name: name, + Parent: "projects/modron-test", + ResourceGroupName: "projects/modron-test", + } +} + +func TestCollectAndStore(t *testing.T) { + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + logrus.StandardLogger().SetFormatter(&logrus.TextFormatter{ForceColors: true}) + ctx := context.Background() + storage := memstorage.New() + gcpCollector := NewFake(ctx, storage, risk.TagConfig{}) + limitFilter := model.StorageFilter{ + Limit: 100, + } + + for _, testResourceID := range []string{"organizations/1111", testProjectID} { + err := gcpCollector.collectAndStoreAllInRg(ctx, collectID, testResourceID, nil) + if err != nil { + t.Errorf("collectAndStoreResources(ctx, %s, %s): %v", collectID, testResourceID, err) + } + } + + if err := storage.FlushOpsLog(ctx); err != nil { + t.Errorf("flush ops log: %v", err) + } + + got, err := storage.ListResources(ctx, limitFilter) + if err != nil { + t.Errorf("error storing resources: %v", err) + } + + for _, r := range got { + if r.CollectionUid != collectID { + t.Errorf("wrong collectUid, want %v got %v", collectID, r.CollectionUid) + } + } + + want := []*pb.Resource{ + { + Name: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace", + Parent: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster", + ResourceGroupName: "projects/modron-test", + }, + { + Name: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace-other", + Parent: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster", + ResourceGroupName: "projects/modron-test", + }, + modronTestResource("api-key-unrestricted-0"), + modronTestResource("api-key-unrestricted-1"), + modronTestResource("api-key-with-overbroad-scope-1"), + modronTestResource("api-key-without-overbroad-scope"), + modronTestResource("backend-svc-external-modern"), + modronTestResource("backend-svc-external-no-modern"), + modronTestResource("backend-svc-iap"), + modronTestResource("backend-svc-internal"), + modronTestResource("backend-svc-no-iap"), + { + Name: "bucket-2", + Parent: "projects/modron-test", + ResourceGroupName: "projects/modron-test", + IamPolicy: &pb.IamPolicy{ + Permissions: []*pb.Permission{ + { + Role: "storage.objectViewer", + Principals: []string{ + "serviceAccount:account-1@modron-test.iam.gserviceaccount.com", + }, + }, + { + Role: "storage.objectViewer", + Principals: []string{ + "serviceAccount:account-2@modron-test.iam.gserviceaccount.com", + }, + }, + }, + }, + }, + { + Name: "bucket-accessible-from-other-project", + Parent: "projects/modron-test", + ResourceGroupName: "projects/modron-test", + IamPolicy: &pb.IamPolicy{ + Permissions: []*pb.Permission{ + { + Role: "storage.legacyBucketOwner", + Principals: []string{ + "serviceAccount:account-3@modron-other-test.iam.gserviceaccount.com", + }, + }, + }, + }, + }, + { + Name: "bucket-public", + Parent: "projects/modron-test", + ResourceGroupName: "projects/modron-test", + IamPolicy: &pb.IamPolicy{ + Permissions: []*pb.Permission{ + { + Role: "storage.objectViewer", + Principals: []string{ + "allAuthenticatedUsers", + }, + }, + }, + }, + }, + { + Name: "bucket-public-allusers", + Parent: "projects/modron-test", + ResourceGroupName: "projects/modron-test", + IamPolicy: &pb.IamPolicy{ + Permissions: []*pb.Permission{ + { + Role: "storage.objectViewer", + Principals: []string{ + "allUsers", + }, + }, + }, + }, + }, + modronTestResource("cloudsql-report-not-enforcing-tls"), + modronTestResource("cloudsql-test-db-ok"), + modronTestResource("cloudsql-test-db-public-and-authorized-networks"), + modronTestResource("cloudsql-test-db-public-and-no-authorized-networks"), + // Groups belong to another parent resource - they shouldn't show up here as they have not been scanned + // as part of the collection phase for the specified resourcegroups (they're one level up) + modronTestResource("instance-1"), + { + Name: "modron-pod-test-name-1", + ResourceGroupName: "projects/modron-test", + Parent: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace", + }, + { + Name: "modron-pod-test-name-2", + ResourceGroupName: "projects/modron-test", + Parent: "//container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace", + }, + { + Name: "organizations/1111", + ResourceGroupName: "organizations/1111", + Link: "https://console.cloud.google.com/welcome?organizationId=1111", + IamPolicy: &pb.IamPolicy{ + Resource: nil, + Permissions: []*pb.Permission{ + { + Role: "owner", + Principals: []string{ + "user:account-1@example.com", + "user:account-2@example.com", + }, + }, + { + Role: "test2", + Principals: []string{ + "account-2@example.com", + }, + }, + { + Role: "iam.serviceAccountAdmin", + Principals: []string{ + "account-1@example.com", + }, + }, + { + Role: "dataflow.admin", + Principals: []string{ + "account-1@example.com", + }, + }, + { + Role: "viewer", + Principals: []string{ + "account-2@example.com", + }, + }, + }, + }, + }, + { + Name: "projects/modron-test", + ResourceGroupName: "projects/modron-test", + Link: "https://console.cloud.google.com/welcome?project=modron-test", + Parent: "folders/234", + Ancestors: []string{ + "folders/234", "folders/123", "organizations/1111", + }, + IamPolicy: &pb.IamPolicy{ + Permissions: []*pb.Permission{ + { + Role: "owner", + Principals: []string{"user:owner1@example.com", "user:owner2@example.com"}, + }, + { + Role: "test2", + Principals: []string{"serviceAccount:account-2@modron-test.iam.gserviceaccount.com"}, + }, + /* + `{role:"iam.serviceAccountAdmin", principals:["account-1@modron-test.iam.gserviceaccount.com"]}`, + `{role:"dataflow.admin", principals:["account-3@modron-other-test.iam.gserviceaccount.com"]}`, + `{role:"iam.serviceAccountAdmin", principals:["account-3@modron-other-test.iam.gserviceaccount.com"]}`, + `{role:"viewer", principals:["account-2@modron-test.iam.gserviceaccount.com"]}`, + */ + { + Role: "iam.serviceAccountAdmin", + Principals: []string{ + "serviceAccount:account-1@modron-test.iam.gserviceaccount.com", + }, + }, + { + Role: "dataflow.admin", + Principals: []string{ + "serviceAccount:account-3@modron-other-test.iam.gserviceaccount.com", + }, + }, + { + Role: "iam.serviceAccountAdmin", + Principals: []string{ + "serviceAccount:account-3@modron-other-test.iam.gserviceaccount.com", + }, + }, + { + Role: "viewer", + Principals: []string{ + "serviceAccount:account-2@modron-test.iam.gserviceaccount.com", + }, + }, + }, + }, + Labels: map[string]string{ + "contact1": "user-1_example_com", + "contact2": "user-2_example_com", + }, + }, + modronTestResource("psc-network-should-not-be-reported"), + modronTestResource("spanner-test-db-1"), + modronTestResource("subnetwork-no-private-access-should-be-reported"), + modronTestResource("subnetwork-private-access-should-not-be-reported"), + { + Name: "user:account-1@modron-test", + Parent: "projects/modron-test", + ResourceGroupName: "projects/modron-test", + IamPolicy: &pb.IamPolicy{ + Permissions: []*pb.Permission{ + { + Role: "iam.serviceAccountUser", + Principals: []string{"user:user-1@example.com"}, + }, + }, + }, + }, + { + Name: "user:account-2@modron-test", + Parent: "projects/modron-test", + ResourceGroupName: "projects/modron-test", + IamPolicy: &pb.IamPolicy{ + Permissions: []*pb.Permission{ + { + Role: "iam.serviceAccountUser", + Principals: []string{"user:user-1@example.com"}, + }, + }, + }, + }, + } + + if diff := cmp.Diff(want, got, protocmp.Transform(), + protocmp.IgnoreOneofs(&pb.Resource{}, "type"), + protocmp.IgnoreFields(&pb.Resource{}, "uid", "collection_uid", "timestamp"), + ); diff != "" { + t.Errorf("resources collected: -want +got\n%s", diff) + } +} + +func msgIsTimestamp(x reflect.Value) bool { + if !x.IsValid() || x.IsZero() || x.IsNil() { + return false + } + return x.Interface().(protocmp.Message).Descriptor().FullName() == "google.protobuf.Timestamp" +} + +func TestCollectAndStoreObservations(t *testing.T) { + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + logrus.StandardLogger().SetFormatter(&logrus.TextFormatter{ForceColors: true}) + ctx := context.Background() + storage := memstorage.New() + gcpCollector := NewFake(ctx, storage, risk.TagConfig{}) + collectID := uuid.NewString() + + if err := gcpCollector.CollectAndStoreAll(ctx, collectID, []string{testProjectID}, nil); err != nil { + t.Fatalf("collectAndStoreObservations: %v", err) + } + if err := storage.AddOperationLog(ctx, []*pb.Operation{ + { + Id: collectID, + ResourceGroup: testProjectID, + Type: "scan", + Status: pb.Operation_COMPLETED, + }, + }); err != nil { + t.Fatalf("add operation log: %v", err) + } + if err := storage.FlushOpsLog(ctx); err != nil { + t.Errorf("flush ops log: %v", err) + } + got, err := storage.ListObservations(ctx, model.StorageFilter{Limit: 100}) + if err != nil { + t.Errorf("error storing observations: %v", err) + } + + want := []*pb.Observation{ + { + Name: "SQL_PUBLIC_IP", + CollectionId: proto.String(collectID), + Timestamp: timestamppb.Now(), + Remediation: &pb.Remediation{ + Description: "To lower your attack surface, Cloud SQL databases should not have public IPs. Private IPs provide improved network security and lower latency for your application.", + Recommendation: "Go to https://console.cloud.google.com/sql/instances/xyz/connections?project=project-id and click the \"Networking\" tab. Uncheck the \"Public IP\" checkbox and click \"SAVE\". If your instance is not configured to use a private IP, you will first have to enable private IP by following the instructions here: https://cloud.google.com/sql/docs/mysql/configure-private-ip#existing-private-instance", + }, + ResourceRef: &pb.ResourceRef{ + CloudPlatform: pb.CloudPlatform_GCP, + ExternalId: proto.String("//cloudsql.googleapis.com/projects/project-id/instances/xyz"), + GroupName: testProjectID, + }, + ExternalId: proto.String("//securitycenter.googleapis.com/projects/12345/sources/123/findings/48230f1978594ffb9d09a3cb1fe5e1b3"), + Source: pb.Observation_SOURCE_SCC, + Severity: pb.Severity_SEVERITY_MEDIUM, + + // We have no information about the folders here, so the impact is MEDIUM and the risk score is equal + // to the severity. + Impact: pb.Impact_IMPACT_MEDIUM, + RiskScore: pb.Severity_SEVERITY_MEDIUM, + Category: pb.Observation_CATEGORY_MISCONFIGURATION, + }, + } + if diff := cmp.Diff(want, got, protocmp.Transform(), + protocmp.IgnoreFields(&pb.Observation{}, "uid"), + cmpopts.EquateApproxTime(10*time.Second), + // Approximate comparison of timestamppb timestamps. + // https://github.com/golang/protobuf/issues/1347 + cmp.FilterPath( + func(p cmp.Path) bool { + if p.Last().Type() == reflect.TypeOf(protocmp.Message{}) { + a, b := p.Last().Values() + return msgIsTimestamp(a) && msgIsTimestamp(b) + } + return false + }, + cmp.Transformer("timestamppb", func(t protocmp.Message) time.Time { + if t["seconds"] == nil { + return time.Time{} + } + return time.Unix(t["seconds"].(int64), 0).UTC() + }), + ), + ); diff != "" { + t.Errorf("observations collected: -want +got\n%s", diff) + } +} + +func TestResourceGroupRegex(t *testing.T) { + validRGNames := []string{ + "projects/google.com:xyz", + "organizations/111111111111", + "folders/111111111111", + "folders/11111111111", + "projects/hello-world", + } + invalidRGNames := []string{ + "projects/!", + "organizations/example", + "folders/test", + } + for _, v := range validRGNames { + t.Run(v, func(t *testing.T) { + if !validGcpResourceGroupRegex.MatchString(v) { + t.Errorf("expected %s to be valid", v) + } + }) + } + + for _, v := range invalidRGNames { + t.Run(v, func(t *testing.T) { + if validGcpResourceGroupRegex.MatchString(v) { + t.Errorf("expected %s to be invalid", v) + } + }) + } +} diff --git a/src/collector/gcpcollector/kubernetes_cluster.go b/src/collector/gcpcollector/kubernetes_cluster.go new file mode 100644 index 0000000..7348645 --- /dev/null +++ b/src/collector/gcpcollector/kubernetes_cluster.go @@ -0,0 +1,202 @@ +package gcpcollector + +import ( + "encoding/json" + "strings" + "time" + + "golang.org/x/net/context" + "google.golang.org/api/container/v1" + "google.golang.org/protobuf/types/known/timestamppb" + "k8s.io/api/core/v1" + apimachineryv1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" +) + +func (collector *GCPCollector) ListKubernetesClusters(ctx context.Context, rgName string) (kubernetesClusters []*pb.Resource, err error) { + clusters, err := collector.api.ListClustersByZone(ctx, rgName, "-") + if err != nil { + return nil, err + } + for _, cluster := range clusters { + nodeVersion := "" + for _, nodePool := range cluster.NodePools { + nodeVersion = nodePool.Version + } + var masterAuthorizedNetworks []string + if cluster.MasterAuthorizedNetworksConfig != nil { + for _, cidrBlock := range cluster.MasterAuthorizedNetworksConfig.CidrBlocks { + masterAuthorizedNetworks = append(masterAuthorizedNetworks, cidrBlock.CidrBlock) + } + } + privateCluster := false + if cluster.PrivateClusterConfig != nil { + privateCluster = cluster.PrivateClusterConfig.EnablePrivateNodes + } + + kubernetesClusters = append(kubernetesClusters, &pb.Resource{ + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Name: cluster.Name, + Parent: rgName, + Type: &pb.Resource_KubernetesCluster{ + KubernetesCluster: &pb.KubernetesCluster{ + Location: cluster.Location, + PrivateCluster: privateCluster, + MasterAuthorizedNetworks: masterAuthorizedNetworks, + MasterVersion: cluster.CurrentMasterVersion, + NodesVersion: nodeVersion, + Security: &pb.KubernetesCluster_Security{ + VulnerabilityScanning: toPbSecurityVulnScanningType(cluster.SecurityPostureConfig), + }, + }, + }, + }) + } + + return kubernetesClusters, nil +} + +func toPbSecurityVulnScanningType(config *container.SecurityPostureConfig) pb.KubernetesCluster_Security_VulnScanning { + if config == nil { + return pb.KubernetesCluster_Security_VULN_SCAN_DISABLED + } + + // https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1beta1/projects.locations.clusters#Cluster.VulnerabilityMode + switch strings.ToUpper(config.VulnerabilityMode) { + case "VULNERABILITY_DISABLED": + return pb.KubernetesCluster_Security_VULN_SCAN_DISABLED + case "VULNERABILITY_BASIC": + return pb.KubernetesCluster_Security_VULN_SCAN_BASIC + case "VULNERABILITY_ENTERPRISE": + return pb.KubernetesCluster_Security_VULN_SCAN_ADVANCED + } + return pb.KubernetesCluster_Security_VULN_SCAN_UNKNOWN +} + +func (collector *GCPCollector) ListKubernetesNamespaces(ctx context.Context, rgName string) (namespaces []*pb.Resource, err error) { + ns, err := collector.api.ListNamespaces(ctx, rgName) + if err != nil { + return nil, err + } + for _, n := range ns { + createTime := parseTimeOrZero(n.CreateTime) + namespaces = append(namespaces, &pb.Resource{ + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Parent: n.ParentFullResourceName, + Name: n.Name, + Type: &pb.Resource_Namespace{ + Namespace: &pb.Namespace{ + Cluster: n.ParentFullResourceName, + CreationTime: timestamppb.New(createTime), + }, + }, + }) + } + return namespaces, nil +} + +func (collector *GCPCollector) ListKubernetesPods(ctx context.Context, rgName string) (pods []*pb.Resource, err error) { + ps, err := collector.api.ListPods(ctx, rgName) + if err != nil { + return nil, err + } + for _, p := range ps { + var pod v1.Pod + if len(p.VersionedResources) == 0 { + log.WithField(constants.LogKeyResourceGroup, rgName). + Warnf("no versioned resources found for pod %q", p.Name) + continue + } + if err := json.Unmarshal(p.VersionedResources[0].Resource, &pod); err != nil { + return nil, err + } + removeSensitiveFields(&pod) + + createTime := parseTimeOrZero(p.CreateTime) + + // Cleanup some fields we don't need in ObjectMeta + pod.ObjectMeta.ManagedFields = []apimachineryv1.ManagedFieldsEntry{} + + pods = append(pods, &pb.Resource{ + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Parent: p.ParentFullResourceName, + Name: utils.GetHumanReadableName(p.Name), + Type: &pb.Resource_Pod{ + Pod: &pb.Pod{ + // Extract the cluster name from the namespace name. + // Namespace names follow this format: + // "//container.googleapis.com/projects/project-id/locations/location/clusters/cluster-name/k8s/namespaces/namespace-name", + Cluster: removeNamespaceFromResourceName(p.ParentFullResourceName), + CreationTime: timestamppb.New(createTime), + Namespace: p.ParentFullResourceName, + Phase: phaseFromString(p.State), + Spec: &pod.Spec, + ObjectMeta: &pod.ObjectMeta, + }, + }, + }) + } + return pods, nil +} + +func removeNamespaceFromResourceName(resourceName string) string { + index := strings.LastIndex(resourceName, "/k8s/namespaces/") + if index == -1 { + return resourceName + } + return resourceName[:index] +} + +// removeSensitiveFields removes certain fields of the pod spec that might contain sensitive information. +// generally this shouldn't be a problem as secrets shouldn't be stored in the pod spec directly (but rather in a Secret), +// but since there is a likelihood that this might happen, we remove these fields just in case. +// TODO: remove this function and replace it with a more specific check based on entropy - and create observations for these cases +func removeSensitiveFields(pod *v1.Pod) { + for i := range pod.Spec.Containers { + for k, e := range pod.Spec.Containers[i].Env { + if e.Value != "" { + pod.Spec.Containers[i].Env[k].Value = "REDACTED" + } + } + } + for i := range pod.Spec.InitContainers { + for k, e := range pod.Spec.InitContainers[i].Env { + if e.Value != "" { + pod.Spec.InitContainers[i].Env[k].Value = "REDACTED" + } + } + } +} + +func parseTimeOrZero(timeString string) time.Time { + t, err := time.Parse(time.RFC3339, timeString) + if err != nil { + log.Errorf("cannot parse namespace creation time %q: %v", timeString, err) + return time.Time{} + } + return t +} + +func phaseFromString(s string) pb.Pod_Phase { + switch strings.ToUpper(s) { + case "RUNNING": + return pb.Pod_RUNNING + case "PENDING": + return pb.Pod_PENDING + case "SUCCEEDED": + return pb.Pod_SUCCEEDED + case "FAILED": + return pb.Pod_FAILED + case "UNKNOWN": + return pb.Pod_UNKNOWN + default: + return pb.Pod_UNKNOWN_PHASE + } +} diff --git a/src/collector/gcpcollector/kubernetes_cluster_test.go b/src/collector/gcpcollector/kubernetes_cluster_test.go new file mode 100644 index 0000000..62a95d1 --- /dev/null +++ b/src/collector/gcpcollector/kubernetes_cluster_test.go @@ -0,0 +1,106 @@ +//go:build integration + +package gcpcollector_test + +import ( + "context" + "fmt" + "os" + "testing" + + "k8s.io/api/core/v1" + + "github.com/nianticlabs/modron/src/collector/gcpcollector" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/storage/memstorage" + "github.com/nianticlabs/modron/src/utils" +) + +func getKubernetesClusterCollector(t *testing.T) (*pb.Resource, *gcpcollector.GCPCollector) { + t.Helper() + ctx := context.Background() + storage := memstorage.New() + orgID := os.Getenv("ORG_ID") + projectID := os.Getenv("PROJECT_ID") + if orgID == "" { + t.Skip("ORG_ID not set, skipping") + } + if projectID == "" { + t.Skip("PROJECT_ID not set, skipping") + } + orgSuffix := "" + tagConfig := risk.TagConfig{} + coll, err := gcpcollector.New(ctx, storage, orgID, orgSuffix, []string{}, tagConfig, []string{}) + if err != nil { + t.Fatal(err) + } + rsrc := &pb.Resource{ + Name: "projects/" + projectID, + ResourceGroupName: "projects/xyz", + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: projectID, + }, + }, + } + return rsrc, coll.(*gcpcollector.GCPCollector) +} + +func TestKubernetesCluster_ListKubernetesPods(t *testing.T) { + rsrc, coll := getKubernetesClusterCollector(t) + res, err := coll.ListKubernetesPods(context.Background(), rsrc.Name) + if err != nil { + t.Fatal(err) + } + if len(res) == 0 { + t.Fatal("No kubernetes pods found") + } + + for _, p := range res { + pod := p.GetPod() + if pod == nil { + t.Fatal("expected pod") + } + fmt.Printf("Pod %s\n", utils.GetHumanReadableName(p.Name)) + if pod.Spec == nil { + t.Fatal("pod spec cannot be nil") + } + + fmt.Printf("\tNamespace\t%s\n", utils.GetHumanReadableName(pod.Namespace)) + + if len(pod.Spec.Containers) == 0 { + t.Fatal("expected at least one container") + } + + for _, c := range pod.Spec.InitContainers { + fmt.Printf("\tInit Container\t%s\n", c.Name) + verifyEnvVarsAreRedacted(t, c.Env) + } + for _, c := range pod.Spec.Containers { + fmt.Printf("\tContainer\t%s\n", c.Name) + verifyEnvVarsAreRedacted(t, c.Env) + } + } +} + +func TestKubernetesCluster_ListClusters(t *testing.T) { + rsrc, coll := getKubernetesClusterCollector(t) + clusters, err := coll.ListKubernetesClusters(context.Background(), rsrc.Name) + if err != nil { + t.Fatal(err) + } + + for _, c := range clusters { + k8sCluster := c.GetKubernetesCluster() + t.Logf("Cluster %s:\n%+v\n", utils.GetHumanReadableName(c.Name), k8sCluster) + } +} + +func verifyEnvVarsAreRedacted(t *testing.T, vars []v1.EnvVar) { + for _, e := range vars { + if e.Value != "" && e.Value != "REDACTED" { + t.Errorf("expected env value for %s to be empty or REDACTED, got %s", e.Name, e.Value) + } + } +} diff --git a/src/collector/gcpcollector/load_balancer.go b/src/collector/gcpcollector/load_balancer.go new file mode 100644 index 0000000..e473444 --- /dev/null +++ b/src/collector/gcpcollector/load_balancer.go @@ -0,0 +1,325 @@ +package gcpcollector + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "regexp" + "time" + + "github.com/nianticlabs/modron/src/common" + pb "github.com/nianticlabs/modron/src/proto/generated" + + "golang.org/x/net/context" + "google.golang.org/api/compute/v1" + "google.golang.org/protobuf/types/known/timestamppb" +) + +var ( + externalMatch = regexp.MustCompile("EXTERNAL") + internalMatch = regexp.MustCompile("INTERNAL") + defaultSSLPolicy = &pb.SslPolicy{ // Default is COMPATIBLE with min TLS1.0 https://cloud.google.com/load-balancing/docs/ssl-policies-concepts + // TODO: Get this via the API + MinTlsVersion: pb.SslPolicy_TLS_1_0, + Profile: pb.SslPolicy_COMPATIBLE, + CreationDate: timestamppb.New(time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)), + Name: "GcpDefaultSslPolicy", + } + policyProfileMap = map[string]pb.SslPolicy_Profile{ + "COMPATIBLE": pb.SslPolicy_COMPATIBLE, + "MODERN": pb.SslPolicy_MODERN, + "RESTRICTED": pb.SslPolicy_RESTRICTED, + "CUSTOM": pb.SslPolicy_CUSTOM, + } + policyMinTLSVersionMap = map[string]pb.SslPolicy_MinTlsVersion{ + "TLS_1_0": pb.SslPolicy_TLS_1_0, + "TLS_1_1": pb.SslPolicy_TLS_1_1, + "TLS_1_2": pb.SslPolicy_TLS_1_2, + "TLS_1_3": pb.SslPolicy_TLS_1_3, + } +) + +func certsFromPemChain(pemChain string) (certs []*x509.Certificate, err error) { + if len(pemChain) == 0 { + return nil, fmt.Errorf("certificate chain is empty") + } + + next := []byte(pemChain) + for len(next) > 0 { + block, rest := pem.Decode(next) + if block == nil { + err = fmt.Errorf("unable to decode PEM-encoded certificate chain") + rest = nil + } else { + if cert, parseErr := x509.ParseCertificate(block.Bytes); parseErr != nil { + err = fmt.Errorf("X509 parsing: %w", parseErr) + } else { + certs = append(certs, cert) + } + } + next = rest + } + return +} + +func getSslPolicyForService( + service *compute.BackendService, + proxies []*compute.TargetHttpsProxy, + urlMaps []*compute.UrlMap, + sslPolicies []*compute.SslPolicy) (*pb.SslPolicy, error) { + + getSslPolicyWithServiceMatched := func(proxy *compute.TargetHttpsProxy) (*compute.SslPolicy, error) { + for _, sslPolicy := range sslPolicies { + if sslPolicy.SelfLink == proxy.SslPolicy { + return sslPolicy, nil + } + } + return nil, fmt.Errorf("sslPolicy for proxy %s not found", proxy.Name) + } + + handlePathMatchers := func(pathMatchers []*compute.PathMatcher, proxy *compute.TargetHttpsProxy) (*compute.SslPolicy, error) { + var sslPolicy *compute.SslPolicy + var err error + for _, pathMatch := range pathMatchers { + sslPolicy = nil + if service.SelfLink == pathMatch.DefaultService { + sslPolicy, err = getSslPolicyWithServiceMatched(proxy) + } else { + for _, pathRule := range pathMatch.PathRules { + if pathRule.Service == service.SelfLink { + sslPolicy, err = getSslPolicyWithServiceMatched(proxy) + break + } + } + } + if sslPolicy != nil { + return sslPolicy, err + } + } + return nil, fmt.Errorf("sslPolicy for proxy %s not found", proxy.Name) + } + + getPolicyForProxy := func(proxy *compute.TargetHttpsProxy) (*compute.SslPolicy, error) { + for _, urlMap := range urlMaps { + if proxy.UrlMap == urlMap.SelfLink && service.SelfLink == urlMap.DefaultService { + return getSslPolicyWithServiceMatched(proxy) + } + if proxy.UrlMap == urlMap.SelfLink { + sslPolicy, err := handlePathMatchers(urlMap.PathMatchers, proxy) + if sslPolicy != nil { + return sslPolicy, err + } + } + for _, pathMatch := range urlMap.PathMatchers { + if proxy.UrlMap == urlMap.SelfLink && service.SelfLink == pathMatch.DefaultService { + sslPolicy, err := getSslPolicyWithServiceMatched(proxy) + if sslPolicy != nil { + return sslPolicy, err + } + } + } + } + return nil, fmt.Errorf("sslPolicy for proxy %s not found", proxy.Name) + } + + usedPolicy := defaultSSLPolicy + for _, proxy := range proxies { + if proxy.SslPolicy != "" { + policy, err := getPolicyForProxy(proxy) + if err != nil { + // proxy uses the GCP Default Policy + continue + } + timeStamp, err := time.Parse(time.RFC3339, policy.CreationTimestamp) + if err != nil { + log.Errorf("SslPolicy %s: %s. %v", policy.Name, policy.CreationTimestamp, err) + continue + } + usedPolicy = &pb.SslPolicy{ + CreationDate: timestamppb.New(timeStamp), + Name: policy.Name, + Profile: policyProfileMap[policy.Profile], + CustomFeatures: policy.CustomFeatures, + EnabledFeatures: policy.EnabledFeatures, + MinTlsVersion: policyMinTLSVersionMap[policy.MinTlsVersion], + } + break + } + } + return usedPolicy, nil +} + +func getBackendServiceCerts( + service *compute.BackendService, + proxies []*compute.TargetHttpsProxy, + certs map[string]*compute.SslCertificate, + urlMaps []*compute.UrlMap, +) (serviceCerts []*compute.SslCertificate, err error) { + getCertsForURLMap := func(proxy *compute.TargetHttpsProxy, urlMap *compute.UrlMap) []*compute.SslCertificate { + var newCerts []*compute.SslCertificate + // TODO: `DefaultService` is not enough. Check `HostRules` too. + if proxy.UrlMap == urlMap.SelfLink && urlMap.DefaultService == service.SelfLink { + for _, url := range proxy.SslCertificates { + newCerts = append(newCerts, certs[url]) + } + } + return newCerts + } + getCertsForProxy := func(proxy *compute.TargetHttpsProxy) []*compute.SslCertificate { + var newCerts []*compute.SslCertificate + for _, urlMap := range urlMaps { + newCerts = append(newCerts, getCertsForURLMap(proxy, urlMap)...) + } + return newCerts + } + for _, proxy := range proxies { + serviceCerts = append(serviceCerts, getCertsForProxy(proxy)...) + } + return serviceCerts, nil +} + +func loadBalancerFromBackendService( + service *compute.BackendService, + proxiesByScope []*compute.TargetHttpsProxy, + certs map[string]*compute.SslCertificate, + urlMapsByScope []*compute.UrlMap, + sslPoliciesByScope []*compute.SslPolicy, +) (*pb.LoadBalancer, error) { + // Check whether there is a frontend for the backendservice + frontEndFound := false +FeCheck: + for _, proxy := range proxiesByScope { + for _, urlMap := range urlMapsByScope { + if proxy.UrlMap == urlMap.SelfLink && urlMap.DefaultService == service.SelfLink { + frontEndFound = true + break FeCheck + } + } + } + if !frontEndFound { + return nil, fmt.Errorf("no frontend defined for backend %q", service.Name) + } + loadBalancerType := pb.LoadBalancer_UNKNOWN_TYPE + if externalMatch.MatchString(service.LoadBalancingScheme) { + loadBalancerType = pb.LoadBalancer_EXTERNAL + } + if internalMatch.MatchString(service.LoadBalancingScheme) { + loadBalancerType = pb.LoadBalancer_INTERNAL + } + + var serviceCerts []*compute.SslCertificate + newServiceCerts, err := getBackendServiceCerts( + service, + proxiesByScope, + certs, + urlMapsByScope, + ) + if err != nil { + return nil, fmt.Errorf("unable to retrieve certificates for backend service %q: %w", service.Name, err) + } + serviceCerts = append(serviceCerts, newServiceCerts...) + var pbCerts []*pb.Certificate + for _, cert := range serviceCerts { + certType, err := common.TypeFromSslCertificate(cert) + if err != nil { + return nil, fmt.Errorf("retrieve %q: %w", cert.Name, err) + } + creationDate, err := time.Parse(time.RFC3339, cert.CreationTimestamp) + if err != nil { + return nil, fmt.Errorf("creation timestamp of %q: %w", cert.Name, err) + } + expirationDate, err := time.Parse(time.RFC3339, cert.ExpireTime) + if err != nil { + return nil, fmt.Errorf("expiration timestamp of certificate %q: %w", cert.Name, err) + } + // Parse the certificate chain. The certificate at index 0 is the leaf certificate. + certs, err := certsFromPemChain(cert.Certificate) + if err != nil { + return nil, fmt.Errorf("parse certificate chain of %q: %w", cert.Name, err) + } + pbCerts = append(pbCerts, &pb.Certificate{ + Type: certType, + DomainName: certs[0].Subject.CommonName, + SubjectAlternativeNames: certs[0].DNSNames, + CreationDate: timestamppb.New(creationDate), + ExpirationDate: timestamppb.New(expirationDate), + Issuer: certs[0].Issuer.CommonName, + SignatureAlgorithm: certs[0].SignatureAlgorithm.String(), + PemCertificateChain: cert.Certificate, + }) + } + usedSslPolicy, err := getSslPolicyForService( + service, + proxiesByScope, + urlMapsByScope, + sslPoliciesByScope, + ) + if err != nil { + return nil, err + } + var iap *pb.IAP + if service.Iap != nil { + iap = &pb.IAP{ + Enabled: service.Iap.Enabled, + CliendId: service.Iap.Oauth2ClientId, + } + } + + return &pb.LoadBalancer{ + Type: loadBalancerType, + Certificates: pbCerts, + SslPolicy: usedSslPolicy, + Iap: iap, + }, nil +} + +// TODO: Retrieve certificates for TCP/SSL LBs as well. This will require retrieving `TargetSslProxies`. +func (collector *GCPCollector) ListLoadBalancers(ctx context.Context, rgName string) (loadBalancers []*pb.Resource, err error) { + targetHTTPSProxies, err := collector.api.ListTargetHTTPSProxies(ctx, rgName) + if err != nil { + return nil, err + } + urlMaps, err := collector.api.ListURLMaps(ctx, rgName) + if err != nil { + return nil, err + } + certs, err := collector.api.ListCertificates(ctx, rgName) + if err != nil { + return nil, err + } + certsByURL := make(map[string]*compute.SslCertificate) + for _, cert := range certs { + certsByURL[cert.SelfLink] = cert + } + backendServices, err := collector.api.ListBackendServices(ctx, rgName) + if err != nil { + return nil, err + } + sslPolicies, err := collector.api.ListSslPolicies(ctx, rgName) + if err != nil { + return nil, err + } + + for _, backendService := range backendServices { + if lb, err := loadBalancerFromBackendService( + backendService, + targetHTTPSProxies, + certsByURL, + urlMaps, + sslPolicies, + ); err != nil { + log.Infof("no LB for backend service %s: %v", backendService.Name, err) + } else { + loadBalancers = append(loadBalancers, &pb.Resource{ + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Name: backendService.Name, + Parent: rgName, + Type: &pb.Resource_LoadBalancer{ + LoadBalancer: lb, + }, + }) + } + } + return loadBalancers, nil +} diff --git a/src/collector/gcpcollector/metrics.go b/src/collector/gcpcollector/metrics.go new file mode 100644 index 0000000..b9ab821 --- /dev/null +++ b/src/collector/gcpcollector/metrics.go @@ -0,0 +1,26 @@ +package gcpcollector + +import ( + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + + "github.com/nianticlabs/modron/src/constants" +) + +var meter = otel.Meter("github.com/nianticlabs/modron/src/collector/gcpcollector") + +type metrics struct { + SccCollectedObservations metric.Int64Counter +} + +func initMetrics() metrics { + sccCollectedObsCounter, err := meter.Int64Counter(constants.MetricsPrefix+"scc_collected_observations", + metric.WithDescription("Number of collected observations from SCC"), + ) + if err != nil { + log.Errorf("failed to create scc_collected_observations counter: %v", err) + } + return metrics{ + SccCollectedObservations: sccCollectedObsCounter, + } +} diff --git a/src/collector/gcpcollector/network.go b/src/collector/gcpcollector/network.go index 48a8604..0013993 100644 --- a/src/collector/gcpcollector/network.go +++ b/src/collector/gcpcollector/network.go @@ -1,8 +1,15 @@ package gcpcollector import ( + "fmt" + "sync" + + "go.opentelemetry.io/otel/attribute" + "golang.org/x/sync/errgroup" + "github.com/nianticlabs/modron/src/common" - "github.com/nianticlabs/modron/src/pb" + "github.com/nianticlabs/modron/src/constants" + pb "github.com/nianticlabs/modron/src/proto/generated" "golang.org/x/net/context" ) @@ -11,44 +18,72 @@ var subnetworkPurposeList = map[string]struct{}{ "PRIVATE": {}, } -func (collector *GCPCollector) ListNetworks(ctx context.Context, resourceGroup *pb.Resource) ([]*pb.Resource, error) { - regions, err := collector.api.ListRegions(ctx, resourceGroup.Name) +func (collector *GCPCollector) ListNetworks(ctx context.Context, rgName string) (networks []*pb.Resource, err error) { + ctx, span := tracer.Start(ctx, "ListNetworks") + span.SetAttributes( + attribute.String(constants.TraceKeyResourceGroup, rgName), + ) + defer span.End() + regions, err := collector.api.ListRegions(ctx, rgName) if err != nil { return nil, err } - - networkIps := map[string][]string{} - networkIds := map[string][]uint64{} - networkGoogleAccessV4 := map[string]bool{} + errGroup := new(errgroup.Group) + networkIPs := sync.Map{} + networkGoogleAccessV4 := sync.Map{} for _, region := range regions { - subNetworks, err := collector.api.ListSubNetworksByRegion(ctx, resourceGroup.Name, region.Name) - if err != nil { - return nil, err - } - for _, subNetwork := range subNetworks { - networkIds[subNetwork.Name] = append(networkIds[subNetwork.Name], subNetwork.Id) - networkIps[subNetwork.Name] = append(networkIps[subNetwork.Name], subNetwork.IpCidrRange) - if _, ok := subnetworkPurposeList[subNetwork.Purpose]; ok { - networkGoogleAccessV4[subNetwork.Name] = networkGoogleAccessV4[subNetwork.Name] || subNetwork.PrivateIpGoogleAccess - } else { - networkGoogleAccessV4[subNetwork.Name] = true - } - } + errGroup.Go(func() error { + return collector.fetchRegion(ctx, rgName, region.Name, &networkIPs, &networkGoogleAccessV4) + }) + } + if err := errGroup.Wait(); err != nil { + return nil, fmt.Errorf("failed to fetch regions: %w", err) } - networks := []*pb.Resource{} - for netName, Ips := range networkIps { + networkIPs.Range(func(netName, value interface{}) bool { + hasGoogleAccess, ok := networkGoogleAccessV4.Load(netName) + if !ok { + hasGoogleAccess = false + } networks = append(networks, &pb.Resource{ - Uid: common.GetUUID(3), - ResourceGroupName: resourceGroup.Name, - Name: formatResourceName(netName, networkIds[netName]), - Parent: resourceGroup.Name, + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Name: netName.(string), + Parent: rgName, Type: &pb.Resource_Network{ Network: &pb.Network{ - GcpPrivateGoogleAccessV4: networkGoogleAccessV4[netName], - Ips: Ips, + GcpPrivateGoogleAccessV4: hasGoogleAccess.(bool), + Ips: value.([]string), }, }, }) - } + return true + }) return networks, nil } + +func (collector *GCPCollector) fetchRegion( + ctx context.Context, + rgName, regionName string, + networkIPs, networkGoogleAccessV4 *sync.Map, +) error { + subNetworks, err := collector.api.ListSubNetworksByRegion(ctx, rgName, regionName) + if err != nil { + return fmt.Errorf("failed to list subnetworks in region %s: %w", regionName, err) + } + for _, subNetwork := range subNetworks { + netIPs, _ := networkIPs.LoadOrStore(subNetwork.Name, []string{}) + netIPs = append(netIPs.([]string), subNetwork.IpCidrRange) + networkIPs.Store(subNetwork.Name, netIPs) + + netGoogleAccessV4, ok := networkGoogleAccessV4.Load(subNetwork.Name) + if !ok { + netGoogleAccessV4 = false + } + if _, ok := subnetworkPurposeList[subNetwork.Purpose]; ok { + networkGoogleAccessV4.Store(subNetwork.Name, netGoogleAccessV4.(bool) || subNetwork.PrivateIpGoogleAccess) + } else { + networkGoogleAccessV4.Store(subNetwork.Name, true) + } + } + return nil +} diff --git a/src/collector/gcpcollector/network_test.go b/src/collector/gcpcollector/network_test.go new file mode 100644 index 0000000..a805d7a --- /dev/null +++ b/src/collector/gcpcollector/network_test.go @@ -0,0 +1,119 @@ +package gcpcollector + +import ( + "context" + "sort" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "google.golang.org/api/compute/v1" + "google.golang.org/protobuf/testing/protocmp" + + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/storage/memstorage" +) + +type testSlowGCPAPI struct { + GCPApi +} + +func (t *testSlowGCPAPI) ListRegions(_ context.Context, _ string) ([]*compute.Region, error) { + return []*compute.Region{ + { + Name: "us-central1", + }, + { + Name: "us-west1", + }, + { + Name: "us-east1", + }, + }, nil +} + +func (t *testSlowGCPAPI) ListSubNetworksByRegion(_ context.Context, _ string, region string) ([]*compute.Subnetwork, error) { + time.Sleep(1 * time.Second) + return []*compute.Subnetwork{ + { + Name: "subnet-" + region, + Purpose: "PRIVATE", + }, + }, nil +} + +func TestListNetworks(t *testing.T) { + ctx := context.Background() + st := memstorage.New() + tagConfig := risk.TagConfig{ + Environment: "111111111111/environment", + EmployeeData: "111111111111/employee_data", + CustomerData: "111111111111/customer_data", + } + coll, err := New(ctx, st, "111111", "example.com", []string{}, tagConfig, []string{}) + if err != nil { + t.Fatalf("failed to create collector: %v", err) + } + gcpColl := coll.(*GCPCollector) + gcpColl.api = &testSlowGCPAPI{} + got, err := coll.(*GCPCollector).ListNetworks(ctx, "projects/test-rg") + if err != nil { + t.Fatalf("failed to list networks: %v", err) + } + sort.Sort(resourceByName(got)) + want := []*pb.Resource{ + { + Name: "subnet-us-central1", + Parent: "projects/test-rg", + ResourceGroupName: "projects/test-rg", + Type: &pb.Resource_Network{ + Network: &pb.Network{ + Ips: []string{""}, + GcpPrivateGoogleAccessV4: false, + }, + }, + }, + { + Name: "subnet-us-east1", + Parent: "projects/test-rg", + ResourceGroupName: "projects/test-rg", + Type: &pb.Resource_Network{ + Network: &pb.Network{ + Ips: []string{""}, + GcpPrivateGoogleAccessV4: false, + }, + }, + }, + { + Name: "subnet-us-west1", + Parent: "projects/test-rg", + ResourceGroupName: "projects/test-rg", + Type: &pb.Resource_Network{ + Network: &pb.Network{ + Ips: []string{""}, + GcpPrivateGoogleAccessV4: false, + }, + }, + }, + } + if diff := cmp.Diff(want, got, protocmp.Transform(), protocmp.IgnoreFields(&pb.Resource{}, "uid")); diff != "" { + t.Errorf("ListNetworks() mismatch (-want +got):\n%s", diff) + } +} + +type resourceByName []*pb.Resource + +func (r resourceByName) Len() int { + return len(r) +} + +func (r resourceByName) Less(i, j int) bool { + return r[i].Name < r[j].Name +} + +func (r resourceByName) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +var _ sort.Interface = resourceByName(nil) diff --git a/src/collector/gcpcollector/ratelimiter.go b/src/collector/gcpcollector/ratelimiter.go new file mode 100644 index 0000000..b6008da --- /dev/null +++ b/src/collector/gcpcollector/ratelimiter.go @@ -0,0 +1,62 @@ +package gcpcollector + +import ( + "context" + "time" + + "golang.org/x/time/rate" + "google.golang.org/api/cloudasset/v1" +) + +const ( + // https://cloud.google.com/asset-inventory/docs/quota + cloudAssetSearchResourcesQuotaPerMinute = 350 + cloudAssetSearchIamQuotaPerMinute = 350 + cloudAssetSearchBurst = 1 + apiKeysPageSize = 300 + pageSize = 500 // Page size is capped at 500 even if a larger value is given. + sccPageSize = 1000 // The maximum number of results to return in a single response. Default is 10, minimum is 1, maximum is 1000. + sccReadBurst = 1 + sccReadRequestsPerMinute = 1000 + listSubnetsRangeQuota = 10_000 + listSubnetsRangeBurst = 5_000 +) + +var ( + rlResources = rate.NewLimiter(rate.Every(time.Minute/cloudAssetSearchResourcesQuotaPerMinute), cloudAssetSearchBurst) + rlIam = rate.NewLimiter(rate.Every(time.Minute/cloudAssetSearchIamQuotaPerMinute), cloudAssetSearchBurst) + rlSCC = rate.NewLimiter(rate.Every(time.Minute/sccReadRequestsPerMinute), sccReadBurst) + rlSubnetRanges = rate.NewLimiter(rate.Every(time.Minute/listSubnetsRangeQuota), listSubnetsRangeBurst) +) + +// newRateLimitedCloudAssetInventoryV1 returns a service similar to the CloudAssetV1Service that is rate limited according +// to the Cloud Asset Inventory API quotas. +func newRateLimitedCloudAssetInventoryV1(svc *cloudasset.V1Service) rateLimitedCloudAssetV1Service { + return &rateLimitedCAI{ + svc: svc, + } +} + +type rateLimitedCAI struct { + svc *cloudasset.V1Service +} + +// rateLimitedCloudAssetV1Service is an interface that implements rate limiting on the original CloudAssetV1Service +type rateLimitedCloudAssetV1Service interface { + SearchAllResources(ctx context.Context, scope string) (*cloudasset.V1SearchAllResourcesCall, error) + SearchAllIamPolicies(ctx context.Context, scope string) (*cloudasset.V1SearchAllIamPoliciesCall, error) +} + +func (r *rateLimitedCAI) SearchAllResources(ctx context.Context, scope string) (*cloudasset.V1SearchAllResourcesCall, error) { + if err := rlResources.Wait(ctx); err != nil { + return nil, err + } + return r.svc.SearchAllResources(scope).PageSize(pageSize), nil +} + +func (r *rateLimitedCAI) SearchAllIamPolicies(ctx context.Context, scope string) (*cloudasset.V1SearchAllIamPoliciesCall, error) { + if err := rlIam.Wait(ctx); err != nil { + return nil, err + } + return r.svc.SearchAllIamPolicies(scope).PageSize(pageSize), nil +} diff --git a/src/collector/gcpcollector/resource_group.go b/src/collector/gcpcollector/resource_group.go new file mode 100644 index 0000000..f211a7d --- /dev/null +++ b/src/collector/gcpcollector/resource_group.go @@ -0,0 +1,241 @@ +package gcpcollector + +import ( + "fmt" + "strings" + + "golang.org/x/net/context" + "google.golang.org/api/cloudasset/v1" + "google.golang.org/api/cloudresourcemanager/v3" + + "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +const ( + projectResourcePrefix = "//cloudresourcemanager.googleapis.com/projects/" + folderResourcePrefix = "//cloudresourcemanager.googleapis.com/folders/" + organizationResourcePrefix = "//cloudresourcemanager.googleapis.com/organizations/" + cloudResourceManagerPrefix = "//cloudresourcemanager.googleapis.com/" + welcomePage = "https://console.cloud.google.com/welcome" +) + +var ( + // Syntax https://cloud.google.com/asset-inventory/docs/query-syntax + projectOwnersQuery = fmt.Sprintf("resource:%s*", projectResourcePrefix) + foldersOwnersQuery = fmt.Sprintf("resource:%s*", folderResourcePrefix) + organizationsOwnersQuery = fmt.Sprintf("resource:%s*", organizationResourcePrefix) +) + +func (collector *GCPCollector) GetResourceGroupWithIamPolicy(ctx context.Context, collectID string, rgName string) (res *pb.Resource, err error) { + ctx, span := tracer.Start(ctx, "GetResourceGroup") + defer span.End() + + rgs, err := collector.ListResourceGroupsWithIamPolicies(ctx, []string{rgName}) + if err != nil { + return nil, err + } + if len(rgs) != 1 { + return nil, fmt.Errorf("found %d resource groups for %s, expected 1", len(rgs), rgName) + } + rgs[0].CollectionUid = collectID + return rgs[0], nil +} + +func (collector *GCPCollector) ListResourceGroupsWithIamPolicies(ctx context.Context, rgNames []string) ([]*pb.Resource, error) { + ctx, span := tracer.Start(ctx, "ListResourceGroupsWithIamPolicies") + defer span.End() + rgs, err := collector.ListResourceGroups(ctx, rgNames) + if err != nil { + return nil, fmt.Errorf("ListResourceGroups: %w", err) + } + for i, rg := range rgs { + var resp *cloudresourcemanager.Policy + switch { + case strings.HasPrefix(rg.Name, constants.GCPFolderIDPrefix): + resp, err = collector.api.ListFoldersIamPolicy(ctx, rg.Name) + case strings.HasPrefix(rg.Name, constants.GCPOrgIDPrefix): + resp, err = collector.api.ListOrganizationsIamPolicy(ctx, rg.Name) + default: // Default to project + resp, err = collector.api.ListProjectIamPolicy(ctx, rg.Name) + } + if err != nil { + return nil, fmt.Errorf("cannot get IAM policies for resource group %q: %w", rg.Name, err) + } + var permissions []*pb.Permission + for _, binding := range resp.Bindings { + permissions = append(permissions, &pb.Permission{ + Role: constants.ToRole(binding.Role).String(), + Principals: binding.Members, + }) + } + rgs[i].IamPolicy = &pb.IamPolicy{ + Permissions: permissions, + } + } + return rgs, nil +} + +func (collector *GCPCollector) ListResourceGroups(ctx context.Context, rgNames []string) (rgs []*pb.Resource, err error) { + ctx, span := tracer.Start(ctx, "ListResourceGroups") + defer span.End() + resourceGroups, err := collector.api.ListResourceGroups(ctx, rgNames) + if err != nil { + return nil, err + } + for _, rg := range resourceGroups { + var ancestors []string + ancestors = append(ancestors, rg.Folders...) + if rg.Organization != "" { + ancestors = append(ancestors, rg.Organization) + } + if rg.State != "ACTIVE" { + log.Warnf("resource group %s is not active", rg.Name) + continue + } + rgName := strings.TrimPrefix(rg.Name, cloudResourceManagerPrefix) + res := &pb.Resource{ + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Name: rgName, + DisplayName: rg.DisplayName, + Parent: strings.TrimPrefix(rg.ParentFullResourceName, cloudResourceManagerPrefix), + Link: getResourceGroupLink(rg.Name), + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: strings.TrimPrefix(rg.Name, cloudResourceManagerPrefix), + Name: rg.DisplayName, + }, + }, + Labels: rg.Labels, + Tags: toKV(rg.Tags), + Ancestors: ancestors, + } + rgs = append(rgs, res) + } + return rgs, nil +} + +func toKV(tags []*cloudasset.Tag) map[string]string { + kv := make(map[string]string) + for _, tag := range tags { + kv[tag.TagKey] = tag.TagValue + } + return kv +} + +func getResourceGroupLink(resourceName string) string { + parts := strings.SplitN(resourceName, "/", 2) //nolint:mnd + if len(parts) != 2 { //nolint:mnd + return "" + } + switch parts[0] { + case "projects": + return welcomePage + "?project=" + parts[1] + case "folders": + return welcomePage + "?folder=" + parts[1] + case "organizations": + return welcomePage + "?organizationId=" + parts[1] + } + return "" +} + +func (collector *GCPCollector) ListResourceGroupNames(ctx context.Context) (rgNames []string, err error) { + ctx, span := tracer.Start(ctx, "ListResourceGroupNames") + defer span.End() + resourceGroups, err := collector.ListResourceGroups(ctx, nil) + if err != nil { + return nil, err + } + for _, rg := range resourceGroups { + rgNames = append(rgNames, strings.TrimPrefix(rg.Name, cloudResourceManagerPrefix)) + } + log.Infof("found %d names", len(rgNames)) + return rgNames, nil +} + +func (collector *GCPCollector) listResourceGroupAdmins( + ctx context.Context, + resourceGroupAdmins map[string]map[string]struct{}, + iamPolicyResult []*cloudasset.IamPolicySearchResult, +) model.ACLCache { + ctx, span := tracer.Start(ctx, "listResourceGroupAdmins") + defer span.End() + for _, res := range iamPolicyResult { + if res.Policy == nil { + continue + } + resourceID := strings.TrimPrefix(res.Resource, cloudResourceManagerPrefix) + if strings.HasPrefix(resourceID, constants.GCPProjectsNamePrefix+constants.GCPSysProjectPrefix) { + log.Debugf("skipping system project %q", resourceID) + continue + } + if strings.HasPrefix(resourceID, constants.GCPFolderIDPrefix) { + // TODO: remove this once we support the hierarchical structure of GCP + log.Debugf("skipping folder %q", resourceID) + continue + } + // Allow admins to see all the resources, regardless on whether they are owned by someone or not. + resourceGroupAdmins["*"][resourceID] = struct{}{} + for _, binding := range res.Policy.Bindings { + theRole := constants.Role(strings.TrimPrefix(binding.Role, constants.GCPRolePrefix)) + _, hasAdminRole := constants.AdminRoles[theRole] + _, hasAdditionalAdminRole := collector.additionalAdminRolesMap[theRole] + if !hasAdminRole && !hasAdditionalAdminRole { + continue + } + + for _, u := range binding.Members { + users := []string{u} + var err error + if strings.HasPrefix(u, constants.GCPAccountGroupPrefix) && strings.HasSuffix(u, collector.orgSuffix) { + users, err = collector.api.ListUsersInGroup(ctx, u) + if err != nil { + log.Warnf("cannot list users in group %q: %v", u, err) + continue + } + } + for _, user := range users { + user = strings.TrimPrefix(user, constants.GCPUserAccountPrefix) + if strings.HasSuffix(user, collector.orgSuffix) { + if _, ok := resourceGroupAdmins[user]; !ok { + resourceGroupAdmins[user] = make(map[string]struct{}) + } + resourceGroupAdmins[user][resourceID] = struct{}{} + } + } + } + } + } + return resourceGroupAdmins +} + +func (collector *GCPCollector) ListResourceGroupAdmins(ctx context.Context) (model.ACLCache, error) { + ctx, span := tracer.Start(ctx, "ListResourceGroupAdmins") + defer span.End() + scope := constants.GCPOrgIDPrefix + collector.orgID + + resp, err := collector.api.SearchIamPolicy(ctx, scope, projectOwnersQuery) + if err != nil { + return nil, err + } + resourceGroupAdmins := model.ACLCache{} + resourceGroupAdmins["*"] = map[string]struct{}{} + resourceGroupAdmins = collector.listResourceGroupAdmins(ctx, resourceGroupAdmins, resp) + + resp, err = collector.api.SearchIamPolicy(ctx, scope, foldersOwnersQuery) + if err != nil { + return nil, err + } + resourceGroupAdmins = collector.listResourceGroupAdmins(ctx, resourceGroupAdmins, resp) + + resp, err = collector.api.SearchIamPolicy(ctx, scope, organizationsOwnersQuery) + if err != nil { + return nil, err + } + resourceGroupAdmins = collector.listResourceGroupAdmins(ctx, resourceGroupAdmins, resp) + + return resourceGroupAdmins, nil +} diff --git a/src/collector/gcpcollector/resource_group_test.go b/src/collector/gcpcollector/resource_group_test.go new file mode 100644 index 0000000..8e2b5d0 --- /dev/null +++ b/src/collector/gcpcollector/resource_group_test.go @@ -0,0 +1,31 @@ +package gcpcollector + +import "testing" + +func TestGetResourceGroupLink(t *testing.T) { + tc := []struct { + name string + expected string + }{ + { + name: "projects/project-1", + expected: "https://console.cloud.google.com/welcome?project=project-1", + }, + { + name: "folders/1000000", + expected: "https://console.cloud.google.com/welcome?folder=1000000", + }, + { + name: "organizations/1000000", + expected: "https://console.cloud.google.com/welcome?organizationId=1000000", + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + if got := getResourceGroupLink(tt.name); got != tt.expected { + t.Errorf("GetResourceGroupLink() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/src/collector/gcpcollector/scc_findings.go b/src/collector/gcpcollector/scc_findings.go new file mode 100644 index 0000000..4060959 --- /dev/null +++ b/src/collector/gcpcollector/scc_findings.go @@ -0,0 +1,233 @@ +package gcpcollector + +import ( + "context" + "fmt" + "regexp" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + + "github.com/google/uuid" + "google.golang.org/api/securitycenter/v1" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/constants" + modronmetric "github.com/nianticlabs/modron/src/metric" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" +) + +// https://cloud.google.com/security-command-center/docs/finding-classes +// https://cloud.google.com/security-command-center/docs/reference/rest/v1/organizations.sources.findings#findingclass +var reportingClasses = map[string]struct{}{ + "VULNERABILITY": {}, + "MISCONFIGURATION": {}, + "TOXIC_COMBINATION": {}, +} + +// This is a list of SCC categories that relate to containers +// we use this to filter out some packages that in the specified category do not make sense +// For example, the package "linux" is not relevant in the context of a container image +// because the kernel being used is not the one from the container image but the one from the host, thus +// we exclude it from `GKE_RUNTIME_OS_VULNERABILITY` +var skipPackagesByCategories = map[string]map[string]struct{}{ + "GKE_RUNTIME_OS_VULNERABILITY": { + "linux": {}, + }, + "GKE_RUNTIME_LANG_VULNERABILITY": {}, +} + +func (collector *GCPCollector) ListSccFindings(ctx context.Context, rgName string) (observations []*pb.Observation, err error) { + rgLogger := log.WithField(constants.LogKeyResourceGroup, rgName) + rgLogger.Info("Listing SCC findings") + sccFindings, err := collector.api.ListSccFindings(ctx, rgName) + if err != nil { + rgLogger.WithError(err).Errorf("Error listing SCC findings") + return nil, err + } + rgLogger.Infof("Found %d SCC findings", len(sccFindings)) + + for _, v := range sccFindings { + if _, ok := reportingClasses[v.FindingClass]; !ok { + continue + } + if _, ok := collector.allowedSccCategories[v.Category]; !ok { + log.Infof("Skipping SCC finding with category %q", v.Category) + continue + } + + if v.Vulnerability != nil && v.Vulnerability.OffendingPackage != nil { + offPkg := v.Vulnerability.OffendingPackage + if pbc, ok := skipPackagesByCategories[v.Category]; ok { + if _, ok := pbc[offPkg.PackageName]; ok { + log. + WithField(modronmetric.KeyCategory, v.Category). + WithField(modronmetric.KeyOffendingPackage, offPkg). + Infof("Skipping SCC finding because it has been excluded") + continue + } + } + } + + observations = append(observations, FindingToObservation(v, rgName)) + collector.metrics.SccCollectedObservations. + Add(ctx, 1, metric.WithAttributes( + attribute.String(modronmetric.KeyCategory, v.Category), + attribute.String(modronmetric.KeySeverity, v.Severity), + )) + } + rgLogger.Infof("Collected %d observations", len(observations)) + return +} + +func FindingToObservation(v *securitycenter.Finding, rgName string) *pb.Observation { + obsTime, err := time.Parse(time.RFC3339, v.EventTime) + if err != nil { + log.Warnf("unable to parse time: %v", err) + obsTime = time.Now() + } + return enrichObservation(&pb.Observation{ + Uid: uuid.NewString(), + Timestamp: timestamppb.New(obsTime), + Name: v.Category, + ExpectedValue: nil, + ObservedValue: nil, + Remediation: getRemediation(v), + ResourceRef: &pb.ResourceRef{ + GroupName: rgName, + ExternalId: utils.RefOrNull(v.ResourceName), + CloudPlatform: pb.CloudPlatform_GCP, + }, + ExternalId: utils.RefOrNull(fmt.Sprintf("//securitycenter.googleapis.com/%s", v.CanonicalName)), + Severity: fromSccSeverity(v.Severity), + Source: pb.Observation_SOURCE_SCC, + Category: fromSccFindingClass(v.FindingClass), + }, v) +} + +func getPackageValue(pkg *securitycenter.Package) *structpb.Value { + if pkg == nil { + return nil + } + return structpb.NewStringValue(fmt.Sprintf("%s %s", pkg.PackageName, pkg.PackageVersion)) +} + +var kubernetesClusterRegex = regexp.MustCompile("^//container.googleapis.com/projects/[^/]+/locations/[^/]+/clusters/([^/]+)$") + +// EnrichObservation enriches the observation by modifying some fields based on the finding +func enrichObservation(p *pb.Observation, v *securitycenter.Finding) *pb.Observation { + if v == nil { + return p + } + obsLogger := log. + WithField("observation", p.Name). + WithField("scc_finding_name", v.Name). + WithField("scc_finding_class", v.FindingClass). + WithField("scc_finding_category", v.Category) + if v.Vulnerability != nil { + if v.Vulnerability.FixedPackage != nil { + p.ExpectedValue = getPackageValue(v.Vulnerability.FixedPackage) + } + if v.Vulnerability.OffendingPackage != nil { + p.ObservedValue = getPackageValue(v.Vulnerability.OffendingPackage) + } + } + if v.Kubernetes != nil { //nolint:nestif + // We have a Kubernetes reference, let's use it + if kubernetesClusterRegex.MatchString(v.ResourceName) { + if len(v.Kubernetes.Objects) > 0 { + obj := v.Kubernetes.Objects[0] + extID := "" + if p.ResourceRef.ExternalId != nil { + extID = *p.ResourceRef.ExternalId + } + switch obj.Kind { + case "Deployment": + extID = fmt.Sprintf("%s/k8s/namespaces/%s/apps/deployments/%s", extID, obj.Ns, obj.Name) + case "DaemonSet": + extID = fmt.Sprintf("%s/k8s/namespaces/%s/apps/daemonsets/%s", extID, obj.Ns, obj.Name) + case "NodePool": + extID = fmt.Sprintf("%s/nodePools/%s", extID, obj.Name) + default: + obsLogger.Warnf("unhandled Kubernetes object kind: %s", obj.Kind) + } + p.ResourceRef.ExternalId = utils.RefOrNull(extID) + } else { + obsLogger.Warnf("no Kubernetes objects found for %s", v.ResourceName) + } + } + } + return p +} + +func getRemediation(v *securitycenter.Finding) *pb.Remediation { + if v == nil { + return nil + } + vuln := v.Vulnerability + if vuln != nil { + if vuln.OffendingPackage != nil && vuln.FixedPackage != nil { + return &pb.Remediation{ + Description: fmt.Sprintf("%s in %s %s%s: update to %s %s", + vuln.Cve.Id, + vuln.OffendingPackage.PackageName, vuln.OffendingPackage.PackageVersion, + getContainerImage(v.Kubernetes), + vuln.FixedPackage.PackageName, vuln.FixedPackage.PackageVersion, + ) + "\n\n" + v.Description, + Recommendation: v.NextSteps, + } + } + } + return &pb.Remediation{ + Description: v.Description, + Recommendation: v.NextSteps, + } +} + +func getContainerImage(kubernetes *securitycenter.Kubernetes) string { + if kubernetes == nil { + return "" + } + + if len(kubernetes.Objects) == 0 { + return "" + } + + firstObj := kubernetes.Objects[0] + if len(firstObj.Containers) == 0 { + return "" + } + + // TODO: We assume the first container of the first object is vulnerable - this might be wrong + return fmt.Sprintf(" (%s)", firstObj.Containers[0].ImageId) +} + +// https://cloud.google.com/security-command-center/docs/reference/rest/v1/organizations.sources.findings#findingclass +func fromSccFindingClass(findingClass string) pb.Observation_Category { + switch findingClass { + case "VULNERABILITY": + return pb.Observation_CATEGORY_VULNERABILITY + case "MISCONFIGURATION": + return pb.Observation_CATEGORY_MISCONFIGURATION + case "TOXIC_COMBINATION": + return pb.Observation_CATEGORY_TOXIC_COMBINATION + } + return pb.Observation_CATEGORY_UNKNOWN +} + +func fromSccSeverity(severity string) pb.Severity { + switch severity { + case "CRITICAL": + return pb.Severity_SEVERITY_CRITICAL + case "HIGH": + return pb.Severity_SEVERITY_HIGH + case "MEDIUM": + return pb.Severity_SEVERITY_MEDIUM + case "LOW": + return pb.Severity_SEVERITY_LOW + } + return pb.Severity_SEVERITY_UNKNOWN +} diff --git a/src/collector/gcpcollector/scc_findings_test.go b/src/collector/gcpcollector/scc_findings_test.go new file mode 100644 index 0000000..d5e4370 --- /dev/null +++ b/src/collector/gcpcollector/scc_findings_test.go @@ -0,0 +1,223 @@ +package gcpcollector + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "google.golang.org/api/securitycenter/v1" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/storage/memstorage" +) + +var ( + vuln1 = &securitycenter.Finding{ + CanonicalName: "projects/1234/sources/5678/locations/global/findings/0000", + Name: "projects/1234/sources/5678/locations/global/findings/0000", + Category: "GKE_RUNTIME_OS_VULNERABILITY", + ResourceName: "//container.googleapis.com/projects/modron0test/locations/us-west1/clusters/my-cluster", + Severity: "SEVERITY_UNSPECIFIED", + State: "ACTIVE", + CreateTime: "2024-05-19T13:20:02.112Z", + EventTime: "2024-07-04T15:47:51.202Z", + Vulnerability: &securitycenter.Vulnerability{ + Cve: &securitycenter.Cve{ + Cvssv3: &securitycenter.Cvssv3{ + BaseScore: 5.5, + AttackComplexity: "ATTACK_COMPLEXITY_LOW", + AttackVector: "ATTACK_VECTOR_LOCAL", + AvailabilityImpact: "IMPACT_HIGH", + ConfidentialityImpact: "IMPACT_NONE", + IntegrityImpact: "IMPACT_NONE", + PrivilegesRequired: "PRIVILEGES_REQUIRED_LOW", + Scope: "SCOPE_UNCHANGED", + UserInteraction: "USER_INTERACTION_NONE", + }, + ExploitationActivity: "NO_KNOWN", + Id: "CVE-2024-35863", + Impact: "LOW", + ObservedInTheWild: false, + References: nil, + UpstreamFixAvailable: true, + ZeroDay: false, + }, + OffendingPackage: &securitycenter.Package{ + CpeUri: "cpe:/o:debian:debian_linux:12", + PackageName: "linux", + PackageType: "OS", + PackageVersion: "6.1.76-1", + }, + FixedPackage: &securitycenter.Package{ + CpeUri: "cpe:/o:debian:debian_linux:12", + PackageName: "linux", + PackageType: "OS", + PackageVersion: "6.1.85-1", + }, + SecurityBulletin: nil, + }, + Description: "In the Linux kernel, the following vulnerability has been resolved: smb: client: fix potential UAF in is_valid_oplock_break() Skip sessions that are being teared down (status == SES_EXITING) to avoid UAF.", + NextSteps: "Use the following resources to help you mitigate CVE-2024-35863.\n\n**More information about CVE-2024-35863**\n* https://security-tracker.debian.org/tracker/CVE-2024-35863\n* https://access.redhat.com/security/cve/CVE-2024-35863\n* https://www.suse.com/security/cve/CVE-2024-35863\n* http://people.ubuntu.com/~ubuntu-security/cve/CVE-2024-35863\n\n**Fixed location**\ncpe:/o:debian:debian_linux:12\n\n**Fixed package**\nlinux\n\n**Fixed version**\n6.1.85-1\n", + Kubernetes: &securitycenter.Kubernetes{ + Objects: []*securitycenter.Object{ + { + Kind: "Deployment", + Ns: "default", + Name: "my-deployment", + Containers: []*securitycenter.Container{ + { + Name: "us-west1-docker.pkg.dev/project-id/my-image:latest", + ImageId: "us-west1-docker.pkg.dev/project-id/my-image:latest", + }, + }, + }, + }, + }, + } +) + +func TestFindingToObservation(t *testing.T) { + got := FindingToObservation(vuln1, "projects/modron-test") + + want := &pb.Observation{ + ExpectedValue: structpb.NewStringValue("linux 6.1.85-1"), + ObservedValue: structpb.NewStringValue("linux 6.1.76-1"), + Uid: "projects/1234/sources/5678/locations/global/findings/0000", + Name: "GKE_RUNTIME_OS_VULNERABILITY", + Remediation: &pb.Remediation{ + Description: "CVE-2024-35863 in linux 6.1.76-1 (us-west1-docker.pkg.dev/project-id/my-image:latest): update to linux 6.1.85-1\n\nIn the Linux kernel, the following vulnerability has been resolved: smb: client: fix potential UAF in is_valid_oplock_break() Skip sessions that are being teared down (status == SES_EXITING) to avoid UAF.", + Recommendation: "Use the following resources to help you mitigate CVE-2024-35863.\n\n**More information about CVE-2024-35863**\n* https://security-tracker.debian.org/tracker/CVE-2024-35863\n* https://access.redhat.com/security/cve/CVE-2024-35863\n* https://www.suse.com/security/cve/CVE-2024-35863\n* http://people.ubuntu.com/~ubuntu-security/cve/CVE-2024-35863\n\n**Fixed location**\ncpe:/o:debian:debian_linux:12\n\n**Fixed package**\nlinux\n\n**Fixed version**\n6.1.85-1\n", + }, + ResourceRef: &pb.ResourceRef{ + GroupName: "projects/modron-test", + ExternalId: proto.String("//container.googleapis.com/projects/modron0test/locations/us-west1/clusters/my-cluster/k8s/namespaces/default/apps/deployments/my-deployment"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + ExternalId: proto.String("//securitycenter.googleapis.com/projects/1234/sources/5678/locations/global/findings/0000"), + Source: pb.Observation_SOURCE_SCC, + } + + if diff := cmp.Diff(want, got, protocmp.Transform(), protocmp.IgnoreFields(&pb.Observation{}, "timestamp", "uid")); diff != "" { + t.Errorf("FindingToObservation() mismatch (-want +got):\n%s", diff) + } +} + +func TestListSccFindings(t *testing.T) { + otelMeter := otel.GetMeterProvider() + var mr *metric.ManualReader + if otelMeter == nil { + mr = metric.NewManualReader() + otel.SetMeterProvider( + metric.NewMeterProvider( + metric.WithReader(mr), + ), + ) + } + + ctx := context.Background() + storage := memstorage.New() + gcpCollector := NewFake(ctx, storage, risk.TagConfig{}) + got, err := gcpCollector.ListSccFindings(ctx, "projects/project-id") + if err != nil { + t.Fatalf("ListSccFindings: %v", err) + } + + want := []*pb.Observation{ + { + Name: "SQL_PUBLIC_IP", + Remediation: &pb.Remediation{ + Description: "To lower your attack surface, Cloud SQL databases should not have public IPs. Private IPs provide improved network security and lower latency for your application.", + Recommendation: "Go to https://console.cloud.google.com/sql/instances/xyz/connections?project=project-id and click the \"Networking\" tab. Uncheck the \"Public IP\" checkbox and click \"SAVE\". If your instance is not configured to use a private IP, you will first have to enable private IP by following the instructions here: https://cloud.google.com/sql/docs/mysql/configure-private-ip#existing-private-instance", + }, + ExternalId: proto.String("//securitycenter.googleapis.com/projects/12345/sources/123/findings/48230f1978594ffb9d09a3cb1fe5e1b3"), + Source: pb.Observation_SOURCE_SCC, + Severity: pb.Severity_SEVERITY_MEDIUM, + Category: pb.Observation_CATEGORY_MISCONFIGURATION, + ResourceRef: &pb.ResourceRef{ + ExternalId: proto.String("//cloudsql.googleapis.com/projects/project-id/instances/xyz"), + CloudPlatform: pb.CloudPlatform_GCP, + GroupName: "projects/project-id", + }, + }, + } + if diff := cmp.Diff(want, got, protocmp.Transform(), protocmp.IgnoreFields(&pb.Observation{}, "timestamp", "uid")); diff != "" { + t.Errorf("ListSccFindings() mismatch (-want +got):\n%s", diff) + } + + if mr != nil { + resourceMetrics := metricdata.ResourceMetrics{} + if err := mr.Collect(context.Background(), &resourceMetrics); err != nil { + t.Fatalf("Collect: %v", err) + } + + metricScope := "github.com/nianticlabs/modron/src/collector/gcpcollector" + gotMetrics := getMetrics(resourceMetrics, metricScope) + if gotMetrics == nil { + t.Fatalf("no metrics found for scope %q", metricScope) + } + + wantMetrics := []metricdata.Metrics{ + { + Name: "modron_scc_collected_observations", + Description: "Number of collected observations from SCC", + Data: metricdata.Sum[int64]{ + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: attribute.NewSet( + attribute.String("category", "SQL_PUBLIC_IP"), + attribute.String("severity", "MEDIUM"), + ), + Value: 1, + }, + }, + }, + }, + } + + if diff := cmp.Diff(wantMetrics, gotMetrics, metricsCmpOpts()...); diff != "" { + t.Errorf("ResourceMetrics mismatch (-want +got):\n%s", diff) + } + } else { + t.Log("The test was run in parallel mode, thus we'll not check the result of the metrics (partial!)") + } + +} + +// Adapted from https://github.com/temporalio/temporal/blob/8752a90c5b15851d81c7aff98e0fe94a6cb57d13/common/metrics/otel_metrics_handler_test.go#L200-L218 +func metricsCmpOpts() []cmp.Option { + return []cmp.Option{ + cmp.Comparer(func(e1, e2 metricdata.Extrema[int64]) bool { + v1, ok1 := e1.Value() + v2, ok2 := e2.Value() + return ok1 && ok2 && v1 == v2 + }), + cmp.Comparer(func(a1, a2 attribute.Set) bool { + return a1.Equals(&a2) + }), + cmpopts.SortSlices(func(x, y metricdata.Metrics) bool { + return x.Name < y.Name + }), + cmpopts.IgnoreFields(metricdata.DataPoint[int64]{}, "StartTime", "Time"), + cmpopts.IgnoreFields(metricdata.DataPoint[float64]{}, "StartTime", "Time"), + cmpopts.IgnoreFields(metricdata.Sum[int64]{}, "Temporality", "IsMonotonic"), + cmpopts.IgnoreFields(metricdata.HistogramDataPoint[int64]{}, "StartTime", "Time", "Bounds"), + } +} + +func getMetrics(resourceMetrics metricdata.ResourceMetrics, s string) []metricdata.Metrics { + for _, v := range resourceMetrics.ScopeMetrics { + if v.Scope.Name == s { + return v.Metrics + } + } + return nil +} diff --git a/src/collector/gcpcollector/service_account.go b/src/collector/gcpcollector/service_account.go new file mode 100644 index 0000000..b93dcc3 --- /dev/null +++ b/src/collector/gcpcollector/service_account.go @@ -0,0 +1,153 @@ +package gcpcollector + +import ( + "fmt" + "time" + + "google.golang.org/api/iam/v1" + + "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" + + "golang.org/x/net/context" + "google.golang.org/api/monitoring/v3" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + serviceAccountResourcePath = "%s/serviceAccounts/%s" + serviceAccountKeyUnusedMaxDelay = "100d" + monitoringPageSize = 1000 +) + +func toIamPolicy(policy *iam.Policy) *pb.IamPolicy { + if policy == nil { + return nil + } + iamPolicy := &pb.IamPolicy{} + for _, binding := range policy.Bindings { + role := constants.ToRole(binding.Role).String() + iamPolicy.Permissions = append(iamPolicy.Permissions, &pb.Permission{ + Role: role, + Principals: binding.Members, + }) + } + return iamPolicy +} + +func (collector *GCPCollector) ListServiceAccounts(ctx context.Context, rgName string) (serviceAccounts []*pb.Resource, err error) { + name := constants.ResourceWithProjectsPrefix(rgName) + serviceAccountsList, err := collector.api.ListServiceAccount(ctx, name) + if err != nil { + return nil, err + } + for _, account := range serviceAccountsList { + iamPolicy, err := collector.api.GetServiceAccountIAMPolicy(ctx, account.Name) + if err != nil { + log.Warnf("cannot get IAM policies for service account %q: %v", account.Email, err) + } + serviceAccounts = append(serviceAccounts, &pb.Resource{ + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Name: account.Email, + Parent: rgName, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, + }, + }, + IamPolicy: toIamPolicy(iamPolicy), + }) + } + resources := serviceAccounts + for _, serviceAccount := range serviceAccounts { + userKeys, err := collector.GetServiceAccountKeys(ctx, rgName, serviceAccount) + if err != nil { + return nil, err + } + resources = append(resources, userKeys...) + } + return resources, nil +} + +// Note: also update the Service Account passed by reference by adding its service accountkeys +func (collector *GCPCollector) GetServiceAccountKeys(ctx context.Context, rgName string, serviceAccount *pb.Resource) (userKeys []*pb.Resource, err error) { + name := fmt.Sprintf(serviceAccountResourcePath, constants.ResourceWithProjectsPrefix(rgName), serviceAccount.Name) + + keys, err := collector.api.ListServiceAccountKeys(ctx, name) + if err != nil { + return nil, err + } + for _, key := range keys { + if key.KeyType != "USER_MANAGED" { + continue + } + + keyCreationDate, err := time.Parse(time.RFC3339, key.ValidAfterTime) + if err != nil { + return nil, fmt.Errorf("serviceAccountKey.CreationDate: %w", err) + } + keyExpirationDate, err := time.Parse(time.RFC3339, key.ValidBeforeTime) + if err != nil { + return nil, fmt.Errorf("serviceAccountKey.ExpirationDate: %w", err) + } + + keyExported := &pb.ExportedCredentials{ + CreationDate: timestamppb.New(keyCreationDate), + ExpirationDate: timestamppb.New(keyExpirationDate), + } + + keyID := utils.GetKeyID(key.Name) + lastUsage, err := collector.GetServiceAccountKeyLastUsage(ctx, rgName, keyID) + if err != nil { + log.Warnf("cannot get key usage %q: %v", key.Name, err) + // Need to return here as otherwise the object is created with the default time value EPOCH. + return nil, fmt.Errorf("serviceAccountKey usage: %w", err) + } + keyExported.LastUsage = timestamppb.New(lastUsage) + serviceAccount.GetServiceAccount().ExportedCredentials = append(serviceAccount.GetServiceAccount().ExportedCredentials, keyExported) + userKeys = append(userKeys, &pb.Resource{ + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Name: key.Name, + Parent: serviceAccount.Name, + Type: &pb.Resource_ExportedCredentials{ + ExportedCredentials: keyExported, + }, + }) + } + return userKeys, nil +} + +func (collector *GCPCollector) GetServiceAccountKeyLastUsage(ctx context.Context, rgName string, keyID string) (time.Time, error) { + request := new(monitoring.QueryTimeSeriesRequest) + request.Query = fmt.Sprintf( + "fetch iam_service_account | metric 'iam.googleapis.com/service_account/key/authn_events_count' | filter (metric.key_id == '%s') | within %s", + keyID, + serviceAccountKeyUnusedMaxDelay, + ) + request.PageSize = monitoringPageSize + + lastUsage := time.Time{} + err := collector.api.ListServiceAccountKeyUsage(ctx, rgName, request). + Pages(ctx, func(qtsr *monitoring.QueryTimeSeriesResponse) error { + for _, timeData := range qtsr.TimeSeriesData { + for _, pd := range timeData.PointData { + timeF, err := time.Parse(time.RFC3339, pd.TimeInterval.EndTime) + if err != nil { + return fmt.Errorf("GetServiceAccountKeyUsage.convertingTime: %w", err) + } + if timeF.After(lastUsage) { + lastUsage = timeF + } + } + } + return nil + }) + if err != nil { + return time.Time{}, fmt.Errorf("GetServiceAccountKeyUsage: query: %q, err: %w", request.Query, err) + } + return lastUsage, nil +} diff --git a/src/collector/gcpcollector/service_account_test.go b/src/collector/gcpcollector/service_account_test.go new file mode 100644 index 0000000..49b62c0 --- /dev/null +++ b/src/collector/gcpcollector/service_account_test.go @@ -0,0 +1,30 @@ +//go:build integration + +package gcpcollector + +import ( + "context" + "os" + "testing" + + "github.com/nianticlabs/modron/src/constants" +) + +func TestListServiceAccounts(t *testing.T) { + ctx := context.Background() + coll, _ := getCollector(ctx, t) + project := constants.GCPProjectsNamePrefix + os.Getenv("PROJECT_ID") + accounts, err := coll.(*GCPCollector).ListServiceAccounts(ctx, project) + if err != nil { + t.Fatalf("ListServiceAccounts failed: %v", err) + } + if len(accounts) == 0 { + t.Fatalf("ListServiceAccounts returned 0 accounts") + } + for _, account := range accounts { + t.Logf("ServiceAccount: %s", account.Name) + if account.IamPolicy != nil && len(account.IamPolicy.Permissions) > 0 { + t.Logf("\tIAM Policy: %v", account.IamPolicy) + } + } +} diff --git a/src/collector/gcpcollector/spanner.go b/src/collector/gcpcollector/spanner.go index c82297c..87ef29e 100644 --- a/src/collector/gcpcollector/spanner.go +++ b/src/collector/gcpcollector/spanner.go @@ -3,25 +3,24 @@ package gcpcollector import ( "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" "golang.org/x/net/context" ) -func (collector *GCPCollector) ListSpannerDatabases(ctx context.Context, resourceGroup *pb.Resource) ([]*pb.Resource, error) { - name := constants.ResourceWithProjectsPrefix(resourceGroup.Name) +func (collector *GCPCollector) ListSpannerDatabases(ctx context.Context, rgName string) (resources []*pb.Resource, err error) { + name := constants.ResourceWithProjectsPrefix(rgName) dbs, err := collector.api.ListSpannerDatabases(ctx, name) if err != nil { return nil, err } - resources := []*pb.Resource{} for _, database := range dbs { dbResource := &pb.Resource{ // TODO: Collect IAM Policy - Uid: common.GetUUID(3), - ResourceGroupName: resourceGroup.Name, + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, Name: database.Name, - Parent: resourceGroup.Name, + Parent: rgName, Type: &pb.Resource_Database{ Database: &pb.Database{ Type: "spanner", diff --git a/src/collector/gcpcollector/vm_instance.go b/src/collector/gcpcollector/vm_instance.go new file mode 100644 index 0000000..5a20dc1 --- /dev/null +++ b/src/collector/gcpcollector/vm_instance.go @@ -0,0 +1,43 @@ +package gcpcollector + +import ( + "github.com/nianticlabs/modron/src/common" + pb "github.com/nianticlabs/modron/src/proto/generated" + + "golang.org/x/net/context" +) + +func (collector *GCPCollector) ListVMInstances(ctx context.Context, rgName string) (vmInstances []*pb.Resource, err error) { + instances, err := collector.api.ListInstances(ctx, rgName) + if err != nil { + return nil, err + } + for _, instance := range instances { + name := instance.Name + privateIP, publicIP := "", "" + for _, networkInterface := range instance.NetworkInterfaces { + privateIP = networkInterface.NetworkIP + for _, accessConfig := range networkInterface.AccessConfigs { + publicIP = accessConfig.NatIP + } + } + serviceAccountName := "" + for _, sa := range instance.ServiceAccounts { + serviceAccountName = sa.Email + } + vmInstances = append(vmInstances, &pb.Resource{ + Uid: common.GetUUID(uuidGenRetries), + ResourceGroupName: rgName, + Name: name, + Parent: rgName, + Type: &pb.Resource_VmInstance{ + VmInstance: &pb.VmInstance{ + PublicIp: publicIP, + PrivateIp: privateIP, + Identity: serviceAccountName, + }, + }, + }) + } + return vmInstances, nil +} diff --git a/src/collector/testcollector/testcollector.go b/src/collector/testcollector/testcollector.go new file mode 100644 index 0000000..02a6a16 --- /dev/null +++ b/src/collector/testcollector/testcollector.go @@ -0,0 +1,56 @@ +package testcollector + +import ( + "fmt" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + + "golang.org/x/net/context" +) + +var _ model.Collector = (*TestCollector)(nil) + +var errNotImplemented = fmt.Errorf("not implemented") + +type TestCollector struct{} + +func (t TestCollector) CollectAndStoreAll(_ context.Context, _ string, _ []string, _ []*pb.Resource) error { + return errNotImplemented +} + +func (t TestCollector) ListResourceGroupObservations(_ context.Context, _ string, _ string) ([]*pb.Observation, []error) { + return nil, []error{errNotImplemented} +} + +func (t TestCollector) GetResourceGroupWithIamPolicy(_ context.Context, _ string, _ string) (*pb.Resource, error) { + return nil, errNotImplemented +} + +func (t TestCollector) ListResourceGroups(_ context.Context, _ []string) ([]*pb.Resource, error) { + return nil, errNotImplemented +} + +func (t TestCollector) ListResourceGroupsWithIamPolicies(_ context.Context, _ []string) ([]*pb.Resource, error) { + return nil, errNotImplemented +} + +func (t TestCollector) ListResourceGroupNames(_ context.Context) ([]string, error) { + return nil, errNotImplemented +} + +func (t TestCollector) ListResourceGroupAdmins(_ context.Context) (model.ACLCache, error) { + return model.ACLCache{ + "*": { + "projects/modron-test": {}, + "projects/super-secret": {}, + }, + "user@example.com": { + "projects/modron-test": {}, + }, + }, nil +} + +func (t TestCollector) ListResourceGroupResources(_ context.Context, _ string, _ string) ([]*pb.Resource, []error) { + return nil, []error{errNotImplemented} +} diff --git a/src/common/protoutils.go b/src/common/protoutils.go index 8821b8d..9432554 100644 --- a/src/common/protoutils.go +++ b/src/common/protoutils.go @@ -9,21 +9,23 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" ) const ( - ResourceApiKey = "ApiKey" + ResourceAPIKey = "ApiKey" ResourceBucket = "Bucket" - ResourceExportedCredentials = "ExportedCredentials" + ResourceDatabase = "Database" + ResourceExportedCredentials = "ExportedCredentials" //nolint:gosec + ResourceGroup = "Group" ResourceKubernetesCluster = "KubernetesCluster" ResourceLoadBalancer = "LoadBalancer" + ResourceNamespace = "Namespace" ResourceNetwork = "Network" + ResourcePod = "Pod" ResourceResourceGroup = "ResourceGroup" ResourceServiceAccount = "ServiceAccount" - ResourceVmInstance = "VmInstance" - ResourceDatabase = "Database" - ResourceGroup = "Group" + ResourceVMInstance = "VmInstance" ) // See `Ssl` @@ -33,39 +35,6 @@ const ( CertificateUnknown = "TYPE_UNSPECIFIED" ) -func TypeFromResourceAsString(rsrc *pb.Resource) (ty string, err error) { - if rsrc == nil { - return "", fmt.Errorf("resource must not be nil") - } - switch rsrc.Type.(type) { - case *pb.Resource_ApiKey: - ty = ResourceApiKey - case *pb.Resource_Bucket: - ty = ResourceBucket - case *pb.Resource_ExportedCredentials: - ty = ResourceExportedCredentials - case *pb.Resource_KubernetesCluster: - ty = ResourceKubernetesCluster - case *pb.Resource_LoadBalancer: - ty = ResourceLoadBalancer - case *pb.Resource_Network: - ty = ResourceNetwork - case *pb.Resource_ResourceGroup: - ty = ResourceResourceGroup - case *pb.Resource_ServiceAccount: - ty = ResourceServiceAccount - case *pb.Resource_VmInstance: - ty = ResourceVmInstance - case *pb.Resource_Database: - ty = ResourceDatabase - case *pb.Resource_Group: - ty = ResourceGroup - default: - err = fmt.Errorf("unknown resource type %q", rsrc.Type) - } - return -} - func TypeFromSslCertificate(cert *compute.SslCertificate) (ty pb.Certificate_Type, err error) { switch cert.Type { case CertificateManaged: @@ -82,13 +51,13 @@ func TypeFromSslCertificate(cert *compute.SslCertificate) (ty pb.Certificate_Typ // TODO: Cast without (un)marshaling if possible. func ResourceFromStructValue(value *structpb.Value) (*pb.Resource, error) { - valueJson, err := protojson.Marshal(value) + valueJSON, err := protojson.Marshal(value) if err != nil { return nil, err } rsrc := &pb.Resource{} - if err := protojson.Unmarshal(valueJson[:], rsrc); err != nil { + if err := protojson.Unmarshal(valueJSON, rsrc); err != nil { return nil, err } @@ -97,13 +66,13 @@ func ResourceFromStructValue(value *structpb.Value) (*pb.Resource, error) { // TODO: Cast without (un)marshaling if possible. func StructValueFromResource(rsrc *pb.Resource) (*structpb.Value, error) { - rsrcJson, err := protojson.Marshal(rsrc) + rsrcJSON, err := protojson.Marshal(rsrc) if err != nil { return nil, err } value := &structpb.Value{} - if err := protojson.Unmarshal(rsrcJson[:], value); err != nil { + if err := protojson.Unmarshal(rsrcJSON, value); err != nil { return nil, err } diff --git a/src/constants/constants.go b/src/constants/constants.go index 8fc1db8..4c62a74 100644 --- a/src/constants/constants.go +++ b/src/constants/constants.go @@ -2,29 +2,86 @@ package constants import ( "strings" + + pb "github.com/nianticlabs/modron/src/proto/generated" ) +type Role string + const ( - OrgIdEnvVar = "ORG_ID" - OrgSuffixEnvVar = "ORG_SUFFIX" + GCPEditorRole Role = "editor" + GCPOwnerRole Role = "owner" + GCPSecurityAdminRole Role = "iam.securityAdmin" + GCPViewerRole Role = "viewer" +) - GCPEditorRole = "editor" - GCPOwnerRole = "owner" - GCPSecurityAdminRole = "iam.securityAdmin" +func (r Role) String() string { + return string(r) +} - GCPOrgIdPrefix = "organizations/" - GCPFolderIdPrefix = "folders/" +const ( + GCPOrgIDPrefix = "organizations/" + GCPFolderIDPrefix = "folders/" GCPProjectsNamePrefix = "projects/" GCPRolePrefix = "roles/" + GCPSysProjectPrefix = "sys-" GCPAccountGroupPrefix = "group:" GCPServiceAccountPrefix = "serviceAccount:" GCPUserAccountPrefix = "user:" + + MetricsPrefix = "modron_" + + ResourceLabelCustomerData = "customer_data" + ResourceLabelEmployeeData = "employee_data" + + LabelContact1 = "contact1" + LabelContact2 = "contact2" + + ImpactEmployeeData = pb.Impact_IMPACT_MEDIUM + ImpactCustomerData = pb.Impact_IMPACT_HIGH +) + +const ( + LogKeyCollectID = "collect_id" + LogKeyCollector = "collector" + LogKeyObservationName = "observation_name" + LogKeyObservationUID = "observation_uid" + LogKeyPkg = "package" + LogKeyResourceGroup = "resource_group" + LogKeyResourceGroupNames = "resource_group_names" + LogKeyRule = "rule" + LogKeyScanID = "scan_id" +) + +const ( + TraceKeyCollectID = "collect_id" + TraceKeyCollector = "collector" + TraceKeyMethod = "method" + TraceKeyName = "name" + TraceKeyNumNotifications = "num_notifications" + TraceKeyNumObservations = "num_observations" + TraceKeyNumResources = "num_resources" + TraceKeyObservationUID = "observation_uid" + TraceKeyPath = "path" + TraceKeyResourceGroup = "resource_group" + TraceKeyResourceGroupNames = "resource_group_names" + TraceKeyRule = "rule" + TraceKeyScanID = "scan_id" + TraceKeyScanType = "scan_type" ) -var AdminRoles = map[string]struct{}{ - strings.ToLower(GCPOwnerRole): {}, - strings.ToLower(GCPEditorRole): {}, - strings.ToLower(GCPSecurityAdminRole): {}, +const ( + MetricKeyStatus = "status" +) + +var AdminRoles = map[Role]struct{}{ + GCPOwnerRole: {}, + GCPEditorRole: {}, + GCPSecurityAdminRole: {}, +} + +func ToRole(role string) Role { + return Role(strings.TrimPrefix(role, GCPRolePrefix)) } func ResourceWithProjectsPrefix(resourceName string) string { diff --git a/src/constants/context.go b/src/constants/context.go new file mode 100644 index 0000000..7ed0944 --- /dev/null +++ b/src/constants/context.go @@ -0,0 +1,8 @@ +package constants + +type runnerCtxKey string + +const ( + CollectIDKey runnerCtxKey = "collect_id" + ScanIDKey runnerCtxKey = "scan_id" +) diff --git a/src/constants/gcp_sa_projects.go b/src/constants/gcp_sa_projects.go new file mode 100644 index 0000000..9c49768 --- /dev/null +++ b/src/constants/gcp_sa_projects.go @@ -0,0 +1,254 @@ +package constants + +// GCPServiceAgentsProjects is generated using the utils/gcp_service_agents tool. +// The result (out.json) is then passed to jq as follows, and the content copy & pasted here +// jq -r '.projects[] | "\"" + . + "\"" + ": {},"' out.json | clipcopy +var GCPServiceAgentsProjects = map[string]struct{}{ + "appsheet-prod-service-accounts": {}, + "bigquery-encryption": {}, + "cloud-cdn-fill": {}, + "cloud-filer": {}, + "cloud-memcache-sa": {}, + "cloud-ml.google.com": {}, + "cloud-redis": {}, + "cloud-tpu": {}, + "cloudcomposer-accounts": {}, + "compute-system": {}, + "container-analysis": {}, + "container-engine-robot": {}, + "containerregistry": {}, + "crashlytics-bigquery-prod": {}, + "dataflow-service-producer-prod": {}, + "dataproc-accounts": {}, + "dlp-api": {}, + "fcm-bq-export-prod": {}, + "firebase-rules": {}, + "firebase-sa-management": {}, + "gae-api-prod.google.com": {}, + "gcf-admin-robot": {}, + "gcp-gae-service": {}, + "gcp-ri-aiplatform": {}, + "gcp-ri-identitypool": {}, + "gcp-sa-accessapproval": {}, + "gcp-sa-adsdatahub": {}, + "gcp-sa-aiplatform": {}, + "gcp-sa-aiplatform-cc": {}, + "gcp-sa-aiplatform-ft": {}, + "gcp-sa-aiplatform-is": {}, + "gcp-sa-aiplatform-re": {}, + "gcp-sa-aiplatform-vm": {}, + "gcp-sa-alloydb": {}, + "gcp-sa-anthos": {}, + "gcp-sa-anthosaudit": {}, + "gcp-sa-anthosconfigmanagement": {}, + "gcp-sa-anthosidentityservice": {}, + "gcp-sa-anthospolicycontroller": {}, + "gcp-sa-anthossupport": {}, + "gcp-sa-apigateway": {}, + "gcp-sa-apigateway-mgmt": {}, + "gcp-sa-apigee": {}, + "gcp-sa-apigeeregistry": {}, + "gcp-sa-apihub": {}, + "gcp-sa-apikeys": {}, + "gcp-sa-apim": {}, + "gcp-sa-appdevexperience": {}, + "gcp-sa-apphub": {}, + "gcp-sa-artifactregistry": {}, + "gcp-sa-asm-hpsa": {}, + "gcp-sa-assuredoss": {}, + "gcp-sa-assuredworkloads": {}, + "gcp-sa-audit-manager": {}, + "gcp-sa-automl": {}, + "gcp-sa-backupdr": {}, + "gcp-sa-backupdr-pr": {}, + "gcp-sa-backupdr-run": {}, + "gcp-sa-bigquery-condel": {}, + "gcp-sa-bigquery-consp": {}, + "gcp-sa-bigqueryconnection": {}, + "gcp-sa-bigquerydatatransfer": {}, + "gcp-sa-bigqueryri": {}, + "gcp-sa-bigqueryspark": {}, + "gcp-sa-bigquerytardis": {}, + "gcp-sa-bigtable": {}, + "gcp-sa-binaryauthorization": {}, + "gcp-sa-bms": {}, + "gcp-sa-bne": {}, + "gcp-sa-bundles": {}, + "gcp-sa-ccai-cmek": {}, + "gcp-sa-ccaip": {}, + "gcp-sa-ccinsights-cmek": {}, + "gcp-sa-certificatemanager": {}, + "gcp-sa-chronicle": {}, + "gcp-sa-chronicle-soar": {}, + "gcp-sa-chronicle-spanner": {}, + "gcp-sa-chronicle-sv": {}, + "gcp-sa-cloud-cw": {}, + "gcp-sa-cloud-cw-cmek": {}, + "gcp-sa-cloud-ekg": {}, + "gcp-sa-cloud-sql": {}, + "gcp-sa-cloud-trace": {}, + "gcp-sa-cloudaicompanion": {}, + "gcp-sa-cloudasset": {}, + "gcp-sa-cloudbatch": {}, + "gcp-sa-cloudbuild": {}, + "gcp-sa-cloudcontrolspartner": {}, + "gcp-sa-clouddeploy": {}, + "gcp-sa-cloudkms": {}, + "gcp-sa-cloudoptim": {}, + "gcp-sa-cloudscheduler": {}, + "gcp-sa-cloudtasks": {}, + "gcp-sa-compute-usage": {}, + "gcp-sa-config": {}, + "gcp-sa-configdelivery": {}, + "gcp-sa-connectors": {}, + "gcp-sa-contactcenterinsights": {}, + "gcp-sa-containerscanning": {}, + "gcp-sa-containersec": {}, + "gcp-sa-dataconnectors": {}, + "gcp-sa-dataform": {}, + "gcp-sa-datafusion": {}, + "gcp-sa-datalabeling": {}, + "gcp-sa-datamigration": {}, + "gcp-sa-datapipelines": {}, + "gcp-sa-dataplex": {}, + "gcp-sa-dataprocrmnode": {}, + "gcp-sa-datastream": {}, + "gcp-sa-datastudio": {}, + "gcp-sa-dep": {}, + "gcp-sa-devconnect": {}, + "gcp-sa-dialogflow": {}, + "gcp-sa-dialogflow-cmek": {}, + "gcp-sa-discoveryengine": {}, + "gcp-sa-dns": {}, + "gcp-sa-edgecontainer": {}, + "gcp-sa-edgecontainercluster": {}, + "gcp-sa-edgecontainergcr": {}, + "gcp-sa-effectivepolicy": {}, + "gcp-sa-ekms": {}, + "gcp-sa-endpoints": {}, + "gcp-sa-eventarc": {}, + "gcp-sa-firebase": {}, + "gcp-sa-firebaseappcheck": {}, + "gcp-sa-firebaseapphosting": {}, + "gcp-sa-firebasedatabase": {}, + "gcp-sa-firebaseml": {}, + "gcp-sa-firebasemods": {}, + "gcp-sa-firebasestorage": {}, + "gcp-sa-firestore": {}, + "gcp-sa-firewallinsights": {}, + "gcp-sa-fs-spanner": {}, + "gcp-sa-gkebackup": {}, + "gcp-sa-gkedataplanev2": {}, + "gcp-sa-gkehub": {}, + "gcp-sa-gkemulticloud": {}, + "gcp-sa-gkemulticloudcontainer": {}, + "gcp-sa-gkemulticloudcpmachine": {}, + "gcp-sa-gkemulticloudnpmachine": {}, + "gcp-sa-gkenode": {}, + "gcp-sa-gkeonprem": {}, + "gcp-sa-gsuiteaddons": {}, + "gcp-sa-healthcare": {}, + "gcp-sa-iap": {}, + "gcp-sa-identitytoolkit": {}, + "gcp-sa-integrations": {}, + "gcp-sa-issuerswitch": {}, + "gcp-sa-ivs": {}, + "gcp-sa-krmapihosting": {}, + "gcp-sa-krmapihosting-dataplane": {}, + "gcp-sa-ktd-control": {}, + "gcp-sa-ktd-hpsa": {}, + "gcp-sa-lifesciences": {}, + "gcp-sa-livestream": {}, + "gcp-sa-logging": {}, + "gcp-sa-looker": {}, + "gcp-sa-managedflink": {}, + "gcp-sa-managedkafka": {}, + "gcp-sa-mcmetering": {}, + "gcp-sa-mcsd": {}, + "gcp-sa-memorystore": {}, + "gcp-sa-meshconfig": {}, + "gcp-sa-meshcontrolplane": {}, + "gcp-sa-meshdataplane": {}, + "gcp-sa-metastore": {}, + "gcp-sa-mi": {}, + "gcp-sa-migcenter": {}, + "gcp-sa-monitoring": {}, + "gcp-sa-monitoring-notification": {}, + "gcp-sa-multiclusteringress": {}, + "gcp-sa-netapp": {}, + "gcp-sa-networkactions": {}, + "gcp-sa-networkconnectivity": {}, + "gcp-sa-networkmanagement": {}, + "gcp-sa-networksecurity": {}, + "gcp-sa-notebooks": {}, + "gcp-sa-notebooksecurityscanner": {}, + "gcp-sa-nss-hpsa": {}, + "gcp-sa-oci": {}, + "gcp-sa-ondemandscanning": {}, + "gcp-sa-osconfig": {}, + "gcp-sa-osconfig-rollout": {}, + "gcp-sa-othercloudcfg": {}, + "gcp-sa-pam": {}, + "gcp-sa-parallelstore": {}, + "gcp-sa-playbooks": {}, + "gcp-sa-privateca": {}, + "gcp-sa-prod-bigqueryomni": {}, + "gcp-sa-prod-dai-core": {}, + "gcp-sa-pubsub": {}, + "gcp-sa-pubsublite": {}, + "gcp-sa-rbe": {}, + "gcp-sa-recommendationengine": {}, + "gcp-sa-remotebuild": {}, + "gcp-sa-retail": {}, + "gcp-sa-riskmanager": {}, + "gcp-sa-rma": {}, + "gcp-sa-routeoptim": {}, + "gcp-sa-runapps": {}, + "gcp-sa-scc-notification": {}, + "gcp-sa-scc-vmtd": {}, + "gcp-sa-secretmanager": {}, + "gcp-sa-securewebproxy": {}, + "gcp-sa-securitycenter": {}, + "gcp-sa-servicedirectory": {}, + "gcp-sa-servicemesh": {}, + "gcp-sa-sourcemanager": {}, + "gcp-sa-spanner": {}, + "gcp-sa-spectrumsas": {}, + "gcp-sa-speech": {}, + "gcp-sa-storageinsights": {}, + "gcp-sa-stream": {}, + "gcp-sa-tpu": {}, + "gcp-sa-transcoder": {}, + "gcp-sa-transferappliance": {}, + "gcp-sa-translation": {}, + "gcp-sa-v1-remediator": {}, + "gcp-sa-vertex-agent": {}, + "gcp-sa-vertex-bp": {}, + "gcp-sa-vertex-es": {}, + "gcp-sa-vertex-eval": {}, + "gcp-sa-vertex-ex": {}, + "gcp-sa-vertex-ex-cc": {}, + "gcp-sa-vertex-mm": {}, + "gcp-sa-vertex-nb": {}, + "gcp-sa-vertex-op": {}, + "gcp-sa-vertex-rag": {}, + "gcp-sa-vertex-shtune": {}, + "gcp-sa-vertex-tune": {}, + "gcp-sa-visionai": {}, + "gcp-sa-vmmigration": {}, + "gcp-sa-vmwareengine": {}, + "gcp-sa-vpcaccess": {}, + "gcp-sa-websecurityscanner": {}, + "gcp-sa-workflows": {}, + "gcp-sa-workloadmanager": {}, + "gcp-sa-workstations": {}, + "gcp-sa-workstationsvm": {}, + "gs-project-accounts": {}, + "performance-bq-export-prod": {}, + "remotebuildexecution": {}, + "security-center-api": {}, + "serverless-robot-prod": {}, + "service-consumer-management": {}, + "service-networking": {}, + "storage-transfer-service": {}, +} diff --git a/src/engine/framework.go b/src/engine/framework.go index 2d661ff..864c231 100644 --- a/src/engine/framework.go +++ b/src/engine/framework.go @@ -6,7 +6,6 @@ import ( "encoding/base64" "fmt" "net/http" - "sync" "time" "golang.org/x/oauth2" @@ -17,8 +16,9 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" ) type TransportProvider func(ctx context.Context, cluster *container.Cluster) (http.RoundTripper, error) @@ -28,8 +28,6 @@ type Storage struct { } var ( - storage *Storage - memoizationMap sync.Map cleanupInterval = 30 * time.Minute cleanupTicker = time.NewTicker(cleanupInterval) ) @@ -39,8 +37,15 @@ type cachedResource struct { timestamp time.Time } -func GetResource(ctx context.Context, resourceName string) (*pb.Resource, error) { - if cache, exists := memoizationMap.Load(resourceName); exists { +func (e *RuleEngine) GetChildren(ctx context.Context, parent string) ([]*pb.Resource, error) { + filter := model.StorageFilter{ + ParentNames: []string{parent}, + } + return e.storage.ListResources(ctx, filter) +} + +func (e *RuleEngine) GetResource(ctx context.Context, resourceName string) (*pb.Resource, error) { + if cache, exists := e.memoizationMap.Load(resourceName); exists { res := cache.(*cachedResource) return res.resource, nil } @@ -49,7 +54,13 @@ func GetResource(ctx context.Context, resourceName string) (*pb.Resource, error) Limit: 1, ResourceNames: []string{resourceName}, } - res, err := storage.Storage.ListResources(ctx, filter) + collectionID, ok := ctx.Value(constants.CollectIDKey).(string) + if ok { + filter.OperationID = collectionID + } else { + log.Warnf("collection ID not found in context") + } + res, err := e.storage.ListResources(ctx, filter) if err != nil { return nil, fmt.Errorf("resource %q could not be fetched: %w", resourceName, err) } @@ -61,26 +72,21 @@ func GetResource(ctx context.Context, resourceName string) (*pb.Resource, error) resource: res[0], timestamp: time.Now(), } - memoizationMap.Store(resourceName, cachedRes) - + e.memoizationMap.Store(resourceName, cachedRes) return res[0], nil } -func init() { - go startCacheCleanup() -} - -func startCacheCleanup() { +func (e *RuleEngine) startCacheCleanup() { for range cleanupTicker.C { - clearExpiredResources() + e.clearExpiredResources() } } -func clearExpiredResources() { - memoizationMap.Range(func(key, value interface{}) bool { +func (e *RuleEngine) clearExpiredResources() { + e.memoizationMap.Range(func(key, value interface{}) bool { cachedRes := value.(*cachedResource) if time.Since(cachedRes.timestamp) >= cleanupInterval { - memoizationMap.Delete(key) + e.memoizationMap.Delete(key) } return true }) @@ -89,7 +95,7 @@ func clearExpiredResources() { func GetKubernetesClient(ctx context.Context, clusterName string, httpClient *http.Client, getTransport TransportProvider) (*kubernetes.Clientset, error) { tokenSource, err := google.DefaultTokenSource(ctx, compute.CloudPlatformScope) if err != nil { - return nil, fmt.Errorf("failed to get a token source: %v", err) + return nil, fmt.Errorf("failed to get a token source: %w", err) } if httpClient == http.DefaultClient { httpClient = oauth2.NewClient(ctx, tokenSource) @@ -97,14 +103,14 @@ func GetKubernetesClient(ctx context.Context, clusterName string, httpClient *ht } containerService, err := container.NewService(ctx, option.WithHTTPClient(httpClient)) if err != nil { - return nil, fmt.Errorf("could not create client for Google Container Engine: %v", err) + return nil, fmt.Errorf("could not create client for Google Container Engine: %w", err) } cluster, err := containerService.Projects.Locations.Clusters.Get(clusterName).Context(ctx).Do() if err != nil { - return nil, fmt.Errorf("cluster %q: %v", clusterName, err) + return nil, fmt.Errorf("cluster %q: %w", clusterName, err) } - // This is a very ugly dependency injection but we have to do it otherwise unittesting would require a complete oauth2 backend. + // This is a very ugly dependency injection, but we have to do it otherwise the unit test would require a complete oauth2 backend. tr, err := getTransport(ctx, cluster) if err != nil { return nil, err @@ -118,7 +124,7 @@ func GetKubernetesClient(ctx context.Context, clusterName string, httpClient *ht kubeHTTPClient, ) if err != nil { - return nil, fmt.Errorf("kubernetes HTTP client could not be created: %v", err) + return nil, fmt.Errorf("kubernetes HTTP client could not be created: %w", err) } return kubeClient, nil } @@ -126,13 +132,13 @@ func GetKubernetesClient(ctx context.Context, clusterName string, httpClient *ht func GetOauthTransport(ctx context.Context, cluster *container.Cluster) (http.RoundTripper, error) { tokenSource, err := google.DefaultTokenSource(ctx, compute.CloudPlatformScope) if err != nil { - return nil, fmt.Errorf("failed to get a token source: %v", err) + return nil, fmt.Errorf("failed to get a token source: %w", err) } // Connect to Kubernetes using OAuth authentication, trusting its CA. caPool := x509.NewCertPool() caCertPEM, err := base64.StdEncoding.DecodeString(cluster.MasterAuth.ClusterCaCertificate) if err != nil { - return nil, fmt.Errorf("invalid base64 in ClusterCaCertificate: %v", err) + return nil, fmt.Errorf("invalid base64 in ClusterCaCertificate: %w", err) } caPool.AppendCertsFromPEM(caCertPEM) return &oauth2.Transport{ diff --git a/src/engine/framework_test.go b/src/engine/framework_test.go index 6b9a980..133d40c 100644 --- a/src/engine/framework_test.go +++ b/src/engine/framework_test.go @@ -4,12 +4,12 @@ import ( "testing" "github.com/nianticlabs/modron/src/common" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/testing/protocmp" - structpb "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/structpb" ) var resources = []*pb.Resource{ diff --git a/src/engine/rules/api_key_overbroad_scope.go b/src/engine/rules/api_key_overbroad_scope.go index 352a16a..47f1cd7 100644 --- a/src/engine/rules/api_key_overbroad_scope.go +++ b/src/engine/rules/api_key_overbroad_scope.go @@ -7,17 +7,18 @@ import ( "github.com/google/uuid" "golang.org/x/exp/slices" + "google.golang.org/protobuf/proto" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" ) -const ApiKeyOverbroadScopeRuleName = "API_KEY_WITH_OVERBROAD_SCOPE" +const apiKeyOverbroadScopeRuleName = "API_KEY_WITH_OVERBROAD_SCOPE" //nolint:gosec // TODO: Complete and/or remove excess scopes. // Uncommon scopes for API keys that should be marked as overbroad. @@ -35,20 +36,20 @@ var overbroadScopes = []string{ "vmmigration.googleapis.com", } -type ApiKeyOverbroadScopeRule struct { +type APIKeyOverbroadScopeRule struct { info model.RuleInfo } func init() { - AddRule(NewApiKeyOverbroadScopeRule()) + AddRule(NewAPIKeyOverbroadScopeRule()) } -func NewApiKeyOverbroadScopeRule() model.Rule { - return &ApiKeyOverbroadScopeRule{ +func NewAPIKeyOverbroadScopeRule() model.Rule { + return &APIKeyOverbroadScopeRule{ info: model.RuleInfo{ - Name: ApiKeyOverbroadScopeRuleName, - AcceptedResourceTypes: []string{ - common.ResourceApiKey, + Name: apiKeyOverbroadScopeRuleName, + AcceptedResourceTypes: []proto.Message{ + &pb.APIKey{}, }, }, } @@ -60,7 +61,7 @@ func toURLKey(name string) string { return name[strings.LastIndex(name, "/")+1:] } -func (r *ApiKeyOverbroadScopeRule) Check(ctx context.Context, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { +func (r *APIKeyOverbroadScopeRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { key := rsrc.GetApiKey() // If the key has no scopes, it is unrestricted. @@ -68,7 +69,7 @@ func (r *ApiKeyOverbroadScopeRule) Check(ctx context.Context, rsrc *pb.Resource) ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue("restricted"), ObservedValue: structpb.NewStringValue("unrestricted"), @@ -80,12 +81,13 @@ func (r *ApiKeyOverbroadScopeRule) Check(ctx context.Context, rsrc *pb.Resource) constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), ), Recommendation: fmt.Sprintf( - "Restrict API key [%q](https://console.cloud.google.com/apis/credentials/key/%s?project=%s) strictly to the APIs it is supposed to call.", + "Restrict API key [%q](https://console.cloud.google.com/apis/credentials/key/%s?project=%s) strictly to the APIs it is supposed to call. [More details available in our documentation.](https://github.com/nianticlabs/modron/blob/main/docs/FINDINGS.md)", toURLKey(rsrc.Name), toURLKey(rsrc.Name), constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), ), }, + Severity: pb.Severity_SEVERITY_MEDIUM, } obs = append(obs, ob) return @@ -96,7 +98,7 @@ func (r *ApiKeyOverbroadScopeRule) Check(ctx context.Context, rsrc *pb.Resource) ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue(""), ObservedValue: structpb.NewStringValue(scope), @@ -109,13 +111,14 @@ func (r *ApiKeyOverbroadScopeRule) Check(ctx context.Context, rsrc *pb.Resource) scope, ), Recommendation: fmt.Sprintf( - "Remove scope %q from API key [%q](https://console.cloud.google.com/apis/credentials/key/%s?project=%s) unless it is used.", + "Remove scope %q from API key [%q](https://console.cloud.google.com/apis/credentials/key/%s?project=%s) unless it is used. [More details available in our documentation.](https://github.com/nianticlabs/modron/blob/main/docs/FINDINGS.md)", scope, toURLKey(rsrc.Name), toURLKey(rsrc.Name), constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), ), }, + Severity: pb.Severity_SEVERITY_MEDIUM, } obs = append(obs, ob) } @@ -124,6 +127,6 @@ func (r *ApiKeyOverbroadScopeRule) Check(ctx context.Context, rsrc *pb.Resource) return } -func (r *ApiKeyOverbroadScopeRule) Info() *model.RuleInfo { +func (r *APIKeyOverbroadScopeRule) Info() *model.RuleInfo { return &r.info } diff --git a/src/engine/rules/api_key_overbroad_scope_test.go b/src/engine/rules/api_key_overbroad_scope_test.go index 8150509..60207cf 100644 --- a/src/engine/rules/api_key_overbroad_scope_test.go +++ b/src/engine/rules/api_key_overbroad_scope_test.go @@ -3,12 +3,12 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) func TestCheckDetectsOverbroadScope(t *testing.T) { @@ -68,37 +68,60 @@ func TestCheckDetectsOverbroadScope(t *testing.T) { }, } - got := TestRuleRun(t, resources, []model.Rule{NewApiKeyOverbroadScopeRule()}) + res1 := pb.Resource{Name: "api-key-with-overbroad-scope-1", + Type: &pb.Resource_ApiKey{ + ApiKey: &pb.APIKey{ + Scopes: []string{ + "iamcredentials.googleapis.com", + "storage_api", + "apikeys", + }, + }, + }, + ResourceGroupName: "projects/project-0", + Parent: "projects/project-0", + } - // Expected values are ordered lexicographically. want := []*pb.Observation{ { - Name: ApiKeyOverbroadScopeRuleName, - Resource: &pb.Resource{ - Name: "api-key-unrestricted-0", + Name: apiKeyOverbroadScopeRuleName, + ResourceRef: utils.GetResourceRef(&res1), + ExpectedValue: structpb.NewStringValue(""), + ObservedValue: structpb.NewStringValue("iamcredentials.googleapis.com"), + Remediation: &pb.Remediation{ + Description: "API key [\"api-key-with-overbroad-scope-1\"](https://console.cloud.google.com/apis/credentials/key/api-key-with-overbroad-scope-1?project=project-0) may have over-broad scope \"iamcredentials.googleapis.com\"", + Recommendation: "Remove scope \"iamcredentials.googleapis.com\" from API key [\"api-key-with-overbroad-scope-1\"](https://console.cloud.google.com/apis/credentials/key/api-key-with-overbroad-scope-1?project=project-0) unless it is used. [More details available in our documentation.](https://github.com/nianticlabs/modron/blob/main/docs/FINDINGS.md)", }, - ExpectedValue: structpb.NewStringValue("restricted"), - ObservedValue: structpb.NewStringValue("unrestricted"), + Severity: pb.Severity_SEVERITY_MEDIUM, }, { - Name: ApiKeyOverbroadScopeRuleName, - Resource: &pb.Resource{ - Name: "api-key-with-overbroad-scope-1", - }, + Name: apiKeyOverbroadScopeRuleName, + ResourceRef: utils.GetResourceRef(&res1), ExpectedValue: structpb.NewStringValue(""), - ObservedValue: structpb.NewStringValue("iamcredentials.googleapis.com"), + ObservedValue: structpb.NewStringValue("apikeys"), + Remediation: &pb.Remediation{ + Description: "API key [\"api-key-with-overbroad-scope-1\"](https://console.cloud.google.com/apis/credentials/key/api-key-with-overbroad-scope-1?project=project-0) may have over-broad scope \"apikeys\"", + Recommendation: "Remove scope \"apikeys\" from API key [\"api-key-with-overbroad-scope-1\"](https://console.cloud.google.com/apis/credentials/key/api-key-with-overbroad-scope-1?project=project-0) unless it is used. [More details available in our documentation.](https://github.com/nianticlabs/modron/blob/main/docs/FINDINGS.md)", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, { - Name: ApiKeyOverbroadScopeRuleName, - Resource: &pb.Resource{ - Name: "api-key-with-overbroad-scope-1", + Name: apiKeyOverbroadScopeRuleName, + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-2"), + GroupName: "projects/project-0", + ExternalId: proto.String("api-key-unrestricted-0"), + CloudPlatform: pb.CloudPlatform_GCP, }, - ExpectedValue: structpb.NewStringValue(""), - ObservedValue: structpb.NewStringValue("apikeys"), + ExpectedValue: structpb.NewStringValue("restricted"), + ObservedValue: structpb.NewStringValue("unrestricted"), + Remediation: &pb.Remediation{ + Description: "API key [\"api-key-unrestricted-0\"](https://console.cloud.google.com/apis/credentials/key/api-key-unrestricted-0?project=project-0) is unrestricted, which allows it to be used against any enabled GCP API", + Recommendation: "Restrict API key [\"api-key-unrestricted-0\"](https://console.cloud.google.com/apis/credentials/key/api-key-unrestricted-0?project=project-0) strictly to the APIs it is supposed to call. [More details available in our documentation.](https://github.com/nianticlabs/modron/blob/main/docs/FINDINGS.md)", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, } - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewAPIKeyOverbroadScopeRule()}, want) } diff --git a/src/engine/rules/bucket_is_public.go b/src/engine/rules/bucket_is_public.go index a2faf00..7e23d4a 100644 --- a/src/engine/rules/bucket_is_public.go +++ b/src/engine/rules/bucket_is_public.go @@ -4,12 +4,12 @@ import ( "context" "fmt" - "github.com/golang/glog" "github.com/google/uuid" + "google.golang.org/protobuf/proto" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -29,21 +29,21 @@ func NewBucketIsPublicRule() model.Rule { return &BucketIsPublicRule{ info: model.RuleInfo{ Name: BucketIsPublicRuleName, - AcceptedResourceTypes: []string{ - common.ResourceBucket, + AcceptedResourceTypes: []proto.Message{ + &pb.Bucket{}, }, }, } } -func (r *BucketIsPublicRule) Check(ctx context.Context, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { +func (r *BucketIsPublicRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { bk := rsrc.GetBucket() if bk.AccessType == pb.Bucket_PUBLIC { ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue(pb.Bucket_PRIVATE.String()), ObservedValue: structpb.NewStringValue(bk.AccessType.String()), @@ -59,10 +59,11 @@ func (r *BucketIsPublicRule) Check(ctx context.Context, rsrc *pb.Resource) (obs rsrc.Name, ), }, + Severity: pb.Severity_SEVERITY_MEDIUM, } obs = append(obs, ob) } else if bk.AccessType == pb.Bucket_ACCESS_UNKNOWN { - glog.Warningf("unknown access type for bucket %q", rsrc.Name) + log.Warnf("unknown access type for bucket %q", rsrc.Name) } return diff --git a/src/engine/rules/bucket_is_public_test.go b/src/engine/rules/bucket_is_public_test.go index 2275c4a..eac0d60 100644 --- a/src/engine/rules/bucket_is_public_test.go +++ b/src/engine/rules/bucket_is_public_test.go @@ -3,14 +3,12 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - - "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" - + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" ) func TestCheckDetectsPublicBucket(t *testing.T) { @@ -74,26 +72,37 @@ func TestCheckDetectsPublicBucket(t *testing.T) { want := []*pb.Observation{ { Name: BucketIsPublicRuleName, - Resource: &pb.Resource{ - Name: "public-bucket-2-is-detected", + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-1"), + GroupName: "projects/project-0", + ExternalId: proto.String("public-bucket-2-is-detected"), + CloudPlatform: pb.CloudPlatform_GCP, }, ObservedValue: structpb.NewStringValue("PUBLIC"), ExpectedValue: structpb.NewStringValue("PRIVATE"), + Remediation: &pb.Remediation{ + Description: "Bucket [\"public-bucket-2-is-detected\"](https://console.cloud.google.com/storage/browser/public-bucket-2-is-detected) is publicly accessible", + Recommendation: "Unless strictly needed, restrict the IAM policy of bucket [\"public-bucket-2-is-detected\"](https://console.cloud.google.com/storage/browser/public-bucket-2-is-detected) to prevent unconditional access by anyone. For more details, see [here](https://cloud.google.com/storage/docs/using-public-access-prevention)", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, { Name: BucketIsPublicRuleName, - Resource: &pb.Resource{ - Name: "public-bucket-1-is-detected", + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-2"), + GroupName: "projects/project-0", + ExternalId: proto.String("public-bucket-1-is-detected"), + CloudPlatform: pb.CloudPlatform_GCP, }, ObservedValue: structpb.NewStringValue("PUBLIC"), ExpectedValue: structpb.NewStringValue("PRIVATE"), + Remediation: &pb.Remediation{ + Description: "Bucket [\"public-bucket-1-is-detected\"](https://console.cloud.google.com/storage/browser/public-bucket-1-is-detected) is publicly accessible", + Recommendation: "Unless strictly needed, restrict the IAM policy of bucket [\"public-bucket-1-is-detected\"](https://console.cloud.google.com/storage/browser/public-bucket-1-is-detected) to prevent unconditional access by anyone. For more details, see [here](https://cloud.google.com/storage/docs/using-public-access-prevention)", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, } - got := TestRuleRun(t, resources, []model.Rule{NewBucketIsPublicRule()}) - - // Check that the observations are correct. - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewBucketIsPublicRule()}, want) } diff --git a/src/engine/rules/cluster_nodes_have_public_ips.go b/src/engine/rules/cluster_nodes_have_public_ips.go index 5560b20..ac62690 100644 --- a/src/engine/rules/cluster_nodes_have_public_ips.go +++ b/src/engine/rules/cluster_nodes_have_public_ips.go @@ -5,11 +5,12 @@ import ( "fmt" "github.com/google/uuid" + "google.golang.org/protobuf/proto" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -29,21 +30,21 @@ func NewClusterNodesHavePublicIpsRule() model.Rule { return &ClusterNodesHavePublicIpsRule{ info: model.RuleInfo{ Name: ClusterNodesHavePublicIps, - AcceptedResourceTypes: []string{ - common.ResourceKubernetesCluster, + AcceptedResourceTypes: []proto.Message{ + &pb.KubernetesCluster{}, }, }, } } -func (r *ClusterNodesHavePublicIpsRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *ClusterNodesHavePublicIpsRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { k8s := rsrc.GetKubernetesCluster() - obs := []*pb.Observation{} + var obs []*pb.Observation if !k8s.PrivateCluster { ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue("private"), ObservedValue: structpb.NewStringValue("public"), @@ -59,6 +60,7 @@ func (r *ClusterNodesHavePublicIpsRule) Check(ctx context.Context, rsrc *pb.Reso constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), ), }, + Severity: pb.Severity_SEVERITY_HIGH, } obs = append(obs, ob) } diff --git a/src/engine/rules/cluster_nodes_have_public_ips_test.go b/src/engine/rules/cluster_nodes_have_public_ips_test.go index 974e8d4..4345ead 100644 --- a/src/engine/rules/cluster_nodes_have_public_ips_test.go +++ b/src/engine/rules/cluster_nodes_have_public_ips_test.go @@ -3,11 +3,11 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" ) func TestPublicClusterNodesDetection(t *testing.T) { @@ -42,21 +42,25 @@ func TestPublicClusterNodesDetection(t *testing.T) { }, } - got := TestRuleRun(t, resources, []model.Rule{NewClusterNodesHavePublicIpsRule()}) - // Expected values are ordered lexicographically. want := []*pb.Observation{ { Name: ClusterNodesHavePublicIps, - Resource: &pb.Resource{ - Name: "public-cluster", + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-0"), + GroupName: "projects/project-0", + ExternalId: proto.String("public-cluster"), + CloudPlatform: pb.CloudPlatform_GCP, }, ExpectedValue: structpb.NewStringValue("private"), ObservedValue: structpb.NewStringValue("public"), + Remediation: &pb.Remediation{ + Description: "Cluster [\"public-cluster\"](https://console.cloud.google.com/kubernetes/list/overview?project=project-0) has a public IP, which could make it accessible by anyone on the internet", + Recommendation: "Unless strictly needed, redeploy cluster [\"public-cluster\"](https://console.cloud.google.com/kubernetes/list/overview?project=project-0) as a [private cluster](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters)", + }, + Severity: pb.Severity_SEVERITY_HIGH, }, } - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewClusterNodesHavePublicIpsRule()}, want) } diff --git a/src/engine/rules/common.go b/src/engine/rules/common.go index 4aaf724..f3278e1 100644 --- a/src/engine/rules/common.go +++ b/src/engine/rules/common.go @@ -4,12 +4,26 @@ import ( "regexp" "strings" - "github.com/nianticlabs/modron/src/pb" + "github.com/nianticlabs/modron/src/constants" + pb "github.com/nianticlabs/modron/src/proto/generated" + + "github.com/sirupsen/logrus" ) +var log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "rules") + func getAccountRoles(perm *pb.Permission, account string) (roles []string) { for _, principal := range perm.Principals { - if strings.EqualFold(principal, account) { + if strings.HasPrefix(principal, PrincipalDeleted) { + continue + } + s := strings.Split(principal, ":") + if len(s) != 2 { // nolint:mnd + log.Warn("invalid principal in org policy: ", principal) + continue + } + p := strings.Split(principal, ":")[1] + if strings.EqualFold(p, account) { roles = append(roles, perm.Role) } } @@ -17,12 +31,7 @@ func getAccountRoles(perm *pb.Permission, account string) (roles []string) { } const ( - PrincipalServiceAccount = "serviceAccount" - PrincipalUser = "user" - PrincipalGroup = "group" - PrincipalAllUsers = "allUsers" - PrincipalAllAuthenticatedUsers = "allAuthenticatedUsers" - PrincipalDomain = "domain" + PrincipalDeleted = "deleted:" ) // TODO: Add SelfLink and HumanReadableName field to Protobuf and move this logic to the collector. diff --git a/src/engine/rules/container_running.go b/src/engine/rules/container_running.go new file mode 100644 index 0000000..3436ef5 --- /dev/null +++ b/src/engine/rules/container_running.go @@ -0,0 +1,114 @@ +package rules + +import ( + "context" + "fmt" + "strings" + + "github.com/google/uuid" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" +) + +const ( + ContainerRunningRuleName = "CONTAINER_NOT_RUNNING" + separator = "@@" +) + +type ContainerRunningConfig struct { + // RequiredContainers is a map of namespaces to a list of prefixes of the pods that should be running: for example {"default": ["my-pod-"]} + RequiredContainers map[string][]string `json:"requiredContainers"` +} + +var ( + found = map[string]bool{} +) + +type ContainerRunningRule struct { + info model.RuleInfo +} + +func init() { + AddRule(NewContainerRunningRule()) +} + +func NewContainerRunningRule() model.Rule { + return &ContainerRunningRule{ + info: model.RuleInfo{ + Name: ContainerRunningRuleName, + AcceptedResourceTypes: []proto.Message{ + &pb.KubernetesCluster{}, + }, + }, + } +} + +func (r *ContainerRunningRule) Check(ctx context.Context, e model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { + var cfg ContainerRunningConfig + if err := utils.GetRuleConfig(ctx, e, r.info.Name, &cfg); err != nil { + return nil, []error{fmt.Errorf("unable to parse rule config: %w", err)} + } + clusterChildren, err := e.GetChildren(ctx, rsrc.Name) + if err != nil { + errs = append(errs, err) + return obs, errs + } + + for _, namespace := range clusterChildren { + // This is safe, if the function returns an error, the value will be "" + if n, _ := utils.TypeFromResource(namespace); n != common.ResourceNamespace { + continue + } + if _, ok := cfg.RequiredContainers[namespace.Name]; !ok { + // There are no pods to check in this namespace. + continue + } + + pods, err := e.GetChildren(ctx, namespace.Name) + if err != nil { + errs = append(errs, err) + } + for _, pod := range pods { + if p, _ := utils.TypeFromResource(pod); p != common.ResourcePod { + continue + } + for _, prefix := range cfg.RequiredContainers[namespace.Name] { + if pod.GetPod().GetPhase() == pb.Pod_RUNNING && strings.HasPrefix(pod.Name, prefix) { + found[namespace.Name+separator+prefix] = true + } + } + } + } + + for namespace, prefixes := range cfg.RequiredContainers { + for _, prefix := range prefixes { + if !found[namespace+separator+prefix] { + // TODO: Improve the observation by reporting the state of the existing pod if any. + obs = append(obs, &pb.Observation{ + Uid: uuid.NewString(), + Timestamp: timestamppb.Now(), + ResourceRef: utils.GetResourceRef(rsrc), + Name: r.Info().Name, + ExpectedValue: structpb.NewStringValue(prefix), + ObservedValue: structpb.NewStringValue(""), + Remediation: &pb.Remediation{ + Description: fmt.Sprintf("Cluster %s doesn't run any running container starting with %s", rsrc.DisplayName, prefix), + Recommendation: "This cluster is likely missing an important part of infrastructure. Check the cluster configuration or reach out to your tech support or SRE.", + }, + Severity: pb.Severity_SEVERITY_LOW, + }) + } + } + } + return +} + +func (r *ContainerRunningRule) Info() *model.RuleInfo { + return &r.info +} diff --git a/src/engine/rules/container_running_test.go b/src/engine/rules/container_running_test.go new file mode 100644 index 0000000..f2d8ec4 --- /dev/null +++ b/src/engine/rules/container_running_test.go @@ -0,0 +1,81 @@ +package rules + +import ( + "testing" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +func TestCheckContainerNotRunning(t *testing.T) { + resources := []*pb.Resource{ + { + Name: testProjectName, + Parent: "", + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{}, + }, + }, + { + Name: "Cluster", + DisplayName: "cluster-display-name", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_KubernetesCluster{ + KubernetesCluster: &pb.KubernetesCluster{}, + }, + }, + { + Name: "Namespace", + Parent: "Cluster", + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_Namespace{ + Namespace: &pb.Namespace{}, + }, + }, + } + + want := []*pb.Observation{ + { + Name: ContainerRunningRuleName, + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-0"), + GroupName: "projects/project-0", + ExternalId: proto.String("Cluster"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + ObservedValue: structpb.NewStringValue(""), + ExpectedValue: structpb.NewStringValue("pod-prefix-1-"), + Remediation: &pb.Remediation{ + Description: "Cluster cluster-display-name doesn't run any running container starting with pod-prefix-1-", + Recommendation: "This cluster is likely missing an important part of infrastructure. Check the cluster configuration or reach out to your tech support or SRE.", + }, + Severity: pb.Severity_SEVERITY_LOW, + }, + { + Name: ContainerRunningRuleName, + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-0"), + GroupName: "projects/project-0", + ExternalId: proto.String("Cluster"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + ObservedValue: structpb.NewStringValue(""), + ExpectedValue: structpb.NewStringValue("pod-prefix-2-"), + Remediation: &pb.Remediation{ + Description: "Cluster cluster-display-name doesn't run any running container starting with pod-prefix-2-", + Recommendation: "This cluster is likely missing an important part of infrastructure. Check the cluster configuration or reach out to your tech support or SRE.", + }, + Severity: pb.Severity_SEVERITY_LOW, + }, + } + + TestRuleRun(t, resources, []model.Rule{NewContainerRunningRule()}, want) +} diff --git a/src/engine/rules/cross_environment_permission.go b/src/engine/rules/cross_environment_permission.go new file mode 100644 index 0000000..133b2c5 --- /dev/null +++ b/src/engine/rules/cross_environment_permission.go @@ -0,0 +1,112 @@ +package rules + +import ( + "context" + "fmt" + "strings" + + "github.com/google/uuid" + "google.golang.org/api/cloudidentity/v1" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/utils" +) + +const CrossEnvironmentPermissionsRuleName = "CROSS_ENVIRONMENT_PERMISSIONS" + +type CrossEnvironmentPermissionsRule struct { + info model.RuleInfo + cloudIdentityService *cloudidentity.Service +} + +func init() { + AddRule(NewCrossEnvironmentPermissionsRule()) +} + +func NewCrossEnvironmentPermissionsRule() model.Rule { + return &CrossEnvironmentPermissionsRule{ + info: model.RuleInfo{ + Name: CrossEnvironmentPermissionsRuleName, + AcceptedResourceTypes: []proto.Message{ + &pb.KubernetesCluster{}, + &pb.ServiceAccount{}, + &pb.Bucket{}, + // &pb.Database{}, // TODO: Uncomment when we start collecting DBs IAM Policies + &pb.ResourceGroup{}, + }, + }, + cloudIdentityService: nil, + } +} + +func (r *CrossEnvironmentPermissionsRule) Check(ctx context.Context, e model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { + log = log.WithField("rule", r.info.Name) + policy := rsrc.IamPolicy + if policy == nil { + return nil, []error{fmt.Errorf("resource %s has no IAM policy", rsrc.Name)} + } + + hierarchy, err := e.GetHierarchy(ctx, rsrc.CollectionUid) + if err != nil { + return nil, []error{err} + } + + myEnv := risk.GetEnvironment(e.GetTagConfig(), hierarchy, rsrc.ResourceGroupName) + principals := make(map[string]string) + for _, perm := range policy.Permissions { + for _, p := range perm.Principals { + if !strings.HasPrefix(p, constants.GCPServiceAccountPrefix) { + log.Debugf("skipping principal %s", p) + continue + } + saEmail := strings.TrimPrefix(p, constants.GCPServiceAccountPrefix) + saProject := utils.GetGCPProjectFromSAEmail(saEmail) + if saProject == "" { + log.Warnf("got an invalid project for service account %s", saEmail) + continue + } + if utils.IsGCPServiceAccountProject(saProject) { + log.Debugf("service account %s is a GCP service account", saEmail) + continue + } + if saProject == strings.TrimPrefix(rsrc.ResourceGroupName, constants.GCPProjectsNamePrefix) { + log.Debugf("service account %s is in the same project as the resource", saEmail) + continue + } + // Cross project + env := risk.GetEnvironment(e.GetTagConfig(), hierarchy, constants.GCPProjectsNamePrefix+saProject) + if env != myEnv { + principals[saEmail] = env + } + } + } + + for principal, otherEnv := range principals { + obs = append(obs, &pb.Observation{ + Uid: uuid.NewString(), + Timestamp: timestamppb.Now(), + Name: r.info.Name, + ExpectedValue: structpb.NewStringValue(myEnv), + ObservedValue: structpb.NewStringValue(otherEnv), + Remediation: &pb.Remediation{ + Description: fmt.Sprintf("%s is in a different environment than the resource %q", principal, rsrc.Name), + Recommendation: fmt.Sprintf("Revoke the access of %q to the resource %q", principal, rsrc.Name), + }, + ResourceRef: utils.GetResourceRef(rsrc), + Category: pb.Observation_CATEGORY_MISCONFIGURATION, + Severity: pb.Severity_SEVERITY_HIGH, + }) + } + + return obs, errs +} + +func (r *CrossEnvironmentPermissionsRule) Info() *model.RuleInfo { + return &r.info +} diff --git a/src/engine/rules/cross_project_permissions.go b/src/engine/rules/cross_project_permissions.go index 067f016..eb666b2 100644 --- a/src/engine/rules/cross_project_permissions.go +++ b/src/engine/rules/cross_project_permissions.go @@ -5,18 +5,17 @@ import ( "fmt" "strings" - "github.com/golang/glog" "github.com/google/uuid" "golang.org/x/exp/slices" "google.golang.org/api/cloudidentity/v1" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" - "github.com/nianticlabs/modron/src/engine" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) const CrossProjectPermissionsRuleName = "CROSS_PROJECT_PERMISSIONS" @@ -55,9 +54,9 @@ type CrossProjectPermissionsRule struct { } type FindingPrincipal struct { - account string - group string - role string + account string + projectID string + role string } func init() { @@ -68,11 +67,12 @@ func NewCrossProjectPermissionsRule() model.Rule { return &CrossProjectPermissionsRule{ info: model.RuleInfo{ Name: CrossProjectPermissionsRuleName, - AcceptedResourceTypes: []string{ - common.ResourceServiceAccount, - common.ResourceBucket, - common.ResourceDatabase, - common.ResourceResourceGroup, + AcceptedResourceTypes: []proto.Message{ + &pb.KubernetesCluster{}, + &pb.ServiceAccount{}, + &pb.Bucket{}, + // &pb.Database{}, // TODO: Uncomment when we start collecting DBs IAM Policies + &pb.ResourceGroup{}, }, }, cloudIdentityService: nil, @@ -80,148 +80,94 @@ func NewCrossProjectPermissionsRule() model.Rule { } func getResourceSpecificString(rsrc *pb.Resource) string { - switch rsrc.Type.(type) { - case *pb.Resource_Bucket: - return "storage bucket [%q](https://console.cloud.google.com/storage/browser/%s)" - case *pb.Resource_Database: - return "database [%q](https://console.cloud.google.com/spanner/instances/%s/details/databases)" - case *pb.Resource_ServiceAccount: - return "service account [%q](https://console.cloud.google.com/iam-admin/serviceaccounts?project=%s)" - case *pb.Resource_ResourceGroup: - return "project [%q](https://console.cloud.google.com/welcome?project=%s)" - default: - return "" - } + resourceLink := utils.LinkGCPResource(rsrc) + return fmt.Sprintf("%s [%s](%s)", resourceLink.Type, resourceLink.Name, resourceLink.URL) } -func getRemediationByResourceType(rsrc *pb.Resource, fp *FindingPrincipal) pb.Remediation { +func getRemediationByResourceType(rsrc *pb.Resource, fp *FindingPrincipal) *pb.Remediation { resourceString := getResourceSpecificString(rsrc) - var throughGroup string - var recommendation string - var recommendationTemplate string - var linkContent string - switch rsrc.Type.(type) { - case *pb.Resource_Bucket, *pb.Resource_Database: - linkContent = rsrc.Name - case *pb.Resource_ServiceAccount, *pb.Resource_ResourceGroup: - linkContent = constants.ResourceWithoutProjectsPrefix(rsrc.Name) - } - if fp.group != "" { - throughGroup = fmt.Sprintf(". The user is part of the group %s", fp.group) - recommendationTemplate = - "Remove the account %q from the group %q, remove the group from the " + - resourceString + - " or replace the principal %q with a principal created in the project %q that grants it the **smallest set of permissions** needed to operate" - recommendation = fmt.Sprintf(recommendationTemplate, - fp.account, - fp.group, - rsrc.Name, - linkContent, - fp.account, - constants.ResourceWithoutProjectsPrefix(rsrc.Parent), - ) - } else { - throughGroup = "" - recommendationTemplate = - "Replace the principal %q controlling the " + - resourceString + - " with a principal created in the project %q that grants it the **smallest set of permissions** needed to operate" - recommendation = fmt.Sprintf( - recommendationTemplate, - fp.account, - rsrc.Name, - linkContent, - constants.ResourceWithoutProjectsPrefix(rsrc.Parent), - ) - } + principalString := getResourceSpecificString(&pb.Resource{ + Name: fp.account, + ResourceGroupName: constants.ResourceWithProjectsPrefix(fp.projectID), + Type: &pb.Resource_ServiceAccount{}, + }) + recommendation := fmt.Sprintf( + "Replace the %s controlling %s with a principal created in the project %q that grants it the **smallest set of permissions** needed to operate.", + principalString, + resourceString, + rsrc.ResourceGroupName, + ) switch rsrc.Type.(type) { case *pb.Resource_Bucket, *pb.Resource_Database, *pb.Resource_ServiceAccount: - descriptionTemplate := "The " + resourceString + " is controlled by the principal %q with role %s defined in another project%s" - return pb.Remediation{ + return &pb.Remediation{ Description: fmt.Sprintf( - descriptionTemplate, - rsrc.Name, - linkContent, - fp.account, + "The %s is controlled by the %s with role `%s` defined in project %q", + resourceString, + principalString, fp.role, - throughGroup, + fp.projectID, ), Recommendation: recommendation, } case *pb.Resource_ResourceGroup: - descriptionTemplate := - "The " + - resourceString + - " gives the principal %q vast permissions through the role %s%s This principal is defined in another project which means that anybody with rights in that project can use it to control the resources in this one" - return pb.Remediation{ + return &pb.Remediation{ Description: fmt.Sprintf( - descriptionTemplate, - rsrc.Name, - linkContent, - fp.account, + "The %s gives the %s vast permissions through the role `%s`.\n"+ + "This principal is defined in project %q, which means that anybody with rights in that project can use it to control the resources in this one", + resourceString, + principalString, fp.role, - "."+throughGroup, + fp.projectID, ), Recommendation: recommendation, } default: - return pb.Remediation{} + return &pb.Remediation{} } } -func isCrossProjectServiceAccount(ctx context.Context, user string, rsrc *pb.Resource) (bool, error) { - svcAccnt := strings.Split(user, "@") +func isCrossProjectServiceAccount(user string, sourceProjectID string) (bool, string) { if user == "allUsers" || user == "allAuthenticatedUsers" { - return false, nil + return false, "" } - if len(svcAccnt) < 2 { - glog.Warningf("unknown service account %q", user) - return false, nil + svcAccnt := strings.Split(user, "@") + if len(svcAccnt) < 2 { //nolint:mnd + log.Warnf("unknown service account %q", user) + return false, "" } - _, contained := googleManagedAccountSuffixes[svcAccnt[1]] - var resourceId string - switch rsrc.Type.(type) { // ResourceGroup is only available on a Resource_ResourceGroup and not on others - case *pb.Resource_ResourceGroup: - resourceId = rsrc.GetResourceGroup().Identifier - default: - parent, err := engine.GetResource(ctx, rsrc.Parent) - if err != nil { - err = fmt.Errorf("could not get parent %v of resource %v", rsrc.Parent, rsrc.Name) - glog.Error(err) - return false, err - } - resourceId = parent.GetResourceGroup().Identifier + if !strings.HasSuffix(user, "iam.gserviceaccount.com") { + return false, "" + } + saProject := utils.GetGCPProjectFromSAEmail(user) + if saProject == "" { + log.Warnf("could not get project from service account %q", user) + return false, "" } - return strings.HasSuffix(user, ".gserviceaccount.com") && // It should be a service account - !strings.HasPrefix(strings.TrimPrefix(user, constants.GCPServiceAccountPrefix), resourceId) && // Should not be an account with the project prefix - !strings.Contains(user, constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName)) && // The service account was created in another project - !strings.HasSuffix(user, "@cloudservices.gserviceaccount.com") && - !contained, nil + if utils.IsGCPServiceAccountProject(saProject) { + return false, "" + } + if saProject == sourceProjectID { + return false, "" + } + return true, saProject } func (r *CrossProjectPermissionsRule) createObservation(rsrc *pb.Resource, fp *FindingPrincipal) *pb.Observation { - remediation := getRemediationByResourceType(rsrc, fp) - observed_value := structpb.NewStringValue(fmt.Sprintf("%q with role %s", fp.account, fp.role)) - if fp.group != "" { - observed_value = structpb.NewStringValue(fmt.Sprintf("%q > %q", fp.group, fp.account)) - } - ob := &pb.Observation{ + return &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, - ExpectedValue: structpb.NewStringValue(""), - ObservedValue: observed_value, - Remediation: &remediation, + ExpectedValue: structpb.NewStringValue(strings.TrimPrefix(rsrc.ResourceGroupName, constants.GCPProjectsNamePrefix)), + ObservedValue: structpb.NewStringValue(fp.projectID), + Remediation: getRemediationByResourceType(rsrc, fp), + Severity: pb.Severity_SEVERITY_MEDIUM, } - - return ob } -func (r *CrossProjectPermissionsRule) Check(ctx context.Context, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { +func (r *CrossProjectPermissionsRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { policy := rsrc.IamPolicy - if policy == nil { return nil, []error{fmt.Errorf("resource %s has no IAM policy", rsrc.Name)} } @@ -229,19 +175,20 @@ func (r *CrossProjectPermissionsRule) Check(ctx context.Context, rsrc *pb.Resour for _, perm := range policy.Permissions { if slices.Contains(rolesToWatch, perm.Role) { for _, principal := range perm.GetPrincipals() { - // TODO: Check for cross project users in groups - shouldFlag, err := isCrossProjectServiceAccount(ctx, principal, rsrc) - if err != nil { - errs = append(errs, err) - continue - } - if shouldFlag { - obs = append(obs, r.createObservation(rsrc, &FindingPrincipal{principal, "", perm.Role})) + // TODO: Check for cross project service accounts in groups + xProjectSA, projectID := isCrossProjectServiceAccount( + principal, + strings.TrimPrefix( + rsrc.ResourceGroupName, + constants.GCPProjectsNamePrefix, + ), + ) + if xProjectSA { + obs = append(obs, r.createObservation(rsrc, &FindingPrincipal{principal, projectID, perm.Role})) } } } } - return obs, errs } diff --git a/src/engine/rules/cross_project_permissions_test.go b/src/engine/rules/cross_project_permissions_test.go index cf1efbe..693a5d5 100644 --- a/src/engine/rules/cross_project_permissions_test.go +++ b/src/engine/rules/cross_project_permissions_test.go @@ -4,20 +4,20 @@ import ( "fmt" "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "google.golang.org/protobuf/types/known/structpb" "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) func TestCheckDetectsCrossProjectAccount(t *testing.T) { - collectId := uuid.NewString() + collectID := uuid.NewString() testProjectName1 := "projects/project-1" testProjectIdentifier := "12345678" + bucketName := "bucket1" accountZeroProjectZero := fmt.Sprintf("serviceAccount:account-0@%s.iam.gserviceaccount.com", constants.ResourceWithoutProjectsPrefix(testProjectName)) devAccountProjectZero := fmt.Sprintf("serviceAccount:%s-compute@developer.gserviceaccount.com", testProjectIdentifier) accountOneProjectOne := fmt.Sprintf("serviceAccount:account-1@%s.iam.gserviceaccount.com", constants.ResourceWithoutProjectsPrefix(testProjectName1)) @@ -27,170 +27,242 @@ func TestCheckDetectsCrossProjectAccount(t *testing.T) { googleServiceAcc := "serviceAccount:service-123455@gcp-sa-firebase.iam.gserviceaccount.com" // TODO: Add a test case that involves a group - resources := []*pb.Resource{ - { - Name: testProjectName, - Parent: "", - ResourceGroupName: testProjectName, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{ - Resource: nil, - Permissions: []*pb.Permission{ - { - Role: "iam.serviceAccountAdmin", - Principals: []string{ - accountZeroProjectZero, - }, + testProjectNameResource := &pb.Resource{ + Name: testProjectName, + Parent: "", + ResourceGroupName: testProjectName, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{ + Resource: nil, + Permissions: []*pb.Permission{ + { + Role: "iam.serviceAccountAdmin", + Principals: []string{ + accountZeroProjectZero, }, - { - Role: "dataflow.admin", - Principals: []string{ - accountOneProjectOne, - devAccountProjectZero, - }, + }, + { + Role: "dataflow.admin", + Principals: []string{ + accountOneProjectOne, + devAccountProjectZero, }, - { - Role: "viewer", - Principals: []string{ - accountOneProjectOne, - }, + }, + { + Role: "viewer", + Principals: []string{ + accountOneProjectOne, }, }, }, - Type: &pb.Resource_ResourceGroup{ - ResourceGroup: &pb.ResourceGroup{ - Identifier: testProjectIdentifier, - }, + }, + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: testProjectIdentifier, }, }, - { - Name: testProjectName1, - Parent: "", - ResourceGroupName: testProjectName1, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{ - Resource: nil, - Permissions: []*pb.Permission{ - { - Role: "compute.admin", - Principals: []string{ - accountTwoProjectOne, - accountThreeProjectZero, - googleServiceAcc, - }, + } + + testProjectName1Resource := &pb.Resource{ + Name: testProjectName1, + Parent: "", + ResourceGroupName: testProjectName1, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{ + Resource: nil, + Permissions: []*pb.Permission{ + { + Role: "compute.admin", + Principals: []string{ + accountTwoProjectOne, + accountThreeProjectZero, + googleServiceAcc, }, }, }, - Type: &pb.Resource_ResourceGroup{ - ResourceGroup: &pb.ResourceGroup{ - Identifier: "9876543221", - }, + }, + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: "9876543221", }, }, - { - Name: accountZeroProjectZero, - Parent: testProjectName, - ResourceGroupName: testProjectName, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{}, + } + + bucketResource := &pb.Resource{ + Name: bucketName, + Parent: testProjectName, + ResourceGroupName: testProjectName, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{ + Resource: nil, + Permissions: []*pb.Permission{ + { + Role: "storage.admin", + Principals: []string{ + devAccountProjectZero, + accountOneProjectOne, + }, }, }, }, - { - Name: accountOneProjectOne, - Parent: testProjectName1, - ResourceGroupName: testProjectName1, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{ - Resource: nil, - Permissions: []*pb.Permission{ - { - Role: "iam.serviceAccountTokenCreator", - Principals: []string{ - accountTwoProjectOne, - accountThreeProjectZero, - invalidAccount, - }, + Type: &pb.Resource_Bucket{ + Bucket: &pb.Bucket{}, + }, + } + + accountZeroResource := &pb.Resource{ + Name: accountZeroProjectZero, + Parent: testProjectName, + ResourceGroupName: testProjectName, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, + }, + }, + } + + accountOneResource := &pb.Resource{ + Name: accountOneProjectOne, + Parent: testProjectName1, + ResourceGroupName: testProjectName1, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{ + Resource: nil, + Permissions: []*pb.Permission{ + { + Role: "iam.serviceAccountTokenCreator", + Principals: []string{ + accountTwoProjectOne, + accountThreeProjectZero, + invalidAccount, }, }, }, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{}, - }, + }, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, }, }, - { - Name: accountTwoProjectOne, - Parent: testProjectName1, - ResourceGroupName: testProjectName1, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{}, - }, + } + + accountTwoResource := &pb.Resource{ + Name: accountTwoProjectOne, + Parent: testProjectName1, + ResourceGroupName: testProjectName1, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, }, }, - { - Name: accountThreeProjectZero, - Parent: testProjectName, - ResourceGroupName: testProjectName, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{}, - }, + } + + accountThreeResource := &pb.Resource{ + Name: accountThreeProjectZero, + Parent: testProjectName, + ResourceGroupName: testProjectName, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, }, }, - { - Name: googleServiceAcc, - Parent: testProjectName1, - ResourceGroupName: testProjectName1, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{}, - }, + } + + googleServiceAccountResource := &pb.Resource{ + Name: googleServiceAcc, + Parent: testProjectName1, + ResourceGroupName: testProjectName1, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, }, }, } + devAccountProjectZeroResource := &pb.Resource{ + Name: devAccountProjectZero, + Parent: testProjectName, + ResourceGroupName: testProjectName, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, + }, + }, + } + + resources := []*pb.Resource{ + testProjectNameResource, + testProjectName1Resource, + bucketResource, + accountZeroResource, + accountOneResource, + accountTwoResource, + accountThreeResource, + googleServiceAccountResource, + devAccountProjectZeroResource, + } + + project0 := "[project-0](https://console.cloud.google.com/welcome?project=project-0)" + project1 := "[project-1](https://console.cloud.google.com/welcome?project=project-1)" + bucket1 := "[bucket1](https://console.cloud.google.com/storage/browser/bucket1)" + saAccount1 := "[account-1@project-1.iam.gserviceaccount.com](https://console.cloud.google.com/iam-admin/serviceaccounts/details/account-1@project-1.iam.gserviceaccount.com?project=project-1)" + saAccount3 := "[account-3@project-0.iam.gserviceaccount.com](https://console.cloud.google.com/iam-admin/serviceaccounts/details/account-3@project-0.iam.gserviceaccount.com?project=project-0)" want := []*pb.Observation{ { - Name: CrossProjectPermissionsRuleName, - Resource: &pb.Resource{ - Name: testProjectName, + Name: CrossProjectPermissionsRuleName, + ResourceRef: utils.GetResourceRef(testProjectNameResource), + ExpectedValue: structpb.NewStringValue("project-0"), + ObservedValue: structpb.NewStringValue("project-1"), + Remediation: &pb.Remediation{ + Description: `The project ` + project0 + ` gives the service account ` + saAccount1 + ` vast permissions through the role ` + "`dataflow.admin`.\n" + `This principal is defined in project "project-1", which means that anybody with rights in that project can use it to control the resources in this one`, + Recommendation: `Replace the service account ` + saAccount1 + ` controlling project ` + project0 + ` with a principal created in the project "projects/project-0" that grants it the **smallest set of permissions** needed to operate.`, + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + }, + { + Name: CrossProjectPermissionsRuleName, + ResourceRef: utils.GetResourceRef(bucketResource), + ExpectedValue: structpb.NewStringValue("project-0"), + ObservedValue: structpb.NewStringValue("project-1"), + Remediation: &pb.Remediation{ + Description: `The bucket ` + bucket1 + ` is controlled by the service account ` + saAccount1 + ` with role ` + "`storage.admin`" + ` defined in project "project-1"`, + Recommendation: `Replace the service account ` + saAccount1 + ` controlling bucket ` + bucket1 + ` with a principal created in the project "projects/project-0" that grants it the **smallest set of permissions** needed to operate.`, }, - ExpectedValue: structpb.NewStringValue(""), - ObservedValue: structpb.NewStringValue(fmt.Sprintf("%q with role %s", accountOneProjectOne, "dataflow.admin")), + Severity: pb.Severity_SEVERITY_MEDIUM, }, { - Name: CrossProjectPermissionsRuleName, - Resource: &pb.Resource{ - Name: testProjectName1, + Name: CrossProjectPermissionsRuleName, + ResourceRef: utils.GetResourceRef(testProjectName1Resource), + ExpectedValue: structpb.NewStringValue("project-1"), + ObservedValue: structpb.NewStringValue("project-0"), + Remediation: &pb.Remediation{ + Description: `The project ` + project1 + ` gives the service account ` + saAccount3 + ` vast permissions through the role ` + "`compute.admin`.\n" + `This principal is defined in project "project-0", which means that anybody with rights in that project can use it to control the resources in this one`, + Recommendation: `Replace the service account ` + saAccount3 + ` controlling project ` + project1 + ` with a principal created in the project "projects/project-1" that grants it the **smallest set of permissions** needed to operate.`, }, - ExpectedValue: structpb.NewStringValue(""), - ObservedValue: structpb.NewStringValue(fmt.Sprintf("%q with role %s", accountThreeProjectZero, "compute.admin")), + Severity: pb.Severity_SEVERITY_MEDIUM, }, { - Name: CrossProjectPermissionsRuleName, - Resource: &pb.Resource{ - Name: accountOneProjectOne, + Name: CrossProjectPermissionsRuleName, + ResourceRef: utils.GetResourceRef(accountOneResource), + ExpectedValue: structpb.NewStringValue("project-1"), + ObservedValue: structpb.NewStringValue("project-0"), + Remediation: &pb.Remediation{ + Description: `The service account ` + saAccount1 + ` is controlled by the service account ` + saAccount3 + ` with role ` + "`iam.serviceAccountTokenCreator`" + ` defined in project "project-0"`, + Recommendation: `Replace the service account ` + saAccount3 + ` controlling service account ` + saAccount1 + ` with a principal created in the project "projects/project-1" that grants it the **smallest set of permissions** needed to operate.`, }, - ExpectedValue: structpb.NewStringValue(""), - ObservedValue: structpb.NewStringValue(fmt.Sprintf("%q with role %s", accountThreeProjectZero, "iam.serviceAccountTokenCreator")), + Severity: pb.Severity_SEVERITY_MEDIUM, }, } - got := TestRuleRun(t, resources, []model.Rule{NewCrossProjectPermissionsRule()}) - - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewCrossProjectPermissionsRule()}, want) } diff --git a/src/engine/rules/database_allows_unencrypted_connections.go b/src/engine/rules/database_allows_unencrypted_connections.go index 3ce7267..4de8715 100644 --- a/src/engine/rules/database_allows_unencrypted_connections.go +++ b/src/engine/rules/database_allows_unencrypted_connections.go @@ -5,11 +5,13 @@ import ( "fmt" "github.com/google/uuid" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) const DatabaseAllowsUnencryptedConnections = "DATABASE_ALLOWS_UNENCRYPTED_CONNECTIONS" @@ -26,14 +28,14 @@ func NewDatabaseAllowsUnencryptedConnectionsRule() model.Rule { return &DatabaseAllowsUnencryptedConnectionsRule{ info: model.RuleInfo{ Name: DatabaseAllowsUnencryptedConnections, - AcceptedResourceTypes: []string{ - common.ResourceDatabase, + AcceptedResourceTypes: []proto.Message{ + &pb.Database{}, }, }, } } -func (r *DatabaseAllowsUnencryptedConnectionsRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *DatabaseAllowsUnencryptedConnectionsRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { db := rsrc.GetDatabase() obs := []*pb.Observation{} @@ -45,7 +47,7 @@ func (r *DatabaseAllowsUnencryptedConnectionsRule) Check(ctx context.Context, rs ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewBoolValue(true), ObservedValue: structpb.NewBoolValue(false), @@ -59,6 +61,7 @@ func (r *DatabaseAllowsUnencryptedConnectionsRule) Check(ctx context.Context, rs getGcpReadableResourceName(rsrc.Name), ), }, + Severity: pb.Severity_SEVERITY_MEDIUM, } obs = append(obs, ob) } diff --git a/src/engine/rules/database_allows_unencrypted_connections_test.go b/src/engine/rules/database_allows_unencrypted_connections_test.go index a85a415..2b44ee3 100644 --- a/src/engine/rules/database_allows_unencrypted_connections_test.go +++ b/src/engine/rules/database_allows_unencrypted_connections_test.go @@ -3,16 +3,27 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" ) func TestCheckDetectsDatabaseAllowsUnencryptedConnections(t *testing.T) { + databaseNoForceTLS := &pb.Resource{ + Name: "database-no-force-tls", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_Database{ + Database: &pb.Database{ + Type: "cloudsql", + Version: "123", + TlsRequired: false, + }, + }, + } resources := []*pb.Resource{ { Name: testProjectName, @@ -23,19 +34,7 @@ func TestCheckDetectsDatabaseAllowsUnencryptedConnections(t *testing.T) { ResourceGroup: &pb.ResourceGroup{}, }, }, - { - Name: "database-no-force-tls", - Parent: testProjectName, - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_Database{ - Database: &pb.Database{ - Type: "cloudsql", - Version: "123", - TlsRequired: false, - }, - }, - }, + databaseNoForceTLS, { Name: "database-force-tls", Parent: testProjectName, @@ -53,19 +52,17 @@ func TestCheckDetectsDatabaseAllowsUnencryptedConnections(t *testing.T) { want := []*pb.Observation{ { - Name: DatabaseAllowsUnencryptedConnections, - Resource: &pb.Resource{ - Name: "database-no-force-tls", - }, + Name: DatabaseAllowsUnencryptedConnections, + ResourceRef: utils.GetResourceRef(databaseNoForceTLS), ObservedValue: structpb.NewBoolValue(false), ExpectedValue: structpb.NewBoolValue(true), + Remediation: &pb.Remediation{ + Description: "Database database-no-force-tls allows for unencrypted connections.", + Recommendation: "Enable the require SSL setting in the database settings to allow only encrypted connections to database-no-force-tls.", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, } - got := TestRuleRun(t, resources, []model.Rule{NewDatabaseAllowsUnencryptedConnectionsRule()}) - - // Check that the observations are correct. - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewDatabaseAllowsUnencryptedConnectionsRule()}, want) } diff --git a/src/engine/rules/database_authorized_network_not_set_test.go b/src/engine/rules/database_authorized_network_not_set_test.go index 789cbeb..afffe9b 100644 --- a/src/engine/rules/database_authorized_network_not_set_test.go +++ b/src/engine/rules/database_authorized_network_not_set_test.go @@ -3,16 +3,28 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" ) func TestCheckDetectsDatabaseAuthorizedNetworksNotSet(t *testing.T) { + databasePublicAndNoAuthorizedNetworks := &pb.Resource{ + Name: "database-public-and-no-authorized-networks", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_Database{ + Database: &pb.Database{ + Type: "cloudsql", + Version: "123", + AuthorizedNetworksSettingAvailable: pb.Database_AUTHORIZED_NETWORKS_NOT_SET, + IsPublic: true, + }, + }, + } resources := []*pb.Resource{ { Name: testProjectName, @@ -23,20 +35,7 @@ func TestCheckDetectsDatabaseAuthorizedNetworksNotSet(t *testing.T) { ResourceGroup: &pb.ResourceGroup{}, }, }, - { - Name: "database-public-and-no-authorized-networks", - Parent: testProjectName, - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_Database{ - Database: &pb.Database{ - Type: "cloudsql", - Version: "123", - AuthorizedNetworksSettingAvailable: pb.Database_AUTHORIZED_NETWORKS_NOT_SET, - IsPublic: true, - }, - }, - }, + databasePublicAndNoAuthorizedNetworks, { Name: "database-private-no-authorized-networks", Parent: testProjectName, @@ -68,19 +67,17 @@ func TestCheckDetectsDatabaseAuthorizedNetworksNotSet(t *testing.T) { want := []*pb.Observation{ { - Name: DatabaseAuthorizedNetworksNotSet, - Resource: &pb.Resource{ - Name: "database-public-and-no-authorized-networks", - }, + Name: DatabaseAuthorizedNetworksNotSet, + ResourceRef: utils.GetResourceRef(databasePublicAndNoAuthorizedNetworks), ObservedValue: structpb.NewStringValue("AUTHORIZED_NETWORKS_NOT_SET"), ExpectedValue: structpb.NewStringValue("AUTHORIZED_NETWORKS_SET"), + Remediation: &pb.Remediation{ + Description: "Database database-public-and-no-authorized-networks is reachable from any IP on the Internet.", + Recommendation: "Enable the authorized network setting in the database settings to restrict what networks can access database-public-and-no-authorized-networks.", + }, + Severity: pb.Severity_SEVERITY_HIGH, }, } - got := TestRuleRun(t, resources, []model.Rule{NewDatabaseAuthorizedNetworksNotSetRule()}) - - // Check that the observations are correct. - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewDatabaseAuthorizedNetworksNotSetRule()}, want) } diff --git a/src/engine/rules/database_authorized_networks_not_set.go b/src/engine/rules/database_authorized_networks_not_set.go index 42bedc3..620e339 100644 --- a/src/engine/rules/database_authorized_networks_not_set.go +++ b/src/engine/rules/database_authorized_networks_not_set.go @@ -5,11 +5,13 @@ import ( "fmt" "github.com/google/uuid" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) const DatabaseAuthorizedNetworksNotSet = "DATABASE_AUTHORIZED_NETWORKS_NOT_SET" @@ -26,14 +28,14 @@ func NewDatabaseAuthorizedNetworksNotSetRule() model.Rule { return &DatabaseAuthorizedNetworksNotSetRule{ info: model.RuleInfo{ Name: DatabaseAuthorizedNetworksNotSet, - AcceptedResourceTypes: []string{ - common.ResourceDatabase, + AcceptedResourceTypes: []proto.Message{ + &pb.Database{}, }, }, } } -func (r *DatabaseAuthorizedNetworksNotSetRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *DatabaseAuthorizedNetworksNotSetRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { db := rsrc.GetDatabase() obs := []*pb.Observation{} @@ -45,7 +47,7 @@ func (r *DatabaseAuthorizedNetworksNotSetRule) Check(ctx context.Context, rsrc * ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue(pb.Database_AUTHORIZED_NETWORKS_SET.String()), ObservedValue: structpb.NewStringValue(pb.Database_AUTHORIZED_NETWORKS_NOT_SET.String()), @@ -59,6 +61,7 @@ func (r *DatabaseAuthorizedNetworksNotSetRule) Check(ctx context.Context, rsrc * getGcpReadableResourceName(rsrc.Name), ), }, + Severity: pb.Severity_SEVERITY_HIGH, } obs = append(obs, ob) } diff --git a/src/engine/rules/exported_key_expiry_too_long.go b/src/engine/rules/exported_key_expiry_too_long.go index 10c2980..f026ecc 100644 --- a/src/engine/rules/exported_key_expiry_too_long.go +++ b/src/engine/rules/exported_key_expiry_too_long.go @@ -6,12 +6,14 @@ import ( "time" "github.com/google/uuid" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) const ( @@ -31,14 +33,14 @@ func NewExportedKeyIsTooOldRule() model.Rule { return &ExportedKeyIsTooOldRule{ info: model.RuleInfo{ Name: ExportedKeyIsTooOld, - AcceptedResourceTypes: []string{ - common.ResourceExportedCredentials, + AcceptedResourceTypes: []proto.Message{ + &pb.ExportedCredentials{}, }, }, } } -func (r *ExportedKeyIsTooOldRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *ExportedKeyIsTooOldRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { expiryMonths := 6 ec := rsrc.GetExportedCredentials() obs := []*pb.Observation{} @@ -47,7 +49,7 @@ func (r *ExportedKeyIsTooOldRule) Check(ctx context.Context, rsrc *pb.Resource) ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue("later creation date"), ObservedValue: structpb.NewStringValue(ec.CreationDate.AsTime().Format(timeFormat)), @@ -64,6 +66,7 @@ func (r *ExportedKeyIsTooOldRule) Check(ctx context.Context, rsrc *pb.Resource) expiryMonths, ), }, + Severity: pb.Severity_SEVERITY_MEDIUM, } obs = append(obs, ob) diff --git a/src/engine/rules/exported_key_expiry_too_long_test.go b/src/engine/rules/exported_key_expiry_too_long_test.go index 6e8f8f0..3554385 100644 --- a/src/engine/rules/exported_key_expiry_too_long_test.go +++ b/src/engine/rules/exported_key_expiry_too_long_test.go @@ -4,12 +4,12 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) func TestExportedKeyTooOld(t *testing.T) { @@ -17,6 +17,19 @@ func TestExportedKeyTooOld(t *testing.T) { yesterday := now.Add(time.Hour * -24) tomorrow := now.Add(time.Hour * 24) oneYearAgo := now.Add(-time.Hour * 24 * 365) + + outdatedExportedKey := &pb.Resource{ + Name: "outdated-exported-key", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ExportedCredentials{ + ExportedCredentials: &pb.ExportedCredentials{ + CreationDate: timestamppb.New(oneYearAgo), + ExpirationDate: timestamppb.New(tomorrow), + }, + }, + } resources := []*pb.Resource{ { Name: testProjectName, @@ -39,35 +52,23 @@ func TestExportedKeyTooOld(t *testing.T) { }, }, }, - { - Name: "outdated-exported-key", - Parent: testProjectName, - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ExportedCredentials{ - ExportedCredentials: &pb.ExportedCredentials{ - CreationDate: timestamppb.New(oneYearAgo), - ExpirationDate: timestamppb.New(tomorrow), - }, - }, - }, + outdatedExportedKey, } - got := TestRuleRun(t, resources, []model.Rule{NewExportedKeyIsTooOldRule()}) - // Expected values are ordered lexicographically. want := []*pb.Observation{ { - Name: ExportedKeyIsTooOld, - Resource: &pb.Resource{ - Name: "outdated-exported-key", - }, + Name: ExportedKeyIsTooOld, + ResourceRef: utils.GetResourceRef(outdatedExportedKey), ExpectedValue: structpb.NewStringValue("later creation date"), ObservedValue: structpb.NewStringValue(oneYearAgo.Format("2006-01-02 15:04:05 +0000 UTC")), + Remediation: &pb.Remediation{ + Description: "Exported key [\"outdated-exported-key\"](https://console.cloud.google.com/apis/credentials?project=project-0) is too long lived", + Recommendation: "Rotate the exported key [\"outdated-exported-key\"](https://console.cloud.google.com/apis/credentials?project=project-0) every 6 months", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, } - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewExportedKeyIsTooOldRule()}, want) } diff --git a/src/engine/rules/exported_key_with_admin_privileges.go b/src/engine/rules/exported_key_with_admin_privileges.go index 7d8e8bd..b5778ee 100644 --- a/src/engine/rules/exported_key_with_admin_privileges.go +++ b/src/engine/rules/exported_key_with_admin_privileges.go @@ -5,13 +5,14 @@ import ( "fmt" "github.com/google/uuid" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" - "github.com/nianticlabs/modron/src/engine" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) const ExportedKeyWithAdminPrivileges = "EXPORTED_KEY_WITH_ADMIN_PRIVILEGES" @@ -41,18 +42,18 @@ func NewExportedKeyWithAdminPrivilegesRule() model.Rule { return &ExportedKeyWithAdminPrivilegesRule{ info: model.RuleInfo{ Name: ExportedKeyWithAdminPrivileges, - AcceptedResourceTypes: []string{ - common.ResourceServiceAccount, + AcceptedResourceTypes: []proto.Message{ + &pb.ServiceAccount{}, }, }, } } -func (r *ExportedKeyWithAdminPrivilegesRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *ExportedKeyWithAdminPrivilegesRule) Check(ctx context.Context, e model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { sa := rsrc.GetServiceAccount() - rsrcGroup, err := engine.GetResource(ctx, rsrc.Parent) + rsrcGroup, err := e.GetResource(ctx, rsrc.Parent) if err != nil { - return nil, []error{fmt.Errorf("error retrieving resource group of resource %q: %v", rsrc.Name, err)} + return nil, []error{fmt.Errorf("error retrieving resource group of resource %q: %w", rsrc.Name, err)} } policy := rsrcGroup.IamPolicy hasAdminRoles := false @@ -75,7 +76,7 @@ func (r *ExportedKeyWithAdminPrivilegesRule) Check(ctx context.Context, rsrc *pb ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue("0 keys"), ObservedValue: structpb.NewStringValue(fmt.Sprintf("%v keys", nbEx)), @@ -92,6 +93,7 @@ func (r *ExportedKeyWithAdminPrivilegesRule) Check(ctx context.Context, rsrc *pb constants.ResourceWithoutProjectsPrefix(rsrcGroup.Name), ), }, + Severity: pb.Severity_SEVERITY_CRITICAL, } obs = append(obs, ob) } diff --git a/src/engine/rules/exported_key_with_admin_privileges_test.go b/src/engine/rules/exported_key_with_admin_privileges_test.go index 0a7e419..9c4de7f 100644 --- a/src/engine/rules/exported_key_with_admin_privileges_test.go +++ b/src/engine/rules/exported_key_with_admin_privileges_test.go @@ -3,45 +3,75 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) func TestExportedKeyWithAdminPrivileges(t *testing.T) { + account1 := &pb.Resource{ + Name: "account-1", + Parent: testProjectName, + ResourceGroupName: testProjectName, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{ + {CreationDate: timestamppb.Now()}, + }, + }, + }, + } + account2 := &pb.Resource{ + Name: "account-2", + Parent: testProjectName, + ResourceGroupName: testProjectName, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{ + {CreationDate: timestamppb.Now()}, + {CreationDate: timestamppb.Now()}, + }, + }, + }, + } + resources := []*pb.Resource{ { Name: testProjectName, ResourceGroupName: testProjectName, - CollectionUid: collectId, + CollectionUid: collectID, IamPolicy: &pb.IamPolicy{ Resource: nil, Permissions: []*pb.Permission{ { Role: "owner", Principals: []string{ - "account-1", + "serviceAccount:account-1", }, }, { Role: "iam.securityAdmin", Principals: []string{ - "account-2", + "serviceAccount:account-2", }, }, { Role: "viewer", Principals: []string{ - "account-3-no-admin-privileges", + "serviceAccount:account-3-no-admin-privileges", }, }, { Role: "editor", Principals: []string{ - "account-no-exported-credentials", + "serviceAccount:account-no-exported-credentials", }, }, }, @@ -50,40 +80,13 @@ func TestExportedKeyWithAdminPrivileges(t *testing.T) { ResourceGroup: &pb.ResourceGroup{}, }, }, - { - Name: "account-1", - Parent: testProjectName, - ResourceGroupName: testProjectName, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{ - {CreationDate: timestamppb.Now()}, - }, - }, - }, - }, - { - Name: "account-2", - Parent: testProjectName, - ResourceGroupName: testProjectName, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{ - {CreationDate: timestamppb.Now()}, - {CreationDate: timestamppb.Now()}, - }, - }, - }, - }, + account1, + account2, { Name: "account-3-no-admin-privileges", Parent: testProjectName, ResourceGroupName: testProjectName, - CollectionUid: collectId, + CollectionUid: collectID, IamPolicy: &pb.IamPolicy{}, Type: &pb.Resource_ServiceAccount{ ServiceAccount: &pb.ServiceAccount{ @@ -99,7 +102,7 @@ func TestExportedKeyWithAdminPrivileges(t *testing.T) { Name: "account-no-exported-credentials", Parent: testProjectName, ResourceGroupName: testProjectName, - CollectionUid: collectId, + CollectionUid: collectID, IamPolicy: &pb.IamPolicy{}, Type: &pb.Resource_ServiceAccount{ ServiceAccount: &pb.ServiceAccount{ @@ -111,26 +114,28 @@ func TestExportedKeyWithAdminPrivileges(t *testing.T) { want := []*pb.Observation{ { - Name: ExportedKeyWithAdminPrivileges, - Resource: &pb.Resource{ - Name: "account-1", - }, + Name: ExportedKeyWithAdminPrivileges, + ResourceRef: utils.GetResourceRef(account1), ExpectedValue: structpb.NewStringValue("0 keys"), ObservedValue: structpb.NewStringValue("1 keys"), + Remediation: &pb.Remediation{ + Description: "Service account [\"account-1\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=project-0) has 1 exported keys with admin privileges", + Recommendation: "Avoid exporting keys of service accounts with admin privileges, they can be copied and used outside of Niantic. Revoke the exported key by clicking on service account [\"account-1\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=project-0), switch to the KEYS tab and delete the exported key. Instead of exporting keys, make use of [workload identity](https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity) or similar concepts", + }, + Severity: pb.Severity_SEVERITY_CRITICAL, }, { - Name: ExportedKeyWithAdminPrivileges, - Resource: &pb.Resource{ - Name: "account-2", - }, + Name: ExportedKeyWithAdminPrivileges, + ResourceRef: utils.GetResourceRef(account2), ExpectedValue: structpb.NewStringValue("0 keys"), ObservedValue: structpb.NewStringValue("2 keys"), + Remediation: &pb.Remediation{ + Description: "Service account [\"account-2\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=project-0) has 2 exported keys with admin privileges", + Recommendation: "Avoid exporting keys of service accounts with admin privileges, they can be copied and used outside of Niantic. Revoke the exported key by clicking on service account [\"account-2\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=project-0), switch to the KEYS tab and delete the exported key. Instead of exporting keys, make use of [workload identity](https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity) or similar concepts", + }, + Severity: pb.Severity_SEVERITY_CRITICAL, }, } - got := TestRuleRun(t, resources, []model.Rule{NewExportedKeyWithAdminPrivilegesRule()}) - - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewExportedKeyWithAdminPrivilegesRule()}, want) } diff --git a/src/engine/rules/human_with_overprivileged_basic_role.go b/src/engine/rules/human_with_overprivileged_basic_role.go new file mode 100644 index 0000000..7323686 --- /dev/null +++ b/src/engine/rules/human_with_overprivileged_basic_role.go @@ -0,0 +1,95 @@ +package rules + +import ( + "context" + "fmt" + "strings" + + "github.com/google/uuid" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" +) + +const HumanWithOverprivilegedBasicRole = "HUMAN_WITH_OVERPRIVILEGED_BASIC_ROLE" + +// Define basic roles. +var basicRoles = map[constants.Role]struct{}{ + constants.GCPEditorRole: {}, + constants.GCPOwnerRole: {}, + constants.GCPSecurityAdminRole: {}, + constants.GCPViewerRole: {}, +} + +type HumanWithOverprivilegedBasicRoleRule struct { + info model.RuleInfo +} + +func init() { + AddRule(NewHumanWithOverprivilegedBasicRoleRule()) +} + +func NewHumanWithOverprivilegedBasicRoleRule() model.Rule { + return &HumanWithOverprivilegedBasicRoleRule{ + info: model.RuleInfo{ + Name: HumanWithOverprivilegedBasicRole, + AcceptedResourceTypes: []proto.Message{ + &pb.ResourceGroup{}, + }, + }, + } +} + +func (r *HumanWithOverprivilegedBasicRoleRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { + policy := rsrc.GetIamPolicy() + + hasBasicRoles := map[string][]string{} + if policy != nil { + for _, perm := range policy.Permissions { + if _, ok := basicRoles[constants.ToRole(perm.Role)]; !ok { + continue + } + for _, principal := range perm.Principals { + // We intentionally don't check service accounts. + // Service accounts could be member of the group, this would make a false positive if the group + // only has service accounts as members. + if strings.HasPrefix(principal, constants.GCPUserAccountPrefix) || strings.HasPrefix(principal, constants.GCPAccountGroupPrefix) { + hasBasicRoles[principal] = append(hasBasicRoles[principal], perm.Role) + } + } + } + } + + obs := []*pb.Observation{} + for principal, roles := range hasBasicRoles { + ob := &pb.Observation{ + Uid: uuid.NewString(), + Timestamp: timestamppb.Now(), + ResourceRef: utils.GetResourceRef(rsrc), + Name: r.Info().Name, + ExpectedValue: structpb.NewStringValue("No basic roles"), + ObservedValue: structpb.NewStringValue(strings.Join(roles, ", ")), + Remediation: &pb.Remediation{ + Description: fmt.Sprintf( + "Human account or group %s has overprivileged basic roles on project [%s](https://console.cloud.google.com/iam-admin/iam?project=%s)", + principal, + constants.ResourceWithoutProjectsPrefix(rsrc.GetResourceGroupName()), + constants.ResourceWithoutProjectsPrefix(rsrc.GetResourceGroupName()), + ), + Recommendation: "Consider assigning \"Developer\" to the editors and \"Owner\" to the owners instead of using basic roles.", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + } + obs = append(obs, ob) + } + return obs, nil +} + +func (r *HumanWithOverprivilegedBasicRoleRule) Info() *model.RuleInfo { + return &r.info +} diff --git a/src/engine/rules/human_with_overprivileged_basic_role_test.go b/src/engine/rules/human_with_overprivileged_basic_role_test.go new file mode 100644 index 0000000..4bdd20c --- /dev/null +++ b/src/engine/rules/human_with_overprivileged_basic_role_test.go @@ -0,0 +1,154 @@ +package rules + +import ( + "testing" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +func TestHumanWithOverprivilegedBasicRole(t *testing.T) { + iamPolicy := &pb.IamPolicy{ + Resource: &pb.Resource{ + Name: testProjectName, + }, + Permissions: []*pb.Permission{ + { + Role: "owner", + Principals: []string{ + "user:human-account-owner", + "group:group-owner", + }, + }, + { + Role: "iam.securityAdmin", + Principals: []string{ + "user:human-account-securityadmin", + }, + }, + { + Role: "viewer", + Principals: []string{ + "user:human-account-viewer", + }, + }, + { + Role: "editor", + Principals: []string{ + "user:human-account-editor", + }, + }, + { + Role: "non-basic", + Principals: []string{ + "human-account-non-basic", + }, + }, + }, + } + resources := []*pb.Resource{ + { + Name: testProjectName, + Parent: "", + ResourceGroupName: testProjectName, + CollectionUid: collectID, + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{}, + }, + IamPolicy: iamPolicy, + }, + } + + want := []*pb.Observation{ + { + ScanUid: proto.String("unit-test-scan"), + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-0"), + ExternalId: proto.String("projects/project-0"), + GroupName: "projects/project-0", + CloudPlatform: pb.CloudPlatform_GCP, + }, + Name: "HUMAN_WITH_OVERPRIVILEGED_BASIC_ROLE", + ExpectedValue: structpb.NewStringValue("No basic roles"), + ObservedValue: structpb.NewStringValue("owner"), + Remediation: &pb.Remediation{ + Description: "Human account or group user:human-account-owner has overprivileged basic roles on project [project-0](https://console.cloud.google.com/iam-admin/iam?project=project-0)", + Recommendation: "Consider assigning \"Developer\" to the editors and \"Owner\" to the owners instead of using basic roles.", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + }, + { + ScanUid: proto.String("unit-test-scan"), + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-0"), + ExternalId: proto.String("projects/project-0"), + GroupName: "projects/project-0", + CloudPlatform: pb.CloudPlatform_GCP, + }, + Name: "HUMAN_WITH_OVERPRIVILEGED_BASIC_ROLE", + ExpectedValue: structpb.NewStringValue("No basic roles"), + ObservedValue: structpb.NewStringValue("iam.securityAdmin"), + Remediation: &pb.Remediation{ + Description: "Human account or group user:human-account-securityadmin has overprivileged basic roles on project [project-0](https://console.cloud.google.com/iam-admin/iam?project=project-0)", + Recommendation: "Consider assigning \"Developer\" to the editors and \"Owner\" to the owners instead of using basic roles.", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + }, + { + ScanUid: proto.String("unit-test-scan"), + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-0"), + ExternalId: proto.String("projects/project-0"), + GroupName: "projects/project-0", + CloudPlatform: pb.CloudPlatform_GCP, + }, + Name: "HUMAN_WITH_OVERPRIVILEGED_BASIC_ROLE", + ExpectedValue: structpb.NewStringValue("No basic roles"), + ObservedValue: structpb.NewStringValue("owner"), + Remediation: &pb.Remediation{ + Description: "Human account or group group:group-owner has overprivileged basic roles on project [project-0](https://console.cloud.google.com/iam-admin/iam?project=project-0)", + Recommendation: "Consider assigning \"Developer\" to the editors and \"Owner\" to the owners instead of using basic roles.", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + }, + { + ScanUid: proto.String("unit-test-scan"), + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-0"), + ExternalId: proto.String("projects/project-0"), + GroupName: "projects/project-0", + CloudPlatform: pb.CloudPlatform_GCP, + }, + Name: "HUMAN_WITH_OVERPRIVILEGED_BASIC_ROLE", + ExpectedValue: structpb.NewStringValue("No basic roles"), + ObservedValue: structpb.NewStringValue("viewer"), + Remediation: &pb.Remediation{ + Description: "Human account or group user:human-account-viewer has overprivileged basic roles on project [project-0](https://console.cloud.google.com/iam-admin/iam?project=project-0)", + Recommendation: "Consider assigning \"Developer\" to the editors and \"Owner\" to the owners instead of using basic roles.", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + }, + { + ScanUid: proto.String("unit-test-scan"), + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-0"), + ExternalId: proto.String("projects/project-0"), + GroupName: "projects/project-0", + CloudPlatform: pb.CloudPlatform_GCP, + }, + Name: "HUMAN_WITH_OVERPRIVILEGED_BASIC_ROLE", + ExpectedValue: structpb.NewStringValue("No basic roles"), + ObservedValue: structpb.NewStringValue("editor"), + Remediation: &pb.Remediation{ + Description: "Human account or group user:human-account-editor has overprivileged basic roles on project [project-0](https://console.cloud.google.com/iam-admin/iam?project=project-0)", + Recommendation: "Consider assigning \"Developer\" to the editors and \"Owner\" to the owners instead of using basic roles.", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + }, + } + + TestRuleRun(t, resources, []model.Rule{NewHumanWithOverprivilegedBasicRoleRule()}, want) +} diff --git a/src/engine/rules/iap_disabled.go b/src/engine/rules/iap_disabled.go new file mode 100644 index 0000000..fd450fb --- /dev/null +++ b/src/engine/rules/iap_disabled.go @@ -0,0 +1,74 @@ +package rules + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "google.golang.org/protobuf/proto" + + "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" + + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const IAPDisabledRuleName = "IAP_DISABLED" + +type IAPDisabledRule struct { + info model.RuleInfo +} + +func init() { + AddRule(NewIAPDisabledRule()) +} + +func NewIAPDisabledRule() model.Rule { + return &IAPDisabledRule{ + info: model.RuleInfo{ + Name: IAPDisabledRuleName, + AcceptedResourceTypes: []proto.Message{ + &pb.LoadBalancer{}, + }, + }, + } +} + +func (r *IAPDisabledRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { + lb := rsrc.GetLoadBalancer() + + // TODO: Add port and name validation to have more accurate detection. + if !lb.GetIap().GetEnabled() { + ob := &pb.Observation{ + Uid: uuid.NewString(), + Timestamp: timestamppb.Now(), + ResourceRef: utils.GetResourceRef(rsrc), + Name: r.Info().Name, + ExpectedValue: structpb.NewBoolValue(true), + ObservedValue: structpb.NewBoolValue(false), + Remediation: &pb.Remediation{ + Description: fmt.Sprintf( + "IAP is disabled on Load Balancer [%q](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=%s) which exposes internal resources on the internet", + getGcpReadableResourceName(rsrc.Name), + constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), + ), + Recommendation: fmt.Sprintf( + "Enable IAP on Load Balancer [%q](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=%s) to secure the access to internal resources and prevent unauthorized access.", + getGcpReadableResourceName(rsrc.Name), + constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), + ), + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + } + obs = append(obs, ob) + } + + return +} + +func (r *IAPDisabledRule) Info() *model.RuleInfo { + return &r.info +} diff --git a/src/engine/rules/iap_disabled_test.go b/src/engine/rules/iap_disabled_test.go new file mode 100644 index 0000000..6ba25bd --- /dev/null +++ b/src/engine/rules/iap_disabled_test.go @@ -0,0 +1,79 @@ +package rules + +import ( + "testing" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" + + "google.golang.org/protobuf/types/known/structpb" +) + +func TestIAPDisabledRule(t *testing.T) { + iapDisabledResource := &pb.Resource{ + Name: "load-balancer-with-iap-disabled", + ResourceGroupName: testProjectName, + Type: &pb.Resource_LoadBalancer{ + LoadBalancer: &pb.LoadBalancer{ + Type: pb.LoadBalancer_EXTERNAL, + Iap: &pb.IAP{ + Enabled: false, + }, + }, + }, + } + iapUnspecifiedResource := &pb.Resource{ + Name: "load-balancer-with-iap-unspecified", + ResourceGroupName: testProjectName, + Type: &pb.Resource_LoadBalancer{ + LoadBalancer: &pb.LoadBalancer{ + Type: pb.LoadBalancer_EXTERNAL, + }, + }, + } + + resources := []*pb.Resource{ + { + Name: "load-balancer-with-iap-enabled", + ResourceGroupName: testProjectName, + Type: &pb.Resource_LoadBalancer{ + LoadBalancer: &pb.LoadBalancer{ + Type: pb.LoadBalancer_EXTERNAL, + Iap: &pb.IAP{ + Enabled: true, + }, + }, + }, + }, + iapDisabledResource, + iapUnspecifiedResource, + } + + want := []*pb.Observation{ + { + Name: IAPDisabledRuleName, + ResourceRef: utils.GetResourceRef(iapDisabledResource), + ExpectedValue: structpb.NewBoolValue(true), + ObservedValue: structpb.NewBoolValue(false), + Remediation: &pb.Remediation{ + Description: "IAP is disabled on Load Balancer [\"load-balancer-with-iap-disabled\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0) which exposes internal resources on the internet", + Recommendation: "Enable IAP on Load Balancer [\"load-balancer-with-iap-disabled\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0) to secure the access to internal resources and prevent unauthorized access.", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + }, + { + Name: IAPDisabledRuleName, + ResourceRef: utils.GetResourceRef(iapUnspecifiedResource), + ExpectedValue: structpb.NewBoolValue(true), + ObservedValue: structpb.NewBoolValue(false), + Remediation: &pb.Remediation{ + Description: "IAP is disabled on Load Balancer [\"load-balancer-with-iap-unspecified\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0) which exposes internal resources on the internet", + Recommendation: "Enable IAP on Load Balancer [\"load-balancer-with-iap-unspecified\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0) to secure the access to internal resources and prevent unauthorized access.", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, + }, + } + + TestRuleRun(t, resources, []model.Rule{NewIAPDisabledRule()}, want) +} diff --git a/src/engine/rules/kubernetes_vulnerability_scanning.go b/src/engine/rules/kubernetes_vulnerability_scanning.go new file mode 100644 index 0000000..e009f03 --- /dev/null +++ b/src/engine/rules/kubernetes_vulnerability_scanning.go @@ -0,0 +1,75 @@ +package rules + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" +) + +func init() { + AddRule(NewKubernetesVulnerabilityScanningDisabledRule()) +} + +const KubernetesVulnerabilityScanningDisabledRuleName = "KUBERNETES_VULNERABILITY_SCANNING_DISABLED" + +type KubernetesVulnerabilityScanningDisabled struct { +} + +func (k KubernetesVulnerabilityScanningDisabled) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { + k8s := rsrc.GetKubernetesCluster() + if k8s.Security == nil { + errs = append(errs, fmt.Errorf("no security configuration provided for the cluster")) + return + } + switch k8s.Security.VulnerabilityScanning { + case pb.KubernetesCluster_Security_VULN_SCAN_DISABLED, pb.KubernetesCluster_Security_VULN_SCAN_UNKNOWN: + obs = append(obs, &pb.Observation{ + Uid: uuid.NewString(), + Timestamp: timestamppb.Now(), + Name: k.Info().Name, + ResourceRef: utils.GetResourceRef(rsrc), + Remediation: &pb.Remediation{ + Description: "Vulnerability scanning is disabled for this cluster", + Recommendation: "Enable the vulnerability scanning for this cluster by following the [Enable advanced vulnerability insights](https://cloud.google.com/kubernetes-engine/docs/how-to/security-posture-vulnerability-scanning#enable-advanced-insights) section of the GKE docs.\n\n" + enableGkeVulnScanCmd(rsrc), + }, + ExpectedValue: structpb.NewStringValue("basic"), + ObservedValue: structpb.NewStringValue(k8s.Security.VulnerabilityScanning.String()), + Severity: pb.Severity_SEVERITY_HIGH, + }) + case pb.KubernetesCluster_Security_VULN_SCAN_BASIC, pb.KubernetesCluster_Security_VULN_SCAN_ADVANCED: + // All good + default: + errs = append(errs, fmt.Errorf("unknown vulnerability scanning type: %s", k8s.Security.VulnerabilityScanning)) + } + return +} + +func enableGkeVulnScanCmd(k8sRsrc *pb.Resource) string { + return fmt.Sprintf( + "```gcloud container clusters update %q --project=%q --location=%q --workload-vulnerability-scanning=standard```", + k8sRsrc.Name, + utils.StripProjectsPrefix(k8sRsrc.ResourceGroupName), + k8sRsrc.GetKubernetesCluster().Location, + ) +} + +func (k KubernetesVulnerabilityScanningDisabled) Info() *model.RuleInfo { + return &model.RuleInfo{ + Name: KubernetesVulnerabilityScanningDisabledRuleName, + AcceptedResourceTypes: []proto.Message{ + &pb.KubernetesCluster{}, + }, + } +} + +func NewKubernetesVulnerabilityScanningDisabledRule() model.Rule { + return &KubernetesVulnerabilityScanningDisabled{} +} diff --git a/src/engine/rules/kubernetes_vulnerability_scanning_e2e_test.go b/src/engine/rules/kubernetes_vulnerability_scanning_e2e_test.go new file mode 100644 index 0000000..e5bb306 --- /dev/null +++ b/src/engine/rules/kubernetes_vulnerability_scanning_e2e_test.go @@ -0,0 +1,19 @@ +//go:build integration + +package rules + +import ( + "testing" + + "github.com/nianticlabs/modron/src/model" +) + +func TestKubernetesVulnerabilityScanningRuleE2E(t *testing.T) { + obs, err := TestE2ERuleRun(t, []model.Rule{NewKubernetesVulnerabilityScanningDisabledRule()}) + if err != nil { + t.Fatalf("TestE2ERuleRun unexpected error: %v", err) + } + for _, o := range obs { + t.Logf("Observation: %v", o) + } +} diff --git a/src/engine/rules/kubernetes_vulnerability_scanning_test.go b/src/engine/rules/kubernetes_vulnerability_scanning_test.go new file mode 100644 index 0000000..44c75f8 --- /dev/null +++ b/src/engine/rules/kubernetes_vulnerability_scanning_test.go @@ -0,0 +1,89 @@ +package rules + +import ( + "testing" + "time" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" +) + +var k8sVulnScanningCluster = &pb.Resource{ + Uid: "my-uuid", + CollectionUid: "scan-id", + Timestamp: timestamppb.New(time.Unix(0, 0)), + DisplayName: "KubernetesCluster", + Link: "", + Name: "my-cluster", + Parent: "projects/my-project-id", + ResourceGroupName: "projects/my-project-id", + IamPolicy: nil, + Type: &pb.Resource_KubernetesCluster{ + KubernetesCluster: &pb.KubernetesCluster{ + MasterAuthorizedNetworks: []string{ + "1.1.1.1/32", + "100.100.0.5/32", + }, + PrivateCluster: false, + MasterVersion: "1.28.9-gke.1209000", + NodesVersion: "1.28.9-gke.1209000", + Location: "us-central1-b", + Security: &pb.KubernetesCluster_Security{ + VulnerabilityScanning: pb.KubernetesCluster_Security_VULN_SCAN_DISABLED, + }, + }, + }, +} + +func TestKubernetesCluster_VulnerabilityScanningDisabled(t *testing.T) { + rsrc := proto.Clone(k8sVulnScanningCluster).(*pb.Resource) + rsrc.GetKubernetesCluster().Security.VulnerabilityScanning = pb.KubernetesCluster_Security_VULN_SCAN_DISABLED + + want := []*pb.Observation{ + { + ScanUid: proto.String("unit-test-scan"), + ResourceRef: utils.GetResourceRef(rsrc), + Name: "KUBERNETES_VULNERABILITY_SCANNING_DISABLED", + Remediation: &pb.Remediation{ + Description: "Vulnerability scanning is disabled for this cluster", + Recommendation: "Enable the vulnerability scanning for this cluster by following the [Enable advanced vulnerability insights](https://cloud.google.com/kubernetes-engine/docs/how-to/security-posture-vulnerability-scanning#enable-advanced-insights) section of the GKE docs.\n\n```gcloud container clusters update \"my-cluster\" --project=\"my-project-id\" --location=\"us-central1-b\" --workload-vulnerability-scanning=standard```", + }, + ExpectedValue: structpb.NewStringValue("basic"), + ObservedValue: structpb.NewStringValue("VULN_SCAN_DISABLED"), + Severity: pb.Severity_SEVERITY_HIGH, + }, + } + TestRuleRun(t, []*pb.Resource{rsrc}, []model.Rule{NewKubernetesVulnerabilityScanningDisabledRule()}, want) +} + +func TestKubernetesCluster_VulnerabilityScanningUnknown(t *testing.T) { + rsrc := proto.Clone(k8sVulnScanningCluster).(*pb.Resource) + rsrc.GetKubernetesCluster().Security.VulnerabilityScanning = pb.KubernetesCluster_Security_VULN_SCAN_UNKNOWN + + want := []*pb.Observation{ + { + ScanUid: proto.String("unit-test-scan"), + ResourceRef: utils.GetResourceRef(rsrc), + Name: "KUBERNETES_VULNERABILITY_SCANNING_DISABLED", + Remediation: &pb.Remediation{ + Description: "Vulnerability scanning is disabled for this cluster", + Recommendation: "Enable the vulnerability scanning for this cluster by following the [Enable advanced vulnerability insights](https://cloud.google.com/kubernetes-engine/docs/how-to/security-posture-vulnerability-scanning#enable-advanced-insights) section of the GKE docs.\n\n```gcloud container clusters update \"my-cluster\" --project=\"my-project-id\" --location=\"us-central1-b\" --workload-vulnerability-scanning=standard```", + }, + ExpectedValue: structpb.NewStringValue("basic"), + ObservedValue: structpb.NewStringValue("VULN_SCAN_UNKNOWN"), + Severity: pb.Severity_SEVERITY_HIGH, + }, + } + TestRuleRun(t, []*pb.Resource{rsrc}, []model.Rule{NewKubernetesVulnerabilityScanningDisabledRule()}, want) +} + +func TestKubernetesCluster_VulnerabilityScanningBasic(t *testing.T) { + rsrc := proto.Clone(k8sVulnScanningCluster).(*pb.Resource) + rsrc.GetKubernetesCluster().Security.VulnerabilityScanning = pb.KubernetesCluster_Security_VULN_SCAN_BASIC + TestRuleRun(t, []*pb.Resource{rsrc}, []model.Rule{NewKubernetesVulnerabilityScanningDisabledRule()}, nil) +} diff --git a/src/engine/rules/lb_tls_cert_expiring_soon.go b/src/engine/rules/lb_tls_cert_expiring_soon.go new file mode 100644 index 0000000..8e6da27 --- /dev/null +++ b/src/engine/rules/lb_tls_cert_expiring_soon.go @@ -0,0 +1,87 @@ +package rules + +import ( + "context" + "fmt" + "time" + + "github.com/google/uuid" + "google.golang.org/protobuf/proto" + + "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" + + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const LoadBalancerTLSCertExpiringSoonRuleName = "LOAD_BALANCER_TLS_CERTIFICATE_EXPIRING_SOON" + +const ( + hoursInADay = 24 + oneDay = hoursInADay * time.Hour + certExpiryWarning = 90 * oneDay // 90 days +) + +type LoadBalancerTLSCertExpiringSoonRule struct { + info model.RuleInfo +} + +func init() { + AddRule(NewLoadBalancerTLSCertExpiringSoonRule()) +} + +func NewLoadBalancerTLSCertExpiringSoonRule() model.Rule { + return &LoadBalancerTLSCertExpiringSoonRule{ + info: model.RuleInfo{ + Name: LoadBalancerTLSCertExpiringSoonRuleName, + AcceptedResourceTypes: []proto.Message{ + &pb.LoadBalancer{}, + }, + }, + } +} + +func (r *LoadBalancerTLSCertExpiringSoonRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { + lb := rsrc.GetLoadBalancer() + + if lb.Type == pb.LoadBalancer_EXTERNAL && lb.SslPolicy != nil { + for _, cert := range lb.Certificates { + daysUntilExpiry := int(time.Until(cert.ExpirationDate.AsTime()).Hours() / hoursInADay) + if cert.Type == pb.Certificate_IMPORTED { + if time.Until(cert.ExpirationDate.AsTime()) < certExpiryWarning { + obs = append(obs, &pb.Observation{ + Uid: uuid.NewString(), + Timestamp: timestamppb.Now(), + ResourceRef: utils.GetResourceRef(rsrc), + Name: r.Info().Name, + ExpectedValue: structpb.NewStringValue(fmt.Sprintf("More than %d days", int(certExpiryWarning.Hours()/hoursInADay))), + ObservedValue: structpb.NewStringValue(fmt.Sprintf("%d days", daysUntilExpiry)), + Remediation: &pb.Remediation{ + Description: fmt.Sprintf( + "The TLS certificate for load balancer [%q](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=%s) will expire in %d days. Certificates should be renewed before expiry to avoid service disruptions.", + getGcpReadableResourceName(rsrc.Name), + constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), + daysUntilExpiry, + ), + Recommendation: fmt.Sprintf( + "Renew the TLS certificate for load balancer [%q](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=%s). go/renew-certificate has information on how to proceed.", + getGcpReadableResourceName(rsrc.Name), + constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), + ), + }, + Severity: pb.Severity_SEVERITY_HIGH, + }) + } + } + } + } + + return obs, errs +} + +func (r *LoadBalancerTLSCertExpiringSoonRule) Info() *model.RuleInfo { + return &r.info +} diff --git a/src/engine/rules/lb_tls_cert_expiring_soon_test.go b/src/engine/rules/lb_tls_cert_expiring_soon_test.go new file mode 100644 index 0000000..5705e25 --- /dev/null +++ b/src/engine/rules/lb_tls_cert_expiring_soon_test.go @@ -0,0 +1,106 @@ +package rules + +import ( + "testing" + "time" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +func TestCheckDetectExpiringCertificate(t *testing.T) { + in10Days := timestamppb.New(time.Now().Add(10 * 24 * time.Hour)) + resources := []*pb.Resource{ + { + Name: "lb-cert-expiring-in-10-days", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_LoadBalancer{ + LoadBalancer: &pb.LoadBalancer{ + Type: pb.LoadBalancer_EXTERNAL, + Certificates: []*pb.Certificate{ + { + ExpirationDate: in10Days, + Type: pb.Certificate_IMPORTED, + }, + }, + SslPolicy: &pb.SslPolicy{ + MinTlsVersion: pb.SslPolicy_TLS_1_2, + Profile: pb.SslPolicy_MODERN, + Name: "Great SSL Policy", + }, + }, + }, + }, + { + Name: "lb-cert-expiring-in-10-days-managed-no-report", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_LoadBalancer{ + LoadBalancer: &pb.LoadBalancer{ + Type: pb.LoadBalancer_EXTERNAL, + Certificates: []*pb.Certificate{ + { + ExpirationDate: in10Days, + Type: pb.Certificate_MANAGED, + }, + }, + SslPolicy: &pb.SslPolicy{ + MinTlsVersion: pb.SslPolicy_TLS_1_2, + Profile: pb.SslPolicy_MODERN, + Name: "Great SSL Policy", + }, + }, + }, + }, + { + Name: "lb-cert-expiring-in-10-days-internal-no-report", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_LoadBalancer{ + LoadBalancer: &pb.LoadBalancer{ + Type: pb.LoadBalancer_INTERNAL, + Certificates: []*pb.Certificate{ + { + ExpirationDate: in10Days, + Type: pb.Certificate_IMPORTED, + }, + }, + SslPolicy: &pb.SslPolicy{ + MinTlsVersion: pb.SslPolicy_TLS_1_2, + Profile: pb.SslPolicy_MODERN, + Name: "Great SSL Policy", + }, + }, + }, + }, + } + + want := []*pb.Observation{ + { + Name: LoadBalancerTLSCertExpiringSoonRuleName, + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("uuid-0"), + ExternalId: proto.String("lb-cert-expiring-in-10-days"), + GroupName: testProjectName, + CloudPlatform: pb.CloudPlatform_GCP, + }, + Remediation: &pb.Remediation{ + Description: "The TLS certificate for load balancer [\"lb-cert-expiring-in-10-days\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0) will expire in 9 days. Certificates should be renewed before expiry to avoid service disruptions.", + Recommendation: "Renew the TLS certificate for load balancer [\"lb-cert-expiring-in-10-days\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0). go/renew-certificate has information on how to proceed.", + }, + ExpectedValue: structpb.NewStringValue("More than 90 days"), + ObservedValue: structpb.NewStringValue("9 days"), + Severity: pb.Severity_SEVERITY_HIGH, + }, + } + + TestRuleRun(t, resources, []model.Rule{NewLoadBalancerTLSCertExpiringSoonRule()}, want) +} diff --git a/src/engine/rules/lb_tls_min_version_too_old.go b/src/engine/rules/lb_tls_min_version_too_old.go index 0e9c2dd..161ed6b 100644 --- a/src/engine/rules/lb_tls_min_version_too_old.go +++ b/src/engine/rules/lb_tls_min_version_too_old.go @@ -5,20 +5,21 @@ import ( "fmt" "github.com/google/uuid" + "google.golang.org/protobuf/proto" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" ) -const LbMinTlsVersionTooOldRuleName = "LOAD_BALANCER_MIN_TLS_VERSION_TOO_OLD" +const lbMinTLSVersionTooOldRule = "LOAD_BALANCER_MIN_TLS_VERSION_TOO_OLD" var ( - minTlsVersion = pb.SslPolicy_TLS_1_2 + minTLSVersion = pb.SslPolicy_TLS_1_2 protoToVersionMap = map[pb.SslPolicy_MinTlsVersion]string{ pb.SslPolicy_TLS_1_0: "TLS 1.0", pb.SslPolicy_TLS_1_1: "TLS 1.1", @@ -27,37 +28,40 @@ var ( } ) -type LbMinTlsVersionTooOldRule struct { +type LbMinTLSVersionTooOldRule struct { info model.RuleInfo } func init() { - AddRule(NewLbMinTlsVersionTooOldRule()) + AddRule(NewLbMinTLSVersionTooOldRule()) } -func NewLbMinTlsVersionTooOldRule() model.Rule { - return &LbMinTlsVersionTooOldRule{ +func NewLbMinTLSVersionTooOldRule() model.Rule { + return &LbMinTLSVersionTooOldRule{ info: model.RuleInfo{ - Name: LbMinTlsVersionTooOldRuleName, - AcceptedResourceTypes: []string{ - common.ResourceLoadBalancer, + Name: lbMinTLSVersionTooOldRule, + AcceptedResourceTypes: []proto.Message{ + &pb.LoadBalancer{}, }, }, } } -func (r *LbMinTlsVersionTooOldRule) Check(ctx context.Context, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { +func (r *LbMinTLSVersionTooOldRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { lb := rsrc.GetLoadBalancer() - sslPolicy := lb.SslPolicy - if lb.Type == pb.LoadBalancer_EXTERNAL && sslPolicy.MinTlsVersion < minTlsVersion { + if sslPolicy == nil { + return nil, []error{fmt.Errorf("SSL policy is nil")} + } + + if lb.Type == pb.LoadBalancer_EXTERNAL && sslPolicy.MinTlsVersion < minTLSVersion { obs = append(obs, &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, - ExpectedValue: structpb.NewStringValue(protoToVersionMap[minTlsVersion]), + ExpectedValue: structpb.NewStringValue(protoToVersionMap[minTLSVersion]), ObservedValue: structpb.NewStringValue(protoToVersionMap[sslPolicy.MinTlsVersion]), Remediation: &pb.Remediation{ Description: fmt.Sprintf( @@ -69,15 +73,16 @@ func (r *LbMinTlsVersionTooOldRule) Check(ctx context.Context, rsrc *pb.Resource "Configure an SSL policy for the load balancer [%q](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=%s) that has a Minimum TLS version of %s and uses e.g. the \"MODERN\" or \"RESTRICTED\" configuration", getGcpReadableResourceName(rsrc.Name), constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), - protoToVersionMap[minTlsVersion], + protoToVersionMap[minTLSVersion], ), }, + Severity: pb.Severity_SEVERITY_HIGH, }) } return obs, errs } -func (r *LbMinTlsVersionTooOldRule) Info() *model.RuleInfo { +func (r *LbMinTLSVersionTooOldRule) Info() *model.RuleInfo { return &r.info } diff --git a/src/engine/rules/lb_tls_min_version_too_old_test.go b/src/engine/rules/lb_tls_min_version_too_old_test.go index a201168..9473c5e 100644 --- a/src/engine/rules/lb_tls_min_version_too_old_test.go +++ b/src/engine/rules/lb_tls_min_version_too_old_test.go @@ -3,14 +3,31 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/types/known/structpb" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) func TestCheckDetectTooOldMinTlsVersion(t *testing.T) { + lbGcpDefault := &pb.Resource{ + Name: "lb-gcp-default", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_LoadBalancer{ + LoadBalancer: &pb.LoadBalancer{ + Type: pb.LoadBalancer_EXTERNAL, + SslPolicy: &pb.SslPolicy{ + MinTlsVersion: pb.SslPolicy_TLS_1_0, + Profile: pb.SslPolicy_COMPATIBLE, + Name: "GCP Default", + }, + }, + }, + } + resources := []*pb.Resource{ { Name: testProjectName, @@ -37,22 +54,7 @@ func TestCheckDetectTooOldMinTlsVersion(t *testing.T) { }, }, }, - { - Name: "lb-gcp-default", - Parent: testProjectName, - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_LoadBalancer{ - LoadBalancer: &pb.LoadBalancer{ - Type: pb.LoadBalancer_EXTERNAL, - SslPolicy: &pb.SslPolicy{ - MinTlsVersion: pb.SslPolicy_TLS_1_0, - Profile: pb.SslPolicy_COMPATIBLE, - Name: "GCP Default", - }, - }, - }, - }, + lbGcpDefault, { Name: "lb-gcp-default-internal", Parent: testProjectName, @@ -73,18 +75,17 @@ func TestCheckDetectTooOldMinTlsVersion(t *testing.T) { want := []*pb.Observation{ { - Name: LbMinTlsVersionTooOldRuleName, - Resource: &pb.Resource{ - Name: "lb-gcp-default", - }, + Name: lbMinTLSVersionTooOldRule, + ResourceRef: utils.GetResourceRef(lbGcpDefault), ExpectedValue: structpb.NewStringValue(protoToVersionMap[pb.SslPolicy_TLS_1_2]), ObservedValue: structpb.NewStringValue(protoToVersionMap[pb.SslPolicy_TLS_1_0]), + Remediation: &pb.Remediation{ + Description: "The load balancer [\"lb-gcp-default\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0) allows connections over an outdated TLS protocol version. Outdated protocol versions use ciphers which can be attacked by dedicated threat actors", + Recommendation: "Configure an SSL policy for the load balancer [\"lb-gcp-default\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0) that has a Minimum TLS version of TLS 1.2 and uses e.g. the \"MODERN\" or \"RESTRICTED\" configuration", + }, + Severity: pb.Severity_SEVERITY_HIGH, }, } - got := TestRuleRun(t, resources, []model.Rule{NewLbMinTlsVersionTooOldRule()}) - // Check that the observations are correct. - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewLbMinTLSVersionTooOldRule()}, want) } diff --git a/src/engine/rules/lb_user_managed_cert.go b/src/engine/rules/lb_user_managed_cert.go index 92ebf8e..673411c 100644 --- a/src/engine/rules/lb_user_managed_cert.go +++ b/src/engine/rules/lb_user_managed_cert.go @@ -5,11 +5,12 @@ import ( "fmt" "github.com/google/uuid" + "google.golang.org/protobuf/proto" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -29,14 +30,14 @@ func NewLbUserManagedCertRule() model.Rule { return &LbUserManagedCertRule{ info: model.RuleInfo{ Name: LbUserManagedCertRuleName, - AcceptedResourceTypes: []string{ - common.ResourceLoadBalancer, + AcceptedResourceTypes: []proto.Message{ + &pb.LoadBalancer{}, }, }, } } -func (r *LbUserManagedCertRule) Check(ctx context.Context, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { +func (r *LbUserManagedCertRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { lb := rsrc.GetLoadBalancer() for _, cert := range lb.Certificates { @@ -51,10 +52,10 @@ func (r *LbUserManagedCertRule) Check(ctx context.Context, rsrc *pb.Resource) (o ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, - ExpectedValue: structpb.NewNumberValue(float64(pb.Certificate_MANAGED)), - ObservedValue: structpb.NewNumberValue(float64(cert.Type)), + ExpectedValue: structpb.NewStringValue(pb.Certificate_MANAGED.String()), + ObservedValue: structpb.NewStringValue(cert.Type.String()), Remediation: &pb.Remediation{ Description: fmt.Sprintf( "Load balancer [%q](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=%s) has user-managed certificate issued by %q for the domain %q", @@ -69,6 +70,7 @@ func (r *LbUserManagedCertRule) Check(ctx context.Context, rsrc *pb.Resource) (o constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), ), }, + Severity: pb.Severity_SEVERITY_INFO, } obs = append(obs, ob) } diff --git a/src/engine/rules/lb_user_managed_cert_test.go b/src/engine/rules/lb_user_managed_cert_test.go index 43f75da..7ee6c3f 100644 --- a/src/engine/rules/lb_user_managed_cert_test.go +++ b/src/engine/rules/lb_user_managed_cert_test.go @@ -1,52 +1,37 @@ package rules import ( - "context" - "strings" + "errors" + "fmt" "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "google.golang.org/protobuf/testing/protocmp" - - "github.com/nianticlabs/modron/src/engine" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" - "github.com/nianticlabs/modron/src/storage/memstorage" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" ) func TestCheckDetectsUserManagedCertificate(t *testing.T) { - resources := []*pb.Resource{ - { - Name: testProjectName, - Parent: "", - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ResourceGroup{ - ResourceGroup: &pb.ResourceGroup{}, - }, - }, - { - Name: "lb-imported-cert-should-be-detected", - Parent: testProjectName, - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_LoadBalancer{ - LoadBalancer: &pb.LoadBalancer{ - Type: pb.LoadBalancer_EXTERNAL, - Certificates: []*pb.Certificate{ - { - Type: pb.Certificate_IMPORTED, - DomainName: "domain-0.github.com/nianticlabs/modron", - SubjectAlternativeNames: []string{}, - CreationDate: ×tamppb.Timestamp{}, - ExpirationDate: ×tamppb.Timestamp{}, - Issuer: "", - SignatureAlgorithm: "sha1WithRSAEncryption", - PemCertificateChain: ` + lbImportedCert := &pb.Resource{ + Name: "lb-imported-cert-should-be-detected", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_LoadBalancer{ + LoadBalancer: &pb.LoadBalancer{ + Type: pb.LoadBalancer_EXTERNAL, + Certificates: []*pb.Certificate{ + { + Type: pb.Certificate_IMPORTED, + DomainName: "domain-0.modron.example.com", + SubjectAlternativeNames: []string{}, + CreationDate: ×tamppb.Timestamp{}, + ExpirationDate: ×tamppb.Timestamp{}, + Issuer: "", + SignatureAlgorithm: "sha1WithRSAEncryption", + PemCertificateChain: ` -----BEGIN CERTIFICATE----- MIIFTTCCAzUCCQD9AMCeW12GEDANBgkqhkiG9w0BAQUFADBVMRAwDgYDVQQLDAdV bmtub3duMRAwDgYDVQQKDAdVbmtub3duMRAwDgYDVQQHDAdVbmtub3duMRAwDgYD @@ -109,11 +94,22 @@ func TestCheckDetectsUserManagedCertificate(t *testing.T) { muOKyutYtJqW5tqke8N7Yy9oDUlqtt6gnFE= -----END CERTIFICATE----- `, - }, }, }, }, }, + } + resources := []*pb.Resource{ + { + Name: testProjectName, + Parent: "", + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{}, + }, + }, + lbImportedCert, { Name: "lb-managed-cert-should-not-be-detected", Parent: testProjectName, @@ -125,7 +121,7 @@ func TestCheckDetectsUserManagedCertificate(t *testing.T) { Certificates: []*pb.Certificate{ { Type: pb.Certificate_MANAGED, - DomainName: "domain-1.github.com/nianticlabs/modron", + DomainName: "domain-1.modron.example.com", SubjectAlternativeNames: []string{}, CreationDate: ×tamppb.Timestamp{}, ExpirationDate: ×tamppb.Timestamp{}, @@ -201,29 +197,30 @@ func TestCheckDetectsUserManagedCertificate(t *testing.T) { }, } - want := []*structpb.Value{ - structpb.NewNumberValue(float64(pb.Certificate_MANAGED)), - } - - obs := TestRuleRun(t, resources, []model.Rule{NewLbUserManagedCertRule()}) - - got := []*structpb.Value{} - for _, ob := range obs { - got = append(got, ob.ExpectedValue) + want := []*pb.Observation{ + { + Name: LbUserManagedCertRuleName, + ObservedValue: structpb.NewStringValue("IMPORTED"), + ExpectedValue: structpb.NewStringValue("MANAGED"), + ResourceRef: utils.GetResourceRef(lbImportedCert), + Remediation: &pb.Remediation{ + Description: "Load balancer [\"lb-imported-cert-should-be-detected\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0) has user-managed certificate issued by \"\" for the domain \"domain-0.modron.example.com\"", + Recommendation: "Configure a platform-managed certificate for load balancer [\"lb-imported-cert-should-be-detected\"](https://console.cloud.google.com/net-services/loadbalancing/list/loadBalancers?project=project-0) to ensure lower management overhead, better security and prevent outages caused by certificate expiry", + }, + Severity: pb.Severity_SEVERITY_INFO, + }, } - // Check that the observations are correct. - if diff := cmp.Diff(want, got, protocmp.Transform(), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewLbUserManagedCertRule()}, want) } func TestCheckDetectsUnknownCertificate(t *testing.T) { resources := []*pb.Resource{ { - Name: "projects/project-1", - Parent: "folders/234", - IamPolicy: &pb.IamPolicy{}, + Name: "projects/project-1", + Parent: "folders/234", + IamPolicy: &pb.IamPolicy{}, + ResourceGroupName: "projects/project-1", Type: &pb.Resource_ResourceGroup{ ResourceGroup: &pb.ResourceGroup{}, }, @@ -239,7 +236,7 @@ func TestCheckDetectsUnknownCertificate(t *testing.T) { Certificates: []*pb.Certificate{ { Type: pb.Certificate_UNKNOWN, - DomainName: "domain-2.github.com/nianticlabs/modron", + DomainName: "domain-2.modron.example.com", SubjectAlternativeNames: []string{}, CreationDate: ×tamppb.Timestamp{}, ExpirationDate: ×tamppb.Timestamp{}, @@ -315,21 +312,9 @@ func TestCheckDetectsUnknownCertificate(t *testing.T) { }, } - storage := memstorage.New() - storageCtx := context.Background() - if _, err := storage.BatchCreateResources(storageCtx, resources); err != nil { - t.Errorf("AddResources unexpected error: %v", err) - } - - re := engine.New(storage, []model.Rule{NewLbUserManagedCertRule()}, []string{}) - reCtx := context.Background() - _, err := re.CheckRules(reCtx, "", []string{"projects/project-1"}) - if len(err) != 1 { - t.Fatalf("len(err) got %d, want %d", len(err), 1) - } - for _, e := range err { - if !strings.Contains(e.Error(), "unknown type") { - t.Errorf("CheckRules unexpected error string, got %q, want %q", e, "*unknown type") - } - } + TestRuleShouldFail(t, resources, []model.Rule{NewLbUserManagedCertRule()}, []error{ + fmt.Errorf("execution of rule LOAD_BALANCER_USER_MANAGED_CERTIFICATE failed: %w", + errors.New("certificate issued by \"\" for the domain \"domain-2.modron.example.com\" is of unknown type"), + ), + }) } diff --git a/src/engine/rules/master_authorized_neworks_not_set.go b/src/engine/rules/master_authorized_neworks_not_set.go index 002e3ce..8f1021e 100644 --- a/src/engine/rules/master_authorized_neworks_not_set.go +++ b/src/engine/rules/master_authorized_neworks_not_set.go @@ -5,11 +5,12 @@ import ( "fmt" "github.com/google/uuid" + "google.golang.org/protobuf/proto" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -29,21 +30,21 @@ func NewMasterAuthorizedNetworksNotSetRule() model.Rule { return &MasterAuthorizedNetworksNotSetRule{ info: model.RuleInfo{ Name: MasterAuthorizedNetworksNotSet, - AcceptedResourceTypes: []string{ - common.ResourceKubernetesCluster, + AcceptedResourceTypes: []proto.Message{ + &pb.KubernetesCluster{}, }, }, } } -func (r *MasterAuthorizedNetworksNotSetRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *MasterAuthorizedNetworksNotSetRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { k8s := rsrc.GetKubernetesCluster() - obs := []*pb.Observation{} + var obs []*pb.Observation if len(k8s.MasterAuthorizedNetworks) < 1 { ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue("not empty"), ObservedValue: structpb.NewStringValue("empty"), @@ -59,6 +60,7 @@ func (r *MasterAuthorizedNetworksNotSetRule) Check(ctx context.Context, rsrc *pb constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), ), }, + Severity: pb.Severity_SEVERITY_HIGH, } obs = append(obs, ob) } diff --git a/src/engine/rules/master_authorized_neworks_not_set_test.go b/src/engine/rules/master_authorized_neworks_not_set_test.go index d43a16a..44f6638 100644 --- a/src/engine/rules/master_authorized_neworks_not_set_test.go +++ b/src/engine/rules/master_authorized_neworks_not_set_test.go @@ -3,15 +3,26 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/types/known/structpb" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) func TestMasterAuthorizedNetworksNotSet(t *testing.T) { notSetResourceName := "master-authorized-networks-not-set" + notSetResource := &pb.Resource{ + Name: notSetResourceName, + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_KubernetesCluster{ + KubernetesCluster: &pb.KubernetesCluster{ + MasterAuthorizedNetworks: []string{}, + }, + }, + } resources := []*pb.Resource{ { Name: testProjectName, @@ -33,34 +44,22 @@ func TestMasterAuthorizedNetworksNotSet(t *testing.T) { }, }, }, - { - Name: notSetResourceName, - Parent: testProjectName, - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_KubernetesCluster{ - KubernetesCluster: &pb.KubernetesCluster{ - MasterAuthorizedNetworks: []string{}, - }, - }, - }, + notSetResource, } want := []*pb.Observation{ { - Name: MasterAuthorizedNetworksNotSet, - Resource: &pb.Resource{ - Name: notSetResourceName, - }, + Name: MasterAuthorizedNetworksNotSet, + ResourceRef: utils.GetResourceRef(notSetResource), ExpectedValue: structpb.NewStringValue("not empty"), ObservedValue: structpb.NewStringValue("empty"), + Remediation: &pb.Remediation{ + Description: "Cluster [\"master-authorized-networks-not-set\"](https://console.cloud.google.com/kubernetes/list/overview?project=project-0) does not have a [Master Authorized Network](https://cloud.google.com/kubernetes-engine/docs/how-to/authorized-networks#create_cluster) set. Without this setting, the cluster control plane is accessible to anyone", + Recommendation: "Set a [Master Authorized Network](https://cloud.google.com/kubernetes-engine/docs/how-to/authorized-networks#create_cluster) network range for cluster [\"master-authorized-networks-not-set\"](https://console.cloud.google.com/kubernetes/list/overview?project=project-0)", + }, + Severity: pb.Severity_SEVERITY_HIGH, }, } - got := TestRuleRun(t, resources, []model.Rule{NewMasterAuthorizedNetworksNotSetRule()}) - - // Check that the observations are correct. - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewMasterAuthorizedNetworksNotSetRule()}, want) } diff --git a/src/engine/rules/outdated_kubernetes_version.go b/src/engine/rules/outdated_kubernetes_version.go index b1f1c1b..68e43f8 100644 --- a/src/engine/rules/outdated_kubernetes_version.go +++ b/src/engine/rules/outdated_kubernetes_version.go @@ -7,11 +7,12 @@ import ( "strings" "github.com/google/uuid" + "google.golang.org/protobuf/proto" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -20,7 +21,7 @@ import ( const ( OutDatedKubernetesVersion = "OUTDATED_KUBERNETES_VERSION" // https://cloud.google.com/kubernetes-engine/docs/release-schedule - currentK8sVersion = 1.23 + currentK8sVersion = 1.27 ) type OutDatedKubernetesVersionRule struct { @@ -35,17 +36,17 @@ func NewOutDatedKubernetesVersionRule() model.Rule { return &OutDatedKubernetesVersionRule{ info: model.RuleInfo{ Name: OutDatedKubernetesVersion, - AcceptedResourceTypes: []string{ - common.ResourceKubernetesCluster, + AcceptedResourceTypes: []proto.Message{ + &pb.KubernetesCluster{}, }, }, } } -func (r *OutDatedKubernetesVersionRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *OutDatedKubernetesVersionRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { k8s := rsrc.GetKubernetesCluster() - obs := []*pb.Observation{} - errs := []error{} + var obs []*pb.Observation + var errs []error if k8s == nil { errs = append(errs, fmt.Errorf("no kubernetes cluster resource provided")) return obs, errs @@ -71,7 +72,7 @@ func (r *OutDatedKubernetesVersionRule) Check(ctx context.Context, rsrc *pb.Reso ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue(fmt.Sprintf("version > %.2f", currentK8sVersion)), ObservedValue: structpb.NewStringValue(k8s.MasterVersion), @@ -88,6 +89,7 @@ func (r *OutDatedKubernetesVersionRule) Check(ctx context.Context, rsrc *pb.Reso currentK8sVersion, ), }, + Severity: pb.Severity_SEVERITY_HIGH, } obs = append(obs, ob) } @@ -96,7 +98,7 @@ func (r *OutDatedKubernetesVersionRule) Check(ctx context.Context, rsrc *pb.Reso ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue(fmt.Sprintf("version > %.2f", currentK8sVersion)), ObservedValue: structpb.NewStringValue(k8s.NodesVersion), @@ -113,6 +115,7 @@ func (r *OutDatedKubernetesVersionRule) Check(ctx context.Context, rsrc *pb.Reso currentK8sVersion, ), }, + Severity: pb.Severity_SEVERITY_HIGH, } obs = append(obs, ob) } diff --git a/src/engine/rules/outdated_kubernetes_version_test.go b/src/engine/rules/outdated_kubernetes_version_test.go index 9027b9c..8734325 100644 --- a/src/engine/rules/outdated_kubernetes_version_test.go +++ b/src/engine/rules/outdated_kubernetes_version_test.go @@ -4,14 +4,27 @@ import ( "fmt" "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/types/known/structpb" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) func TestOutdatedKubernetesVersionDetection(t *testing.T) { + clusterWithOutdatedNodesVersion := &pb.Resource{ + Name: "cluster-with-outdated-nodes-version", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_KubernetesCluster{ + KubernetesCluster: &pb.KubernetesCluster{ + PrivateCluster: true, + MasterVersion: "1.27.10-gke.600", + NodesVersion: "1.15.10-gke.600", + }, + }, + } resources := []*pb.Resource{ { Name: testProjectName, @@ -35,36 +48,22 @@ func TestOutdatedKubernetesVersionDetection(t *testing.T) { }, }, }, - { - Name: "cluster-with-outdated-nodes-version", - Parent: testProjectName, - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_KubernetesCluster{ - KubernetesCluster: &pb.KubernetesCluster{ - PrivateCluster: true, - MasterVersion: "1.27.10-gke.600", - NodesVersion: "1.15.10-gke.600", - }, - }, - }, + clusterWithOutdatedNodesVersion, } want := []*pb.Observation{ { - Name: OutDatedKubernetesVersion, - Resource: &pb.Resource{ - Name: "cluster-with-outdated-nodes-version", - }, + Name: OutDatedKubernetesVersion, + ResourceRef: utils.GetResourceRef(clusterWithOutdatedNodesVersion), ExpectedValue: structpb.NewStringValue(fmt.Sprintf("version > %.2f", currentK8sVersion)), ObservedValue: structpb.NewStringValue("1.15.10-gke.600"), + Remediation: &pb.Remediation{ + Description: "Cluster [\"cluster-with-outdated-nodes-version\"](https://console.cloud.google.com/kubernetes/list/overview?project=project-0) uses an outdated Kubernetes version", + Recommendation: "Update the Kubernetes version on cluster [\"cluster-with-outdated-nodes-version\"](https://console.cloud.google.com/kubernetes/list/overview?project=project-0) to at least 1.27. For more details on this process, see [this article](https://cloud.google.com/kubernetes-engine/docs/how-to/upgrading-a-cluster)", + }, + Severity: pb.Severity_SEVERITY_HIGH, }, } - got := TestRuleRun(t, resources, []model.Rule{NewOutDatedKubernetesVersionRule()}) - - // Check that the observations are correct. - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewOutDatedKubernetesVersionRule()}, want) } diff --git a/src/engine/rules/private_google_access_disabled.go b/src/engine/rules/private_google_access_disabled.go index 21a684d..870fa69 100644 --- a/src/engine/rules/private_google_access_disabled.go +++ b/src/engine/rules/private_google_access_disabled.go @@ -5,12 +5,14 @@ import ( "fmt" "github.com/google/uuid" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) const PrivateGoogleAccessDisabled = "PRIVATE_GOOGLE_ACCESS_DISABLED" @@ -27,22 +29,22 @@ func NewPrivateGoogleAccessDisabledRule() model.Rule { return &PrivateGoogleAccessDisabledRule{ info: model.RuleInfo{ Name: PrivateGoogleAccessDisabled, - AcceptedResourceTypes: []string{ - common.ResourceNetwork, + AcceptedResourceTypes: []proto.Message{ + &pb.Network{}, }, }, } } -func (r *PrivateGoogleAccessDisabledRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *PrivateGoogleAccessDisabledRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { net := rsrc.GetNetwork() - obs := []*pb.Observation{} + var obs []*pb.Observation if !net.GcpPrivateGoogleAccessV4 { ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue("enabled"), ObservedValue: structpb.NewStringValue("disabled"), @@ -60,6 +62,7 @@ func (r *PrivateGoogleAccessDisabledRule) Check(ctx context.Context, rsrc *pb.Re constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), ), }, + Severity: pb.Severity_SEVERITY_LOW, } obs = append(obs, ob) diff --git a/src/engine/rules/private_google_access_disabled_test.go b/src/engine/rules/private_google_access_disabled_test.go index c56b1b0..4aa9ad5 100644 --- a/src/engine/rules/private_google_access_disabled_test.go +++ b/src/engine/rules/private_google_access_disabled_test.go @@ -3,16 +3,26 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" ) func TestCheckDetectsPrivateGoogleAccessDisabled(t *testing.T) { + networkNoPrivateAccess := &pb.Resource{ + Name: "network-no-private-access", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_Network{ + Network: &pb.Network{ + Ips: []string{"8.8.4.4"}, + GcpPrivateGoogleAccessV4: false, + }, + }, + } resources := []*pb.Resource{ { Name: testProjectName, @@ -23,18 +33,7 @@ func TestCheckDetectsPrivateGoogleAccessDisabled(t *testing.T) { ResourceGroup: &pb.ResourceGroup{}, }, }, - { - Name: "network-no-private-access", - Parent: testProjectName, - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_Network{ - Network: &pb.Network{ - Ips: []string{"8.8.4.4"}, - GcpPrivateGoogleAccessV4: false, - }, - }, - }, + networkNoPrivateAccess, { Name: "network-private-access", Parent: testProjectName, @@ -51,19 +50,17 @@ func TestCheckDetectsPrivateGoogleAccessDisabled(t *testing.T) { want := []*pb.Observation{ { - Name: PrivateGoogleAccessDisabled, - Resource: &pb.Resource{ - Name: "network-no-private-access", - }, + Name: PrivateGoogleAccessDisabled, + ResourceRef: utils.GetResourceRef(networkNoPrivateAccess), ObservedValue: structpb.NewStringValue("disabled"), ExpectedValue: structpb.NewStringValue("enabled"), + Remediation: &pb.Remediation{ + Description: "Network [\"network-no-private-access\"](https://console.cloud.google.com/networking/networks/details/network-no-private-access?project=project-0) has [Private Google Access](https://cloud.google.com/vpc/docs/configure-private-google-access) disabled. Private Google Access allows the workloads to access Google APIs via a private network which is safer than going over the public Internet", + Recommendation: "Enable [Private Google Access](https://cloud.google.com/vpc/docs/configure-private-google-access) for Network [\"network-no-private-access\"](https://console.cloud.google.com/networking/networks/details/network-no-private-access?project=project-0)", + }, + Severity: pb.Severity_SEVERITY_LOW, }, } - got := TestRuleRun(t, resources, []model.Rule{NewPrivateGoogleAccessDisabledRule()}) - - // Check that the observations are correct. - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewPrivateGoogleAccessDisabledRule()}, want) } diff --git a/src/engine/rules/registry.go b/src/engine/rules/registry.go index d8050d5..afc0f62 100644 --- a/src/engine/rules/registry.go +++ b/src/engine/rules/registry.go @@ -10,6 +10,11 @@ import ( var rules sync.Map func AddRule(r model.Rule) { + ruleName := r.Info().Name + _, ok := rules.Load(ruleName) + if ok { + panic(fmt.Sprintf("rule %q already exists", ruleName)) + } rules.Store(r.Info().Name, r) } @@ -21,9 +26,8 @@ func GetRule(name string) (model.Rule, error) { } func GetRules() []model.Rule { - rulesSnapshot := []model.Rule{} - - rules.Range(func(name, rule any) bool { + var rulesSnapshot []model.Rule + rules.Range(func(_, rule any) bool { rulesSnapshot = append(rulesSnapshot, rule.(model.Rule)) return true }) diff --git a/src/engine/rules/registry_test.go b/src/engine/rules/registry_test.go index a1df937..414eb50 100644 --- a/src/engine/rules/registry_test.go +++ b/src/engine/rules/registry_test.go @@ -4,8 +4,10 @@ import ( "context" "testing" + "google.golang.org/protobuf/proto" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" ) type TestRule struct { @@ -16,12 +18,12 @@ func NewTestRule(name string) *TestRule { return &TestRule{ info: model.RuleInfo{ Name: name, - AcceptedResourceTypes: []string{}, + AcceptedResourceTypes: []proto.Message{}, }, } } -func (r *TestRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *TestRule) Check(context.Context, model.Engine, *pb.Resource) ([]*pb.Observation, []error) { return []*pb.Observation{{}}, nil } diff --git a/src/engine/rules/svc_account_too_high_privileges.go b/src/engine/rules/svc_account_too_high_privileges.go index 9252992..25ad229 100644 --- a/src/engine/rules/svc_account_too_high_privileges.go +++ b/src/engine/rules/svc_account_too_high_privileges.go @@ -6,12 +6,12 @@ import ( "github.com/google/uuid" "golang.org/x/exp/slices" + "google.golang.org/protobuf/proto" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" - "github.com/nianticlabs/modron/src/engine" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -46,21 +46,21 @@ func NewTooHighPrivilegesRule() model.Rule { return &TooHighPrivilegesRule{ info: model.RuleInfo{ Name: TooHighPrivilegesRuleName, - AcceptedResourceTypes: []string{ - common.ResourceServiceAccount, + AcceptedResourceTypes: []proto.Message{ + &pb.ServiceAccount{}, }, }, } } -func (r *TooHighPrivilegesRule) Check(ctx context.Context, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { - rsrcGroup, err := engine.GetResource(ctx, rsrc.Parent) +func (r *TooHighPrivilegesRule) Check(ctx context.Context, e model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { + rsrcGroup, err := e.GetResource(ctx, rsrc.Parent) if err != nil { - errs = append(errs, fmt.Errorf("error retrieving resource group of resource %q: %v", rsrc.Name, err)) + errs = append(errs, fmt.Errorf("error retrieving resource group of resource %q: %w", rsrc.Name, err)) return } policy := rsrcGroup.IamPolicy - roles := []string{} + var roles []string if policy != nil { for _, perm := range policy.Permissions { @@ -78,7 +78,7 @@ func (r *TooHighPrivilegesRule) Check(ctx context.Context, rsrc *pb.Resource) (o ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue(""), ObservedValue: structpb.NewStringValue(role), @@ -99,6 +99,7 @@ func (r *TooHighPrivilegesRule) Check(ctx context.Context, rsrc *pb.Resource) (o dangerousRoles, ), }, + Severity: pb.Severity_SEVERITY_MEDIUM, } obs = append(obs, ob) } diff --git a/src/engine/rules/svc_account_too_high_privileges_test.go b/src/engine/rules/svc_account_too_high_privileges_test.go index a689812..e39ca86 100644 --- a/src/engine/rules/svc_account_too_high_privileges_test.go +++ b/src/engine/rules/svc_account_too_high_privileges_test.go @@ -3,48 +3,104 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "google.golang.org/protobuf/types/known/structpb" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) const ( - collectId = "collectId-1" + collectID = "collectID-1" ) func TestCheckDetectsHighPrivilege(t *testing.T) { // Because of the new memoization we need a specific project name for this test. - testProjectName := "projects/test-project" + uuid.NewString() - testProjectName1 := "projects/test-project1" + uuid.NewString() + testProjectName := "projects/check-detects-high-privilege-0" + testProjectName1 := "projects/check-detects-high-privilege-1" + + account0 := &pb.Resource{ + Uid: uuid.NewString(), + Name: "account-0", + Parent: testProjectName, + ResourceGroupName: testProjectName, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, + }, + }, + } + + account1 := &pb.Resource{ + Uid: uuid.NewString(), + Name: "account-1", + Parent: testProjectName, + ResourceGroupName: testProjectName, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, + }, + }, + } + + account2 := &pb.Resource{ + Uid: uuid.NewString(), + Name: "account-2", + Parent: testProjectName1, + ResourceGroupName: testProjectName1, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, + }, + }, + } + + account3 := &pb.Resource{ + Uid: uuid.NewString(), + Name: "account-3", + Parent: testProjectName1, + ResourceGroupName: testProjectName1, + CollectionUid: collectID, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ServiceAccount{ + ServiceAccount: &pb.ServiceAccount{ + ExportedCredentials: []*pb.ExportedCredentials{}, + }, + }, + } + resources := []*pb.Resource{ { Name: testProjectName, Parent: "folders/123", ResourceGroupName: testProjectName, - CollectionUid: collectId, + CollectionUid: collectID, IamPolicy: &pb.IamPolicy{ Resource: nil, Permissions: []*pb.Permission{ { Role: "iam.serviceAccountAdmin", Principals: []string{ - "account-0", + "serviceAccount:account-0", }, }, { Role: "dataflow.admin", Principals: []string{ - "account-1", + "serviceAccount:account-1", }, }, { Role: "viewer", Principals: []string{ - "account-1", + "serviceAccount:account-1", }, }, }, @@ -57,15 +113,15 @@ func TestCheckDetectsHighPrivilege(t *testing.T) { Name: testProjectName1, Parent: "folders/234", ResourceGroupName: testProjectName1, - CollectionUid: collectId, + CollectionUid: collectID, IamPolicy: &pb.IamPolicy{ Resource: nil, Permissions: []*pb.Permission{ { Role: "iam.serviceAccountUser", Principals: []string{ - "account-2", - "account-3", + "serviceAccount:account-2", + "serviceAccount:account-3", }, }, }, @@ -74,98 +130,58 @@ func TestCheckDetectsHighPrivilege(t *testing.T) { ResourceGroup: &pb.ResourceGroup{}, }, }, - { - Uid: uuid.NewString(), - Name: "account-0", - Parent: testProjectName, - ResourceGroupName: testProjectName, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{}, - }, - }, - }, - { - Uid: uuid.NewString(), - Name: "account-1", - Parent: testProjectName, - ResourceGroupName: testProjectName, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{}, - }, - }, - }, - { - Uid: uuid.NewString(), - Name: "account-2", - Parent: testProjectName1, - ResourceGroupName: testProjectName1, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{}, - }, - }, - }, - { - Uid: uuid.NewString(), - Name: "account-3", - Parent: testProjectName1, - ResourceGroupName: testProjectName1, - CollectionUid: collectId, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_ServiceAccount{ - ServiceAccount: &pb.ServiceAccount{ - ExportedCredentials: []*pb.ExportedCredentials{}, - }, - }, - }, + account0, + account1, + account2, + account3, } want := []*pb.Observation{ { - Name: TooHighPrivilegesRuleName, - Resource: &pb.Resource{ - Name: "account-0", - }, + Name: TooHighPrivilegesRuleName, + ResourceRef: utils.GetResourceRef(account0), ExpectedValue: structpb.NewStringValue(""), ObservedValue: structpb.NewStringValue("iam.serviceAccountAdmin"), + Remediation: &pb.Remediation{ + Description: "Service account [\"account-0\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=check-detects-high-privilege-0) has over-broad role \"iam.serviceAccountAdmin\"", + Recommendation: "Replace the role \"iam.serviceAccountAdmin\" for service account [\"account-0\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=check-detects-high-privilege-0) with a predefined or custom role that grants it the **smallest set of permissions** needed to operate. This role **cannot** be any of the following: `[editor owner composer.admin dataproc.admin dataproc.editor dataflow.admin dataflow.developer iam.serviceAccountAdmin iam.serviceAccountUser iam.serviceAccountTokenCreator]` *Hint: The Security insights column can help you reduce the amount of permissions*", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, { - Name: TooHighPrivilegesRuleName, - Resource: &pb.Resource{ - Name: "account-1", - }, + Name: TooHighPrivilegesRuleName, + ResourceRef: utils.GetResourceRef(account1), ExpectedValue: structpb.NewStringValue(""), ObservedValue: structpb.NewStringValue("dataflow.admin"), + Remediation: &pb.Remediation{ + Description: "Service account [\"account-1\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=check-detects-high-privilege-0) has over-broad role \"dataflow.admin\"", + Recommendation: "Replace the role \"dataflow.admin\" for service account [\"account-1\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=check-detects-high-privilege-0) with a predefined or custom role that grants it the **smallest set of permissions** needed to operate. This role **cannot** be any of the following: `[editor owner composer.admin dataproc.admin dataproc.editor dataflow.admin dataflow.developer iam.serviceAccountAdmin iam.serviceAccountUser iam.serviceAccountTokenCreator]` *Hint: The Security insights column can help you reduce the amount of permissions*", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, { - Name: TooHighPrivilegesRuleName, - Resource: &pb.Resource{ - Name: "account-2", - }, + Name: TooHighPrivilegesRuleName, + ResourceRef: utils.GetResourceRef(account2), ExpectedValue: structpb.NewStringValue(""), ObservedValue: structpb.NewStringValue("iam.serviceAccountUser"), + Remediation: &pb.Remediation{ + Description: "Service account [\"account-2\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=check-detects-high-privilege-1) has over-broad role \"iam.serviceAccountUser\"", + Recommendation: "Replace the role \"iam.serviceAccountUser\" for service account [\"account-2\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=check-detects-high-privilege-1) with a predefined or custom role that grants it the **smallest set of permissions** needed to operate. This role **cannot** be any of the following: `[editor owner composer.admin dataproc.admin dataproc.editor dataflow.admin dataflow.developer iam.serviceAccountAdmin iam.serviceAccountUser iam.serviceAccountTokenCreator]` *Hint: The Security insights column can help you reduce the amount of permissions*", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, { - Name: TooHighPrivilegesRuleName, - Resource: &pb.Resource{ - Name: "account-3", - }, + Name: TooHighPrivilegesRuleName, + ResourceRef: utils.GetResourceRef(account3), ExpectedValue: structpb.NewStringValue(""), ObservedValue: structpb.NewStringValue("iam.serviceAccountUser"), + Remediation: &pb.Remediation{ + Description: "Service account [\"account-3\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=check-detects-high-privilege-1) has over-broad role \"iam.serviceAccountUser\"", + Recommendation: "Replace the role \"iam.serviceAccountUser\" for service account [\"account-3\"](https://console.cloud.google.com/iam-admin/serviceaccounts?project=check-detects-high-privilege-1) with a predefined or custom role that grants it the **smallest set of permissions** needed to operate. This role **cannot** be any of the following: `[editor owner composer.admin dataproc.admin dataproc.editor dataflow.admin dataflow.developer iam.serviceAccountAdmin iam.serviceAccountUser iam.serviceAccountTokenCreator]` *Hint: The Security insights column can help you reduce the amount of permissions*", + }, + Severity: pb.Severity_SEVERITY_MEDIUM, }, } - got := TestRuleRun(t, resources, []model.Rule{NewTooHighPrivilegesRule()}) - - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewTooHighPrivilegesRule()}, want) } diff --git a/src/engine/rules/testing.go b/src/engine/rules/testing.go index 358e674..32b2dc1 100644 --- a/src/engine/rules/testing.go +++ b/src/engine/rules/testing.go @@ -2,87 +2,174 @@ package rules import ( "context" - "fmt" + "encoding/json" + "errors" "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/structpb" "github.com/nianticlabs/modron/src/engine" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" "github.com/nianticlabs/modron/src/storage/memstorage" + "github.com/nianticlabs/modron/src/utils" ) const testProjectName = "projects/project-0" var observationsSorter = func(lhs, rhs *pb.Observation) bool { - return lhs.Resource.Name < rhs.Resource.Name + if lhs.ResourceRef == nil || rhs.ResourceRef == nil { + return lhs.Remediation.Description < rhs.Remediation.Description + } + + if lhs.ResourceRef.ExternalId == nil || rhs.ResourceRef.ExternalId == nil { + return lhs.Remediation.Description < rhs.Remediation.Description + } + + if *lhs.ResourceRef.ExternalId < *rhs.ResourceRef.ExternalId { + return true + } else if *lhs.ResourceRef.ExternalId > *rhs.ResourceRef.ExternalId { + return false + } + return lhs.Remediation.Description < rhs.Remediation.Description +} + +func mustMarshal[T any](v T) json.RawMessage { + b, err := json.Marshal(v) + if err != nil { + panic(err) + } + return b } -func TestRuleRun(t *testing.T, resources []*pb.Resource, rules []model.Rule) []*pb.Observation { +func testRuleRunHelper(t *testing.T, resources []*pb.Resource, rules []model.Rule) ([]*pb.Observation, []error) { t.Helper() ctx := context.Background() - storage := memstorage.New() + + allGroups := utils.GroupsFromResources(resources) + + // Fake a collection event + collectID := uuid.NewString() + for i := range resources { + resources[i].CollectionUid = collectID + } + // Fake a collection completed event, otherwise the resources cannot be found + now := time.Now() + for _, group := range allGroups { + err := storage.AddOperationLog(ctx, []*pb.Operation{{ + Id: collectID, + ResourceGroup: group, + Type: "collection", + StatusTime: timestamppb.New(now), + Status: pb.Operation_STARTED, + Reason: "", + }}) + if err != nil { + t.Fatalf("AddOperationLog unexpected error: %v", err) + } + } + // Flush Ops Log + if err := storage.FlushOpsLog(ctx); err != nil { + t.Fatalf("FlushOpsLog unexpected error: %v", err) + } if _, err := storage.BatchCreateResources(ctx, resources); err != nil { t.Fatalf("AddResources unexpected error: %v", err) } + end := time.Now() + for _, group := range allGroups { + err := storage.AddOperationLog(ctx, []*pb.Operation{{ + Id: collectID, + ResourceGroup: group, + Type: "collection", + StatusTime: timestamppb.New(end), + Status: pb.Operation_COMPLETED, + Reason: "", + }}) + if err != nil { + t.Fatalf("AddOperationLog unexpected error: %v", err) + } + } + // Flush Ops Log + if err := storage.FlushOpsLog(ctx); err != nil { + t.Fatalf("FlushOpsLog unexpected error: %v", err) + } - allGroups := groupsFromResources(resources) - - obs, err := engine.New(storage, rules, []string{}).CheckRules(ctx, "unit-test-scan", allGroups) + scanID := uuid.NewString() + e, err := engine.New(storage, rules, map[string]json.RawMessage{ + "CONTAINER_NOT_RUNNING": mustMarshal(ContainerRunningConfig{ + RequiredContainers: map[string][]string{ + "namespace-1": {"pod-prefix-1-", "pod-prefix-2-"}, + }, + }), + }, []string{}, risk.TagConfig{ + Environment: "111111111111/environment", + EmployeeData: "111111111111/employee_data", + CustomerData: "111111111111/customer_data", + }) if err != nil { - t.Fatalf("CheckRules unexpected error: %v", err) + t.Fatalf("New unexpected error: %v", err) } - return obs + return e.CheckRules(ctx, scanID, "", allGroups, nil) } -func groupsFromResources(resources []*pb.Resource) (allGroups []string) { - resourceGroups := map[string]struct{}{} - for _, r := range resources { - switch r.Type.(type) { - case *pb.Resource_ResourceGroup: - resourceGroups[r.Name] = struct{}{} - } - } - for k := range resourceGroups { - allGroups = append(allGroups, k) +func errorStrings(errs []error) []string { + var errStrs []string + for _, err := range errs { + errStrs = append(errStrs, err.Error()) } - return allGroups + return errStrs } -func observationComparer(o1, o2 *pb.Observation) bool { - if o1 == nil || o2 == nil { - return false - } - if fmt.Sprintf("%T", o1.ExpectedValue) != fmt.Sprintf("%T", o2.ExpectedValue) { - return false - } - if fmt.Sprintf("%T", o1.ObservedValue) != fmt.Sprintf("%T", o2.ObservedValue) { - return false - } - if o1.Name != o2.Name { - return false +func TestRuleShouldFail(t *testing.T, resources []*pb.Resource, rules []model.Rule, expectedErr []error) { + _, err := testRuleRunHelper(t, resources, rules) + if diff := cmp.Diff(errorStrings(expectedErr), errorStrings(err)); diff != "" { + t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) } - if o1.Resource.Name != o2.Resource.Name { - return false +} + +func TestRuleRun(t *testing.T, resources []*pb.Resource, rules []model.Rule, want []*pb.Observation) { + got, errArr := testRuleRunHelper(t, resources, rules) + if len(errArr) > 0 { + t.Fatalf("CheckRules unexpected error: %v", errors.Join(errArr...)) } - switch o1.ExpectedValue.Kind.(type) { - case *structpb.Value_StringValue: - if o1.ExpectedValue.GetStringValue() == o2.ExpectedValue.GetStringValue() && o1.ObservedValue.GetStringValue() == o2.ObservedValue.GetStringValue() { - return true + for _, obs := range got { + if obs.Uid == "" { + t.Errorf("CheckRules unexpected empty UID") } - return false - case *structpb.Value_NumberValue: - if o1.ExpectedValue.GetNumberValue() == o2.ExpectedValue.GetNumberValue() && o1.ObservedValue.GetNumberValue() == o2.ObservedValue.GetNumberValue() { - return true + if obs.Timestamp == nil { + t.Errorf("CheckRules unexpected nil timestamp") } - return false - case *structpb.Value_BoolValue: - if o1.ExpectedValue.GetBoolValue() == o2.ExpectedValue.GetBoolValue() && o1.ObservedValue.GetBoolValue() == o2.ObservedValue.GetBoolValue() { - return true + if obs.Severity == pb.Severity_SEVERITY_UNKNOWN { + t.Errorf("CheckRules unexpected unknown severity") } - return false - default: - panic(fmt.Sprintf("comparison for type %T not implemented", o1.ExpectedValue.Kind)) + } + + // We add some fields to `want` so that we don't have to change the test data every time + // we modify one "meta" field: + for i, ob := range want { + ob.Source = pb.Observation_SOURCE_MODRON + ob.RiskScore = ob.Severity + ob.Impact = pb.Impact_IMPACT_MEDIUM + want[i] = ob + } + + if diff := cmp.Diff(want, got, protocmp.Transform(), protocmp.IgnoreFields( + &pb.Observation{}, + "timestamp", + "uid", + "scan_uid", + ), + protocmp.IgnoreFields(&pb.ResourceRef{}, "uid"), + protocmp.IgnoreUnknown(), + cmpopts.SortSlices(observationsSorter), + ); diff != "" { + t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) } } diff --git a/src/engine/rules/testing_e2e.go b/src/engine/rules/testing_e2e.go new file mode 100644 index 0000000..01099b9 --- /dev/null +++ b/src/engine/rules/testing_e2e.go @@ -0,0 +1,61 @@ +package rules + +import ( + "context" + "encoding/json" + "errors" + "os" + "testing" + + "github.com/sirupsen/logrus" + + "github.com/nianticlabs/modron/src/collector/gcpcollector" + "github.com/nianticlabs/modron/src/engine" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/storage/memstorage" +) + +func TestE2ERuleRun(t *testing.T, rules []model.Rule) ([]*pb.Observation, error) { + t.Helper() + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + projectID := os.Getenv("PROJECT_ID") + if projectID == "" { + t.Skip("PROJECT_ID is not set") + } + orgID := os.Getenv("ORG_ID") + if orgID == "" { + t.Skip("ORG_ID is not set") + } + orgSuffix := os.Getenv("ORG_SUFFIX") + if orgSuffix == "" { + t.Skip("ORG_SUFFIX is not set") + } + tagConfig := risk.TagConfig{ + Environment: "111111111111/environment", + EmployeeData: "111111111111/employee_data", + CustomerData: "111111111111/customer_data", + } + qualifiedProjectID := "projects/" + projectID + ctx := context.Background() + storage := memstorage.New() + collector, err := gcpcollector.New(ctx, storage, orgID, orgSuffix, []string{}, tagConfig, []string{}) + if err != nil { + t.Fatalf("NewCollector unexpected error: %v", err) + } + + if err := collector.CollectAndStoreAll(ctx, "test-collect", []string{qualifiedProjectID}, nil); err != nil { + t.Fatalf("collectAndStoreResources unexpected error: %v", err) + } + + e, err := engine.New(storage, rules, map[string]json.RawMessage{}, []string{}, tagConfig) + if err != nil { + t.Fatalf("NewEngine unexpected error: %v", err) + } + obs, errArr := e.CheckRules(ctx, "unit-test-scan", "", []string{qualifiedProjectID}, nil) + if errArr != nil { + return nil, errors.Join(errArr...) + } + return obs, nil +} diff --git a/src/engine/rules/unused_exported_credentials.go b/src/engine/rules/unused_exported_credentials.go index 99bbf62..4e6ed86 100644 --- a/src/engine/rules/unused_exported_credentials.go +++ b/src/engine/rules/unused_exported_credentials.go @@ -6,21 +6,27 @@ import ( "time" "github.com/google/uuid" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/common" - "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) const ( - UnusedExportedCredentials = "UNUSED_EXPORTED_CREDENTIALS" + unusedExportedCredentials = "UNUSED_EXPORTED_CREDENTIALS" //nolint:gosec // If you increase this value, also fetch the metric over a longer timeframe in the collector. oldestUsageVerificationMonths = 3 + sevenDays = 7 * time.Hour * 24 +) + +const ( + oneMonth = time.Hour * 24 * 30 ) -var oldestUsage = time.Now().Add(time.Duration(-oldestUsageVerificationMonths) * time.Hour * 24 * 30) +var oldestUsage = time.Now().Add(time.Duration(-oldestUsageVerificationMonths) * oneMonth) type UnusedExportedCredentialsRule struct { info model.RuleInfo @@ -33,42 +39,47 @@ func init() { func NewUnusedExportedCredentialsRule() model.Rule { return &UnusedExportedCredentialsRule{ info: model.RuleInfo{ - Name: UnusedExportedCredentials, - AcceptedResourceTypes: []string{ - common.ResourceExportedCredentials, + Name: unusedExportedCredentials, + AcceptedResourceTypes: []proto.Message{ + &pb.ExportedCredentials{}, }, }, } } -func (r *UnusedExportedCredentialsRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *UnusedExportedCredentialsRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { ec := rsrc.GetExportedCredentials() - obs := []*pb.Observation{} + var obs []*pb.Observation if ec.LastUsage == nil { // If there is no last usage value, we don't report anything. return []*pb.Observation{}, []error{} } - if ec.LastUsage.AsTime().Before(oldestUsage) { + if time.Since(ec.CreationDate.AsTime()) > sevenDays && ec.LastUsage.AsTime().Before(oldestUsage) { ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue(fmt.Sprintf("%s <", oldestUsage.Format(time.RFC3339))), ObservedValue: structpb.NewStringValue(ec.LastUsage.AsTime().Format(time.RFC3339)), Remediation: &pb.Remediation{ Description: fmt.Sprintf( - "Exported key [%q](https://console.cloud.google.com/apis/credentials?project=%s) has not been used in the last %d months", - getGcpReadableResourceName(rsrc.Name), - constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), + "Exported key `%s` of [%q](https://console.cloud.google.com/iam-admin/serviceaccounts/details/%s/keys?project=%s) has not been used in the last %d months", + utils.GetKeyID(rsrc.Name), + utils.GetServiceAccountNameFromKeyRef(rsrc.Name), + utils.GetServiceAccountNameFromKeyRef(rsrc.Name), + utils.StripProjectsPrefix(rsrc.ResourceGroupName), oldestUsageVerificationMonths, ), Recommendation: fmt.Sprintf( - "Consider deleting the exported key [%q](https://console.cloud.google.com/apis/credentials?project=%s), which is no longer in use", - getGcpReadableResourceName(rsrc.Name), - constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), + "Consider deleting the exported key `%s` of [%q](https://console.cloud.google.com/iam-admin/serviceaccounts/details/%s/keys?project=%s) which is no longer in use", + utils.GetKeyID(rsrc.Name), + utils.GetServiceAccountNameFromKeyRef(rsrc.Name), + utils.GetServiceAccountNameFromKeyRef(rsrc.Name), + utils.StripProjectsPrefix(rsrc.ResourceGroupName), ), }, + Severity: pb.Severity_SEVERITY_HIGH, } obs = append(obs, ob) } diff --git a/src/engine/rules/unused_exported_credentials_test.go b/src/engine/rules/unused_exported_credentials_test.go index 7aadb4c..6249a99 100644 --- a/src/engine/rules/unused_exported_credentials_test.go +++ b/src/engine/rules/unused_exported_credentials_test.go @@ -5,12 +5,12 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" ) func TestUnusedExportedKey(t *testing.T) { @@ -19,6 +19,21 @@ func TestUnusedExportedKey(t *testing.T) { oneYearAgo := now.Add(-time.Hour * 24 * 365) threeMonthsAndOneDay := now.Add(-time.Hour * 24 * 91) oneYearAhead := now.Add(time.Hour * 24 * 365) + + resourceNotUsedInALongTime := &pb.Resource{ + Name: testProjectName + "/serviceAccounts/unused-svc-account@project-id.iam.gserviceaccount.com/keys/d88bb32b79ee4193a05ee178447e09a4", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_ExportedCredentials{ + ExportedCredentials: &pb.ExportedCredentials{ + CreationDate: timestamppb.New(oneYearAgo), + ExpirationDate: ×tamppb.Timestamp{Seconds: oneYearAhead.Unix(), Nanos: 0}, + LastUsage: timestamppb.New(threeMonthsAndOneDay), + }, + }, + } + resources := []*pb.Resource{ { Name: testProjectName, @@ -29,8 +44,9 @@ func TestUnusedExportedKey(t *testing.T) { ResourceGroup: &pb.ResourceGroup{}, }, }, + resourceNotUsedInALongTime, { - Name: "not-used-in-a-long-time", + Name: "used-yesterday", Parent: testProjectName, ResourceGroupName: testProjectName, IamPolicy: &pb.IamPolicy{}, @@ -38,40 +54,39 @@ func TestUnusedExportedKey(t *testing.T) { ExportedCredentials: &pb.ExportedCredentials{ CreationDate: timestamppb.New(oneYearAgo), ExpirationDate: ×tamppb.Timestamp{Seconds: oneYearAhead.Unix(), Nanos: 0}, - LastUsage: timestamppb.New(threeMonthsAndOneDay), + LastUsage: timestamppb.New(yesterday), }, }, }, { - Name: "used-yesterday", + Name: "created-yesterday-unused-do-not-report", Parent: testProjectName, ResourceGroupName: testProjectName, IamPolicy: &pb.IamPolicy{}, Type: &pb.Resource_ExportedCredentials{ ExportedCredentials: &pb.ExportedCredentials{ - CreationDate: timestamppb.New(oneYearAgo), + CreationDate: timestamppb.New(yesterday), ExpirationDate: ×tamppb.Timestamp{Seconds: oneYearAhead.Unix(), Nanos: 0}, - LastUsage: timestamppb.New(yesterday), + LastUsage: nil, }, }, }, } - got := TestRuleRun(t, resources, []model.Rule{NewUnusedExportedCredentialsRule()}) - // Expected values are ordered lexicographically. want := []*pb.Observation{ { - Name: UnusedExportedCredentials, - Resource: &pb.Resource{ - Name: "not-used-in-a-long-time", - }, + Name: unusedExportedCredentials, + ResourceRef: utils.GetResourceRef(resourceNotUsedInALongTime), ExpectedValue: structpb.NewStringValue(fmt.Sprintf("%s <", oldestUsage.Format(time.RFC3339))), ObservedValue: structpb.NewStringValue(threeMonthsAndOneDay.Format(time.RFC3339)), + Remediation: &pb.Remediation{ + Description: "Exported key `d88bb32b79ee4193a05ee178447e09a4` of [\"unused-svc-account@project-id.iam.gserviceaccount.com\"](https://console.cloud.google.com/iam-admin/serviceaccounts/details/unused-svc-account@project-id.iam.gserviceaccount.com/keys?project=project-0) has not been used in the last 3 months", + Recommendation: "Consider deleting the exported key `d88bb32b79ee4193a05ee178447e09a4` of [\"unused-svc-account@project-id.iam.gserviceaccount.com\"](https://console.cloud.google.com/iam-admin/serviceaccounts/details/unused-svc-account@project-id.iam.gserviceaccount.com/keys?project=project-0) which is no longer in use", + }, + Severity: pb.Severity_SEVERITY_HIGH, }, } - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewUnusedExportedCredentialsRule()}, want) } diff --git a/src/engine/rules/vm_has_public_ip.go b/src/engine/rules/vm_has_public_ip.go index 5494c1f..1dd3ac5 100644 --- a/src/engine/rules/vm_has_public_ip.go +++ b/src/engine/rules/vm_has_public_ip.go @@ -6,11 +6,12 @@ import ( "strings" "github.com/google/uuid" + "google.golang.org/protobuf/proto" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -30,21 +31,21 @@ func NewVMHasPublicIPRule() model.Rule { return &VMHasPublicIPRule{ info: model.RuleInfo{ Name: VMHasPublicIPRuleName, - AcceptedResourceTypes: []string{ - common.ResourceVmInstance, + AcceptedResourceTypes: []proto.Message{ + &pb.VmInstance{}, }, }, } } -func (r *VMHasPublicIPRule) Check(ctx context.Context, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { +func (r *VMHasPublicIPRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { vm := rsrc.GetVmInstance() if vm.PublicIp != "" && !strings.HasPrefix(rsrc.GetName(), "gke-") && len([]rune(rsrc.GetName())) <= 30 { ob := &pb.Observation{ Uid: uuid.NewString(), Timestamp: timestamppb.Now(), - Resource: rsrc, + ResourceRef: utils.GetResourceRef(rsrc), Name: r.Info().Name, ExpectedValue: structpb.NewStringValue("empty"), ObservedValue: structpb.NewStringValue(vm.PublicIp), @@ -59,6 +60,7 @@ func (r *VMHasPublicIPRule) Check(ctx context.Context, rsrc *pb.Resource) (obs [ constants.ResourceWithoutProjectsPrefix(rsrc.ResourceGroupName), ), }, + Severity: pb.Severity_SEVERITY_HIGH, } obs = append(obs, ob) } diff --git a/src/engine/rules/vm_has_public_ip_test.go b/src/engine/rules/vm_has_public_ip_test.go index acb8ca5..22107e8 100644 --- a/src/engine/rules/vm_has_public_ip_test.go +++ b/src/engine/rules/vm_has_public_ip_test.go @@ -3,16 +3,25 @@ package rules import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "google.golang.org/protobuf/types/known/structpb" ) func TestCheckVMHasPublicIP(t *testing.T) { + publicIPResource := &pb.Resource{ + Name: "public-ip", + Parent: testProjectName, + ResourceGroupName: testProjectName, + IamPolicy: &pb.IamPolicy{}, + Type: &pb.Resource_VmInstance{ + VmInstance: &pb.VmInstance{ + PublicIp: "8.8.8.8", + }, + }, + } resources := []*pb.Resource{ { Name: testProjectName, @@ -23,17 +32,7 @@ func TestCheckVMHasPublicIP(t *testing.T) { ResourceGroup: &pb.ResourceGroup{}, }, }, - { - Name: "public-ip", - Parent: testProjectName, - ResourceGroupName: testProjectName, - IamPolicy: &pb.IamPolicy{}, - Type: &pb.Resource_VmInstance{ - VmInstance: &pb.VmInstance{ - PublicIp: "8.8.8.8", - }, - }, - }, + publicIPResource, { Name: "gke-public-ip", Parent: testProjectName, @@ -69,19 +68,17 @@ func TestCheckVMHasPublicIP(t *testing.T) { want := []*pb.Observation{ { - Name: VMHasPublicIPRuleName, - Resource: &pb.Resource{ - Name: "public-ip", - }, + Name: VMHasPublicIPRuleName, + ResourceRef: utils.GetResourceRef(publicIPResource), ObservedValue: structpb.NewStringValue("8.8.8.8"), ExpectedValue: structpb.NewStringValue("empty"), + Remediation: &pb.Remediation{ + Description: "VM \"public-ip\" has a public IP assigned", + Recommendation: "Compute instances should not be configured to have external IP addresses. Update network-settings of [public-ip](https://console.cloud.google.com/compute/instances?project=project-0). You can connect to Linux VMs that do not have public IP addresses by using Identity-Aware Proxy for TCP forwarding. [Learn more](https://cloud.google.com/compute/docs/instances/connecting-advanced#sshbetweeninstances)", + }, + Severity: pb.Severity_SEVERITY_HIGH, }, } - got := TestRuleRun(t, resources, []model.Rule{NewVMHasPublicIPRule()}) - - // Check that the observations are correct. - if diff := cmp.Diff(want, got, cmp.Comparer(observationComparer), cmpopts.SortSlices(observationsSorter)); diff != "" { - t.Errorf("CheckRules unexpected diff (-want, +got): %v", diff) - } + TestRuleRun(t, resources, []model.Rule{NewVMHasPublicIPRule()}, want) } diff --git a/src/engine/runner.go b/src/engine/runner.go index 279ccb0..685d475 100644 --- a/src/engine/runner.go +++ b/src/engine/runner.go @@ -2,22 +2,61 @@ package engine import ( "context" + "encoding/json" + "errors" "fmt" "sync" "time" - "github.com/golang/glog" - "golang.org/x/exp/slices" + "github.com/google/uuid" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" + "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" + modronmetric "github.com/nianticlabs/modron/src/metric" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/utils" + + "github.com/sirupsen/logrus" +) + +var ( + log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "engine") + meter = otel.Meter("github.com/nianticlabs/modron/src/engine") + tracer = otel.Tracer("github.com/nianticlabs/modron/src/engine") +) + +const ( + checkRulesBufferSize = 100 ) type RuleEngine struct { excludedRules []string + metrics metrics rules []model.Rule + ruleConfigs map[string]json.RawMessage storage model.Storage + + tagConfig risk.TagConfig + // memoizationMap is our caching layer that holds the resources that have been fetched from the storage. + // In the current implementation, multiple engines will not share their cache. + // Ideally, to avoid fetching multiple times the same resource, we would want to share the cache between engines. + memoizationMap sync.Map + // rgHierarchyMap is a map containing the resource group hierarchy for each collection ID. + rgHierarchyMap sync.Map +} + +var _ model.Engine = (*RuleEngine)(nil) + +type metrics struct { + RulesDuration metric.Float64Histogram + CheckRulesDuration metric.Float64Histogram + CreateObservation metric.Int64Counter } type CheckRuleResult struct { @@ -26,30 +65,52 @@ type CheckRuleResult struct { errs []error } -func New(s model.Storage, rules []model.Rule, excludedRules []string) *RuleEngine { - storage = &Storage{s} - glog.Infof("new rule engine with %d rules", len(rules)) - return &RuleEngine{ +func New( + s model.Storage, + rules []model.Rule, + ruleConfigs map[string]json.RawMessage, + excludedRules []string, + tagConfig risk.TagConfig, +) (*RuleEngine, error) { + log.Debugf("new rule engine with %d rules", len(rules)) + e := &RuleEngine{ excludedRules: excludedRules, rules: rules, storage: s, + ruleConfigs: ruleConfigs, + tagConfig: tagConfig, } + err := e.initMetrics() + if err != nil { + return nil, err + } + go e.startCacheCleanup() + return e, nil } // Checks that the supplied rule applies to the provided resources. func (e *RuleEngine) checkRule(ctx context.Context, r model.Rule, resources []*pb.Resource) (obs []*pb.Observation, errs []error) { + ctx, span := tracer.Start(ctx, "checkRule") + span.SetAttributes( + attribute.String(constants.TraceKeyRule, r.Info().Name), + ) + defer span.End() for _, rsrc := range resources { - t, err := common.TypeFromResourceAsString(rsrc) + t, err := utils.TypeFromResource(rsrc) if err != nil { - errs = append(errs, fmt.Errorf("could not retrieve type from resource %q: %v", rsrc, err)) + errs = append(errs, fmt.Errorf("could not retrieve type from resource %q: %w", rsrc, err)) continue } - if !slices.Contains(r.Info().AcceptedResourceTypes, t) { + acceptedResourceTypes := map[string]struct{}{} + for _, at := range r.Info().AcceptedResourceTypes { + acceptedResourceTypes[string(at.ProtoReflect().Type().Descriptor().FullName())] = struct{}{} + } + if _, ok := acceptedResourceTypes[t]; !ok { errs = append(errs, fmt.Errorf("resource type %q is not accepted by rule %s", t, r.Info().Name)) continue } - newObs, newErrs := r.Check(ctx, rsrc) + newObs, newErrs := r.Check(ctx, e, rsrc) if len(newErrs) > 0 { errs = append(errs, newErrs...) } else { @@ -60,17 +121,76 @@ func (e *RuleEngine) checkRule(ctx context.Context, r model.Rule, resources []*p } func (e *RuleEngine) checkRuleAsync(ctx context.Context, r model.Rule, resources []*pb.Resource, ch chan *CheckRuleResult) { + ctx, span := tracer.Start(ctx, "checkRuleAsync", + trace.WithAttributes(attribute.String(constants.TraceKeyRule, r.Info().Name)), + ) + log := log.WithField("rule", r.Info().Name) + defer span.End() + start := time.Now() ret := &CheckRuleResult{ rule: r, obs: nil, errs: nil, } ret.obs, ret.errs = e.checkRule(ctx, r, resources) + // Risk Score calculation + for k, obs := range ret.obs { + ret.obs[k].RiskScore, ret.obs[k].Impact, ret.obs[k].ImpactReason = e.computeRsImpact(ctx, obs) + } + + status := modronmetric.StatusSuccess + if len(ret.errs) > 0 { + status = modronmetric.StatusError + log.Errorf("rule execution failed: %v", ret.errs) + } + e.metrics.RulesDuration. + Record(ctx, time.Since(start).Seconds(), + metric.WithAttributes( + attribute.String(modronmetric.KeyRule, r.Info().Name), + attribute.String(modronmetric.KeyStatus, status), + ), + ) ch <- ret } +// computeRsImpactSeverity calculates the risk score by taking into account some external factors such as +// the parent resource group labels (e.g: environment: prod). +// It then returns the determined impact and the original severity +func (e *RuleEngine) computeRsImpact(ctx context.Context, obs *pb.Observation) (riskScore pb.Severity, impact pb.Impact, reason string) { + riskScore = pb.Severity_SEVERITY_UNKNOWN + impact = pb.Impact_IMPACT_UNKNOWN + + collectID, ok := ctx.Value(constants.CollectIDKey).(string) + if !ok { + log.Errorf("no %s in context", constants.CollectIDKey) + return + } + mapVal, ok := e.rgHierarchyMap.Load(collectID) + if !ok { + log.Errorf("no resource group hierarchy found for %q", collectID) + return + } + rgHierarchy, ok := mapVal.(map[string]*pb.RecursiveResource) + if !ok { + log.Errorf("resource group hierarchy is not a map") + return + } + if obs.ResourceRef == nil { + log.Errorf("observation %q has no resource ref", obs.Uid) + return + } + if obs.ResourceRef.GroupName == "" { + log.Errorf("observation %q has no group name", obs.Uid) + return + } + impact, reason = risk.GetImpact(e.tagConfig, rgHierarchy, obs.ResourceRef.GroupName) + return risk.GetRiskScore(impact, obs.Severity), impact, reason +} + // Fetches accepted resources and runs each rule in the engine asynchronously. func (e *RuleEngine) checkRulesAsync(ctx context.Context, resourceGroups []string, ch chan *CheckRuleResult) (errs []error) { + ctx, span := tracer.Start(ctx, "checkRulesAsync") + defer span.End() wg := sync.WaitGroup{} for _, r := range e.rules { isExcluded := false @@ -81,36 +201,81 @@ func (e *RuleEngine) checkRulesAsync(ctx context.Context, resourceGroups []strin } } if isExcluded { - glog.V(5).Infof("rule %s excluded", r.Info().Name) + log.Infof("rule %s excluded", r.Info().Name) continue } wg.Add(1) go func(r model.Rule) { + ctx, span := tracer.Start(ctx, "goCheckRuleAsync", + trace.WithNewRoot(), + trace.WithAttributes( + attribute.String(constants.TraceKeyRule, r.Info().Name), + attribute.StringSlice(constants.TraceKeyResourceGroupNames, resourceGroups), + ), + trace.WithLinks(trace.LinkFromContext(ctx)), + ) + defer span.End() types := r.Info().AcceptedResourceTypes + acceptedTypes := utils.ProtoAcceptsTypes(types) filter := model.StorageFilter{ - ResourceTypes: types, + ResourceTypes: acceptedTypes, ResourceGroupNames: resourceGroups, + OperationID: ctx.Value(constants.CollectIDKey).(string), } if resources, err := e.storage.ListResources(ctx, filter); err != nil { - errs = append(errs, fmt.Errorf("listing accepted resources: %+v", err)) + log.Errorf("listing accepted resources: %v", err) + span.RecordError(err) + errs = append(errs, fmt.Errorf("listing accepted resources: %w", err)) } else if len(resources) < 1 { - errs = append(errs, fmt.Errorf("no resources for %+v", filter)) + log.Warnf("no resources found") + span.RecordError(err) + errs = append(errs, fmt.Errorf("no resources found")) } else { + span.SetAttributes(attribute.Int(constants.TraceKeyNumResources, len(resources))) e.checkRuleAsync(ctx, r, resources, ch) } - glog.V(5).Infof("done with rule %s", r.Info().Name) + log.Infof("done with rule %s", r.Info().Name) wg.Done() }(r) } - glog.V(5).Infof("waiting for rules to finish") + log.Infof("waiting for rules to finish") wg.Wait() return errs } -// Checks that all the supplied rules apply to resources belonging to `resourceGroups`. -func (e *RuleEngine) CheckRules(ctx context.Context, scanId string, resourceGroups []string) (obs []*pb.Observation, errs []error) { - e.logScanStatus(ctx, scanId, resourceGroups, model.OperationStarted) - checkCh := make(chan *CheckRuleResult, 100) +// CheckRules checks that all the supplied rules apply to resources belonging to `resourceGroups`. +func (e *RuleEngine) CheckRules(ctx context.Context, scanID string, collectID string, resourceGroups []string, preCollectedRgs []*pb.Resource) (obs []*pb.Observation, errs []error) { + ctx, span := tracer.Start(ctx, "CheckRules") + defer span.End() + log := log.WithFields(logrus.Fields{ + constants.LogKeyScanID: scanID, + constants.LogKeyCollectID: collectID, + constants.LogKeyResourceGroupNames: resourceGroups, + }) + log.Infof("Start CheckRules") + defer log.Infof("End CheckRules") + e.logScanStatus(ctx, scanID, resourceGroups, pb.Operation_STARTED) + ctx = context.WithValue(ctx, constants.ScanIDKey, scanID) + ctx = context.WithValue(ctx, constants.CollectIDKey, collectID) + start := time.Now() + failed := func() { + e.logScanStatus(ctx, scanID, resourceGroups, pb.Operation_CANCELLED) + e.metrics.CheckRulesDuration.Record(ctx, + time.Since(start).Seconds(), + metric.WithAttributes( + attribute.String(modronmetric.KeyStatus, modronmetric.StatusCancelled), + ), + ) + } + + // Get Resource Group hierarchy and store it in the scan-specific cache + rgHierarchy, _ := utils.ComputeRgHierarchy(preCollectedRgs) + e.rgHierarchyMap.Store(collectID, rgHierarchy) + defer func() { + e.rgHierarchyMap.Delete(collectID) + }() + + checkCh := make(chan *CheckRuleResult, checkRulesBufferSize) wg := sync.WaitGroup{} wg.Add(1) go func() { @@ -118,9 +283,9 @@ func (e *RuleEngine) CheckRules(ctx context.Context, scanId string, resourceGrou select { case <-ctx.Done(): errs = append(errs, fmt.Errorf("context cancelled: %w", ctx.Err())) - e.logScanStatus(ctx, scanId, resourceGroups, model.OperationCancelled) + failed() if err := e.storage.FlushOpsLog(ctx); err != nil { - glog.Errorf("flushing operation log: %v", err) + log.Errorf("flushing operation log: %v", err) } break case res, ok := <-checkCh: @@ -129,14 +294,28 @@ func (e *RuleEngine) CheckRules(ctx context.Context, scanId string, resourceGrou break } for _, err := range res.errs { - errs = append(errs, fmt.Errorf("execution of rule %v failed: %w", res.rule, err)) + errs = append(errs, fmt.Errorf("execution of rule %v failed: %w", res.rule.Info().Name, err)) } for _, ob := range res.obs { - ob.ScanUid = scanId + ob.ScanUid = utils.RefOrNull(scanID) + ob.Source = pb.Observation_SOURCE_MODRON + + if ob.Uid == "" { + log.Errorf("observation from rule %s has no UUID", res.rule.Info().Name) + ob.Uid = uuid.NewString() + } } + status := modronmetric.StatusSuccess if _, err := e.storage.BatchCreateObservations(ctx, res.obs); err != nil { + status = modronmetric.StatusError errs = append(errs, fmt.Errorf("creation of observations for rule %v failed: %w", res.rule, err)) } + e.metrics.CreateObservation.Add(ctx, int64(len(res.obs)), + metric.WithAttributes( + attribute.String(modronmetric.KeyRule, res.rule.Info().Name), + attribute.String(modronmetric.KeyStatus, status), + ), + ) obs = append(obs, res.obs...) } if checkCh == nil { @@ -151,25 +330,92 @@ func (e *RuleEngine) CheckRules(ctx context.Context, scanId string, resourceGrou err := e.checkRulesAsync(ctx, resourceGroups, checkCh) if len(err) > 0 { errs = append(errs, err...) - glog.Warningf("rules run for %v : %v", resourceGroups, err) + log.WithError(errors.Join(errs...)).Warnf("rules run for with errors") } - glog.V(5).Infof("closing channel") + log.Tracef("closing channel") close(checkCh) wg.Done() }() - glog.V(5).Infof("waiting for scan %s to finish", scanId) + log.Infof("waiting for scan %q to finish", scanID) wg.Wait() - e.logScanStatus(ctx, scanId, resourceGroups, model.OperationCompleted) + e.logScanStatus(ctx, scanID, resourceGroups, pb.Operation_COMPLETED) + log.Infof("scan %q done", scanID) + e.metrics.CheckRulesDuration.Record(ctx, + time.Since(start).Seconds(), + metric.WithAttributes(attribute.String(modronmetric.KeyStatus, modronmetric.StatusCompleted)), + ) return } -func (e *RuleEngine) logScanStatus(ctx context.Context, scanId string, resourceGroups []string, status model.OperationStatus) { - ops := []model.Operation{} - glog.V(5).Infof("scan %s status %s for %+v", scanId, status, resourceGroups) +func (e *RuleEngine) GetRules() []model.Rule { + return e.rules +} + +func (e *RuleEngine) GetRuleConfig(_ context.Context, ruleName string) (json.RawMessage, error) { + v, ok := e.ruleConfigs[ruleName] + if !ok { + return nil, fmt.Errorf("no configuration found for rule %q", ruleName) + } + return v, nil +} + +func (e *RuleEngine) GetHierarchy(_ context.Context, collID string) (map[string]*pb.RecursiveResource, error) { + v, ok := e.rgHierarchyMap.Load(collID) + if !ok { + return nil, fmt.Errorf("no hierarchy found for %q", collID) + } + return v.(map[string]*pb.RecursiveResource), nil +} + +func (e *RuleEngine) logScanStatus(ctx context.Context, scanID string, resourceGroups []string, status pb.Operation_Status) { + var ops []*pb.Operation + log.Infof("scan %q status %s for %+v", scanID, status, resourceGroups) for _, resourceGroup := range resourceGroups { - ops = append(ops, model.Operation{ID: scanId, ResourceGroup: resourceGroup, OpsType: "scan", StatusTime: time.Now(), Status: status}) + ops = append(ops, &pb.Operation{ + Id: scanID, + ResourceGroup: resourceGroup, + Type: "scan", + StatusTime: timestamppb.New(time.Now()), + Status: status, + }) } if err := e.storage.AddOperationLog(ctx, ops); err != nil { - glog.Warningf("log operation: %v", err) + log.Warnf("log operation: %v", err) + } +} + +func (e *RuleEngine) initMetrics() error { + rulesDurationHist, err := meter.Float64Histogram( + constants.MetricsPrefix+"rules_duration", + metric.WithDescription("Duration of rules execution"), + metric.WithUnit("s"), + ) + if err != nil { + return err } + checkRulesDurationHist, err := meter.Float64Histogram( + constants.MetricsPrefix+"check_rules_duration", + metric.WithDescription("Duration of check_rules operations"), + metric.WithUnit("s"), + ) + if err != nil { + return err + } + createObservationCounter, err := meter.Int64Counter( + constants.MetricsPrefix+"create_observation", + metric.WithDescription("Number of observations created"), + ) + if err != nil { + return err + } + e.metrics = metrics{ + RulesDuration: rulesDurationHist, + CheckRulesDuration: checkRulesDurationHist, + CreateObservation: createObservationCounter, + } + return nil +} + +func (e *RuleEngine) GetTagConfig() risk.TagConfig { + return e.tagConfig } diff --git a/src/engine/runner_integration_test.go b/src/engine/runner_integration_test.go new file mode 100644 index 0000000..5767549 --- /dev/null +++ b/src/engine/runner_integration_test.go @@ -0,0 +1,66 @@ +//go:build integration + +package engine_test + +import ( + "context" + "encoding/json" + "errors" + "os" + "testing" + + "github.com/google/uuid" + "github.com/sirupsen/logrus" + + "github.com/nianticlabs/modron/src/collector/gcpcollector" + "github.com/nianticlabs/modron/src/engine" + "github.com/nianticlabs/modron/src/engine/rules" + "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/storage/memstorage" +) + +func TestCrossEnvironmentRuleIntegration(t *testing.T) { + rgNames := []string{ + "projects/modron-dev", + } + ctx := context.Background() + st := memstorage.New() + collectID := uuid.NewString() + scanID := uuid.NewString() + + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + logrus.StandardLogger().SetFormatter(&logrus.TextFormatter{ + ForceColors: true, + }) + + orgID := os.Getenv("ORG_ID") + orgSuffix := os.Getenv("ORG_SUFFIX") + if orgID == "" || orgSuffix == "" { + t.Fatalf("ORG_ID and ORG_SUFFIX are required for this test") + } + tagConfig := risk.TagConfig{ + Environment: "111111111111/environment", + EmployeeData: "111111111111/employee_data", + CustomerData: "111111111111/customer_data", + } + + // Collect resources + c, err := gcpcollector.New(ctx, st, orgID, orgSuffix, []string{}, tagConfig, []string{}) + if err != nil { + t.Fatalf("failed to create collector: %v", err) + } + if err := c.CollectAndStoreAll(ctx, collectID, rgNames, nil); err != nil { + t.Fatalf("failed to collect resources: %v", err) + } + + e, _ := engine.New(st, []model.Rule{ + rules.NewCrossEnvironmentPermissionsRule(), + }, map[string]json.RawMessage{}, []string{}, tagConfig) + obs, errArr := e.CheckRules(ctx, scanID, collectID, rgNames, nil) + err = errors.Join(errArr...) + if err != nil { + t.Fatalf("failed to check rules: %v", err) + } + t.Logf("Observations: %v", obs) +} diff --git a/src/engine/runner_test.go b/src/engine/runner_test.go index ca977c5..2196ea9 100644 --- a/src/engine/runner_test.go +++ b/src/engine/runner_test.go @@ -2,37 +2,63 @@ package engine import ( "context" + "encoding/json" "fmt" "strings" "testing" + "time" + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/sirupsen/logrus" "golang.org/x/exp/slices" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" "github.com/nianticlabs/modron/src/storage/memstorage" + "github.com/nianticlabs/modron/src/utils" ) type TestRule struct { info model.RuleInfo } +var impactMap = map[string]pb.Impact{ + "prod": pb.Impact_IMPACT_HIGH, + "pre-prod": pb.Impact_IMPACT_MEDIUM, + "dev": pb.Impact_IMPACT_LOW, + "playground": pb.Impact_IMPACT_LOW, +} + func NewTestRule() *TestRule { return &TestRule{ info: model.RuleInfo{ Name: "TEST_RULE", - AcceptedResourceTypes: []string{common.ResourceApiKey, common.ResourceServiceAccount}, + AcceptedResourceTypes: []proto.Message{&pb.APIKey{}, &pb.ServiceAccount{}}, }, } } -func (r *TestRule) Check(ctx context.Context, rsrc *pb.Resource) ([]*pb.Observation, []error) { +func (r *TestRule) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) ([]*pb.Observation, []error) { if strings.Contains(rsrc.Name, "fail") { - return nil, []error{fmt.Errorf(rsrc.Name)} - } else { - return []*pb.Observation{{Name: rsrc.Name, Resource: &pb.Resource{}}}, nil + return nil, []error{fmt.Errorf("%s", rsrc.Name)} } + return []*pb.Observation{{ + Uid: uuid.NewString(), + Name: rsrc.Name, + ResourceRef: &pb.ResourceRef{ + Uid: proto.String(rsrc.Uid), + GroupName: rsrc.ResourceGroupName, + CloudPlatform: pb.CloudPlatform_GCP, + ExternalId: nil, + }, + Severity: pb.Severity_SEVERITY_LOW, + }}, nil } func (r *TestRule) Info() *model.RuleInfo { @@ -49,12 +75,39 @@ type TestRule2 struct { info model.RuleInfo } +type TestRuleBucketPublic struct{} + +func (t TestRuleBucketPublic) Check(_ context.Context, _ model.Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) { + if rsrc.GetBucket().GetAccessType() == pb.Bucket_PUBLIC { + obs = append(obs, &pb.Observation{ + Uid: uuid.NewString(), + Name: rsrc.Name, + Severity: pb.Severity_SEVERITY_HIGH, + ResourceRef: &pb.ResourceRef{ + Uid: proto.String(rsrc.Uid), + GroupName: rsrc.ResourceGroupName, + CloudPlatform: pb.CloudPlatform_GCP, + }, + }) + } + return +} + +func (t TestRuleBucketPublic) Info() *model.RuleInfo { + return &model.RuleInfo{ + Name: "TEST_RULE_BUCKET_PUBLIC", + AcceptedResourceTypes: []proto.Message{&pb.Bucket{}}, + } +} + +var _ model.Rule = (*TestRuleBucketPublic)(nil) + func NewTestRule1(name string) *TestRule1 { return &TestRule1{ Rule: NewTestRule(), info: model.RuleInfo{ Name: name, - AcceptedResourceTypes: []string{common.ResourceVmInstance, common.ResourceLoadBalancer}, + AcceptedResourceTypes: []proto.Message{&pb.VmInstance{}, &pb.LoadBalancer{}}, }, } } @@ -64,7 +117,7 @@ func NewTestRule2(name string) *TestRule2 { Rule: NewTestRule(), info: model.RuleInfo{ Name: name, - AcceptedResourceTypes: []string{common.ResourceVmInstance, common.ResourceNetwork}, + AcceptedResourceTypes: []proto.Message{&pb.VmInstance{}, &pb.Network{}}, }, } } @@ -81,7 +134,54 @@ func TestCheckRuleHandlesAllResourcesCorrectly(t *testing.T) { storage := memstorage.New() rule1 := NewTestRule1("TEST_RULE1") rule2 := NewTestRule2("TEST_RULE2") + collectionUID := uuid.NewString() resources := []*pb.Resource{ + { + Name: "projects/project-0", + Parent: "folders/folder-0", + ResourceGroupName: "projects/project-0", + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: "projects/project-0", + Name: "project-0", + }, + }, + Tags: map[string]string{ + "111111111111/customer_data": "111111111111/customer_data/yes", + }, + }, + { + Name: "folders/folder-0", + Parent: "organizations/1", + ResourceGroupName: "folders/folder-0", + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: "folders/folder-0", + Name: "Dev", + }, + }, + Tags: map[string]string{ + "111111111111/environment": "111111111111/environment/development", + "111111111111/employee_data": "111111111111/employee_data/no", + "111111111111/customer_data": "111111111111/customer_data/no", + }, + }, + { + Name: "organizations/1", + Parent: "", + ResourceGroupName: "organizations/1", + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: "organizations/1", + Name: "ACME Inc.", + }, + }, + Tags: map[string]string{ + "111111111111/environment": "111111111111/environment/production", + "111111111111/customer_data": "111111111111/customer_data/yes", + "111111111111/employee_data": "111111111111/employee_data/yes", + }, + }, { Name: "instance-0", Parent: "projects/project-0", @@ -138,25 +238,35 @@ func TestCheckRuleHandlesAllResourcesCorrectly(t *testing.T) { }, }, } + rgNames := map[string]struct{}{} + for i, res := range resources { + resources[i].CollectionUid = collectionUID + rgNames[res.ResourceGroupName] = struct{}{} + } rules := []model.Rule{rule1, rule2} ctx := context.Background() + registerCollectOperation(ctx, t, resources, storage, collectionUID) if _, err := storage.BatchCreateResources(ctx, resources); err != nil { t.Fatalf(`unexpected error: "%v"`, err) } - re := New(storage, rules, []string{}) + re, _ := New(storage, rules, map[string]json.RawMessage{}, []string{}, risk.TagConfig{ + Environment: "111111111111/environment", + EmployeeData: "111111111111/employee_data", + CustomerData: "111111111111/customer_data", + }) type Want struct { value string isErrorString bool } - want := []Want{} + var want []Want for _, rsrc := range resources { for _, rule := range rules { - if ty, err := common.TypeFromResourceAsString(rsrc); err != nil { - t.Errorf("common.TypeFromResourceAsString unexpected error: %v", err) - } else if slices.Contains(rule.Info().AcceptedResourceTypes, ty) { + if ty, err := utils.TypeFromResource(rsrc); err != nil { + t.Errorf("common.TypeFromResource unexpected error: %v", err) + } else if slices.Contains(utils.ProtoAcceptsTypes(rule.Info().AcceptedResourceTypes), ty) { want = append(want, Want{rsrc.Name, strings.Contains(rsrc.Name, "fail")}) } } @@ -171,7 +281,7 @@ func TestCheckRuleHandlesAllResourcesCorrectly(t *testing.T) { return 0 }) - obs, errs := re.CheckRules(ctx, "", []string{"projects/project-0", "projects/project-1"}) + obs, errs := re.CheckRules(ctx, uuid.NewString(), collectionUID, []string{"projects/project-0", "projects/project-1"}, nil) if len(obs) != 5 { t.Errorf("len(obs) got %d, want %d", len(obs), 5) } @@ -212,6 +322,340 @@ func TestCheckRuleHandlesAllResourcesCorrectly(t *testing.T) { } } +func TestCheckRuleSeverity(t *testing.T) { + st := memstorage.New() + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + logrus.StandardLogger().SetFormatter(&logrus.TextFormatter{ + ForceColors: true, + }) + ctx := context.Background() + collID := uuid.NewString() + + resources := []*pb.Resource{ + { + Name: "organizations/1", + Parent: "", + ResourceGroupName: "organizations/1", + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: "organizations/1", + Name: "ACME Inc.", + }, + }, + Tags: map[string]string{ + "111111111111/environment": "111111111111/environment/prod", + "111111111111/customer_data": "111111111111/customer_data/yes", + "111111111111/employee_data": "111111111111/employee_data/yes", + }, + CollectionUid: collID, + }, + { + Name: "folders/dev-folder", + Parent: "organizations/1", + ResourceGroupName: "folders/dev-folder", + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: "folders/dev-folder", + Name: "Dev", + }, + }, + Tags: map[string]string{ + "111111111111/environment": "111111111111/environment/dev", + "111111111111/customer_data": "111111111111/customer_data/no", + "111111111111/employee_data": "111111111111/employee_data/no", + }, + }, + { + Name: "folders/pre-prod-folder", + Parent: "organizations/1", + ResourceGroupName: "folders/pre-prod-folder", + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: "folders/pre-prod-folder", + Name: "Pre-Prod", + }, + }, + Tags: map[string]string{ + "111111111111/environment": "111111111111/environment/pre-prod", + "111111111111/customer_data": "111111111111/customer_data/yes", + "111111111111/employee_data": "111111111111/employee_data/no", + }, + }, + { + Name: "projects/project-0", + Parent: "folders/dev-folder", + ResourceGroupName: "projects/project-0", + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: "projects/project-0", + Name: "project-0", + }, + }, + Tags: map[string]string{ + "111111111111/customer_data": "111111111111/customer_data/no", + }, + }, + { + Name: "bucket-0", + Parent: "projects/project-0", + ResourceGroupName: "projects/project-0", + Type: &pb.Resource_Bucket{ + Bucket: &pb.Bucket{ + AccessType: pb.Bucket_PUBLIC, + }, + }, + }, + { + Name: "projects/project-1", + Parent: "folders/pre-prod-folder", + ResourceGroupName: "projects/project-1", + Type: &pb.Resource_ResourceGroup{ + ResourceGroup: &pb.ResourceGroup{ + Identifier: "projects/project-1", + Name: "project-1", + }, + }, + Tags: map[string]string{ + "111111111111/customer_data": "111111111111/customer_data/no", + "111111111111/employee_data": "111111111111/employee_data/no", + }, + }, + { + Name: "bucket-1", + Parent: "projects/project-1", + ResourceGroupName: "projects/project-1", + Type: &pb.Resource_Bucket{ + Bucket: &pb.Bucket{ + AccessType: pb.Bucket_PUBLIC, + }, + }, + }, + } + for k := range resources { + resources[k].CollectionUid = collID + } + registerCollectOperation(ctx, t, resources, st, collID) + if _, err := st.BatchCreateResources(ctx, resources); err != nil { + t.Fatalf(`unexpected error: "%v"`, err) + } + _ = st.FlushOpsLog(ctx) + engine, _ := New(st, []model.Rule{TestRuleBucketPublic{}}, map[string]json.RawMessage{}, []string{}, risk.TagConfig{ + ImpactMap: impactMap, + Environment: "111111111111/environment", + EmployeeData: "111111111111/employee_data", + CustomerData: "111111111111/customer_data", + }) + scanID := uuid.NewString() + got, err := engine.CheckRules(ctx, scanID, collID, []string{"projects/project-0", "projects/project-1"}, resources) + if err != nil { + t.Fatalf(`unexpected error: "%v"`, err) + } + want := []*pb.Observation{ + { + Name: "bucket-0", + ScanUid: proto.String(scanID), + Source: pb.Observation_SOURCE_MODRON, + ResourceRef: &pb.ResourceRef{ + GroupName: "projects/project-0", + CloudPlatform: pb.CloudPlatform_GCP, + }, + Impact: pb.Impact_IMPACT_LOW, + ImpactReason: "environment=dev", + Severity: pb.Severity_SEVERITY_HIGH, + RiskScore: pb.Severity_SEVERITY_MEDIUM, + }, + { + Name: "bucket-1", + ScanUid: proto.String(scanID), + Source: pb.Observation_SOURCE_MODRON, + ResourceRef: &pb.ResourceRef{ + GroupName: "projects/project-1", + CloudPlatform: pb.CloudPlatform_GCP, + }, + Impact: pb.Impact_IMPACT_MEDIUM, + ImpactReason: "environment=pre-prod", + Severity: pb.Severity_SEVERITY_HIGH, + RiskScore: pb.Severity_SEVERITY_HIGH, + }, + } + if diff := cmp.Diff(want, got, protocmp.Transform(), + protocmp.IgnoreFields(&pb.ResourceRef{}, "uid"), + protocmp.IgnoreFields(&pb.Observation{}, "uid"), + ); diff != "" { + t.Errorf(`unexpected diff (-want +got): %s`, diff) + } +} + +func registerCollectOperation(ctx context.Context, t *testing.T, resources []*pb.Resource, storage model.Storage, collectionUID string) { + now := time.Now() + for _, group := range utils.GroupsFromResources(resources) { + err := storage.AddOperationLog(ctx, []*pb.Operation{{ + Id: collectionUID, + ResourceGroup: group, + Type: "collection", + Status: pb.Operation_STARTED, + StatusTime: timestamppb.New(now), + }}) + if err != nil { + t.Fatalf(`unexpected error: "%v"`, err) + } + } + // Flush + if err := storage.FlushOpsLog(ctx); err != nil { + t.Fatalf(`unexpected error: "%v"`, err) + } + now = time.Now() + for _, group := range utils.GroupsFromResources(resources) { + err := storage.AddOperationLog(ctx, []*pb.Operation{{ + Id: collectionUID, + ResourceGroup: group, + Type: "collection", + Status: pb.Operation_COMPLETED, + StatusTime: timestamppb.New(now), + }}) + if err != nil { + t.Fatalf(`unexpected error: "%v"`, err) + } + } + // Flush + if err := storage.FlushOpsLog(ctx); err != nil { + t.Fatalf(`unexpected error: "%v"`, err) + } +} + +func getRecursiveResource(recRes *pb.RecursiveResource, children []*pb.RecursiveResource) *pb.RecursiveResource { + newRecRes := proto.Clone(recRes).(*pb.RecursiveResource) + newRecRes.Children = children + return newRecRes +} + +func TestGetImpact(t *testing.T) { + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + logrus.StandardLogger().SetFormatter(&logrus.TextFormatter{ + ForceColors: true, + }) + org1 := &pb.RecursiveResource{Name: "organizations/1", + DisplayName: "ACME Inc.", + Type: "ResourceGroup", + Parent: "", + Tags: map[string]string{ + "111111111111/environment": "111111111111/environment/prod", + "111111111111/customer_data": "111111111111/customer_data/yes", + "111111111111/employee_data": "111111111111/employee_data/yes", + }, + Children: []*pb.RecursiveResource{ + { + Name: "folders/dev-folder", + DisplayName: "Dev", + Type: "ResourceGroup", + Parent: "organizations/1", + Labels: map[string]string{ + "111111111111/environment": "111111111111/environment/dev", + "111111111111/customer_data": "111111111111/customer_data/no", + "111111111111/employee_data": "111111111111/employee_data/no", + }, + }, + }, + } + folder1 := &pb.RecursiveResource{ + Name: "folders/dev-folder", + DisplayName: "Dev", + Type: "ResourceGroup", + Parent: "organizations/1", + Tags: map[string]string{ + "111111111111/environment": "111111111111/environment/dev", + "111111111111/customer_data": "111111111111/customer_data/no", + "111111111111/employee_data": "111111111111/employee_data/no", + }, + } + folder2 := &pb.RecursiveResource{ + Name: "folders/prod-folder", + DisplayName: "111111111111/environment/prod", + Type: "ResourceGroup", + Parent: "organizations/1", + Tags: map[string]string{ + "111111111111/environment": "111111111111/environment/prod", + "111111111111/customer_data": "111111111111/customer_data/yes", + "111111111111/employee_data": "111111111111/employee_data/yes", + }, + } + prj0 := &pb.RecursiveResource{ + Name: "projects/project-0", + DisplayName: "Project 0", + Type: "ResourceGroup", + Parent: "folders/dev-folder", + Tags: map[string]string{ + "111111111111/customer_data": "111111111111/customer_data/no", + }, + } + prj1 := &pb.RecursiveResource{ + Name: "projects/project-1", + DisplayName: "Project 1", + Type: "ResourceGroup", + Parent: "folders/prod-folder", + Labels: map[string]string{ + "contact1": "alice@example.com", + "contact2": "bob@example.com", + }, + } + prj2 := &pb.RecursiveResource{ + Name: "projects/project-2", + DisplayName: "Project 2", + Type: "ResourceGroup", + Parent: "folders/dev-folder", + Tags: map[string]string{ + "111111111111/customer_data": "111111111111/customer_data/yes", + }, + } + prj3 := &pb.RecursiveResource{ + Name: "projects/project-3", + DisplayName: "Project 3", + Type: "ResourceGroup", + Parent: "folders/dev-folder", + Tags: map[string]string{ + "111111111111/employee_data": "111111111111/employee_data/yes", + }, + } + // A project that is a direct child of the organization, with no default labels + prj4 := &pb.RecursiveResource{ + Name: "projects/project-4", + DisplayName: "Project 4", + Type: "ResourceGroup", + Parent: "organizations/1", + } + + rgHierarchy := map[string]*pb.RecursiveResource{ + "": org1, + "organizations/1": getRecursiveResource(org1, []*pb.RecursiveResource{prj4}), + "folders/dev-folder": getRecursiveResource(folder1, []*pb.RecursiveResource{prj0, prj2, prj3}), + "folders/prod-folder": getRecursiveResource(folder2, []*pb.RecursiveResource{prj1}), + "projects/project-0": prj0, + "projects/project-1": prj1, + "projects/project-2": prj2, + "projects/project-3": prj3, + "projects/project-4": prj4, + } + + testForImpact(t, rgHierarchy, "projects/project-0", pb.Impact_IMPACT_LOW) + testForImpact(t, rgHierarchy, "projects/project-1", constants.ImpactCustomerData) + testForImpact(t, rgHierarchy, "projects/project-2", pb.Impact_IMPACT_HIGH) + testForImpact(t, rgHierarchy, "projects/project-3", constants.ImpactEmployeeData) + testForImpact(t, rgHierarchy, "projects/project-4", pb.Impact_IMPACT_HIGH) +} + +func testForImpact(t *testing.T, rgHierarchy map[string]*pb.RecursiveResource, rgName string, want pb.Impact) { + t.Helper() + got, _ := risk.GetImpact(risk.TagConfig{ + ImpactMap: impactMap, + Environment: "111111111111/environment", + EmployeeData: "111111111111/employee_data", + CustomerData: "111111111111/customer_data", + }, rgHierarchy, rgName) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf(`unexpected diff for %s (-want +got): %s`, rgName, diff) + } +} + // TODO fix flaky test. // func TestCheckRulesHandlesCheckCancellation(t *testing.T) { // rules := []model.Rule{ diff --git a/src/go.mod b/src/go.mod index fef8803..269c440 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,90 +1,139 @@ module github.com/nianticlabs/modron/src -go 1.21 +go 1.23.2 -replace github.com/nianticlabs/modron/src/pb => ./proto/ - -require github.com/improbable-eng/grpc-web v0.15.0 +replace github.com/nianticlabs/modron/src/proto/generated => ./proto/generated require ( - github.com/golang/glog v1.1.2 - github.com/google/go-cmp v0.5.9 - github.com/google/uuid v1.3.1 + cloud.google.com/go/longrunning v0.6.2 + github.com/alexflint/go-arg v1.5.1 + github.com/gogo/protobuf v1.3.2 + github.com/google/go-cmp v0.6.0 + github.com/google/uuid v1.6.0 + github.com/h2non/gock v1.2.0 + github.com/improbable-eng/grpc-web v0.15.0 github.com/lib/pq v1.10.9 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/net v0.14.0 - golang.org/x/oauth2 v0.11.0 - google.golang.org/api v0.138.0 - google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d - google.golang.org/grpc v1.57.0 - google.golang.org/protobuf v1.31.0 - k8s.io/client-go v0.28.1 - modernc.org/sqlite v1.25.0 - github.com/nianticlabs/modron/src/pb v0.0.0-00010101000000-000000000000 + github.com/sirupsen/logrus v1.9.3 + github.com/testcontainers/testcontainers-go v0.32.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.32.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.56.0 + go.opentelemetry.io/otel v1.31.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 + go.opentelemetry.io/otel/metric v1.31.0 + go.opentelemetry.io/otel/sdk v1.31.0 + go.opentelemetry.io/otel/sdk/metric v1.31.0 + go.opentelemetry.io/otel/trace v1.31.0 + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c + golang.org/x/net v0.30.0 + golang.org/x/oauth2 v0.23.0 + golang.org/x/sync v0.8.0 + golang.org/x/time v0.7.0 + google.golang.org/api v0.203.0 + google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 + google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.35.1 + gorm.io/driver/postgres v1.5.9 + gorm.io/driver/sqlite v1.5.6 + gorm.io/gorm v1.25.12 + gorm.io/plugin/opentelemetry v0.1.8 + k8s.io/api v0.31.2 + k8s.io/apimachinery v0.31.2 + k8s.io/client-go v0.31.2 + github.com/nianticlabs/modron/src/proto/generated v0.0.0-00010101000000-000000000000 ) require ( - cloud.google.com/go/compute v1.23.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/gin-gonic/gin v1.8.1 // indirect - github.com/go-logr/logr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/gogo/protobuf v1.3.2 // indirect + cloud.google.com/go/auth v0.10.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect + cloud.google.com/go/compute/metadata v0.5.2 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/hcsshim v0.12.5 // indirect + github.com/alexflint/go-scalar v1.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/containerd/containerd v1.7.20 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/desertbit/timer v1.0.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.1.2+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rs/cors v1.9.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.28.1 // indirect - k8s.io/apimachinery v0.28.1 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230901164831-6c774f458599 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect - lukechampine.com/uint128 v1.3.0 // indirect - modernc.org/cc/v3 v3.41.0 // indirect - modernc.org/ccgo/v3 v3.16.15 // indirect - modernc.org/libc v1.24.1 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.1 // indirect - modernc.org/opt v0.1.3 // indirect - modernc.org/strutil v1.2.0 // indirect - modernc.org/token v1.1.0 // indirect - nhooyr.io/websocket v1.8.7 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241009091222-67ed5848f094 // indirect + k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect + nhooyr.io/websocket v1.8.17 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/src/go.sum b/src/go.sum new file mode 100644 index 0000000..1c4f142 --- /dev/null +++ b/src/go.sum @@ -0,0 +1,854 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/auth v0.9.8 h1:+CSJ0Gw9iVeSENVCKJoLHhdUykDgXSc4Qn+gu2BRtR8= +cloud.google.com/go/auth v0.9.8/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth v0.10.0 h1:tWlkvFAh+wwTOzXIjrwM64karR1iTBZ/GRr0S/DULYo= +cloud.google.com/go/auth v0.10.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= +cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs= +cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= +cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0= +github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= +github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= +github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= +github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ= +github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/desertbit/timer v1.0.1 h1:yRpYNn5Vaaj6QXecdLMPMJsW81JLiI1eokUft5nBmeo= +github.com/desertbit/timer v1.0.1/go.mod h1:htRrYeY5V/t4iu1xCJ5XsQvp4xve8QulXXctAzxqcwE= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= +github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= +github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= +github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= +github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= +github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= +github.com/testcontainers/testcontainers-go/modules/postgres v0.32.0 h1:ZE4dTdswj3P0j71nL+pL0m2e5HTXJwPoIFr+DDgdPaU= +github.com/testcontainers/testcontainers-go/modules/postgres v0.32.0/go.mod h1:njrNuyuoF2fjhVk6TG/R3Oeu82YwfYkbf5WVTyBXhV4= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0 h1:GotCpbh7YkCHdFs+hYMdvAEyGsBZifFognqrOnBwyJM= +go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0/go.mod h1:6b0AS55EEPj7qP44khqF5dqTUq+RkakDMShFaW1EcA4= +go.opentelemetry.io/contrib/instrumentation/runtime v0.56.0 h1:s7wHG+t8bEoH7ibWk1nk682h7EoWLJ5/8j+TSO3bX/o= +go.opentelemetry.io/contrib/instrumentation/runtime v0.56.0/go.mod h1:Q8Hsv3d9DwryfIl+ebj4mY81IYVRSPy4QfxroVZwqLo= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 h1:WypxHH02KX2poqqbaadmkMYalGyy/vil4HE4PM4nRJc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0/go.mod h1:U79SV99vtvGSEBeeHnpgGJfTsnsdkWLpPN/CcHAzBSI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792jZO1bo4BXkM= +go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= +google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= +google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= +google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= +google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= +google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw= +google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= +gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= +gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/plugin/opentelemetry v0.1.6 h1:+qFdvyBoaB6i9mJsToAUyAwO40WFAH2GHBzIEb9eSSg= +gorm.io/plugin/opentelemetry v0.1.6/go.mod h1:TYGUagk7h8WwuCsDDznEzznY31PP3+NRpfh6FH7Yqfs= +gorm.io/plugin/opentelemetry v0.1.8 h1:uX3deb3w71mufbx8iY9buiGh+4HJjhItRNisZIy1fDY= +gorm.io/plugin/opentelemetry v0.1.8/go.mod h1:TYGUagk7h8WwuCsDDznEzznY31PP3+NRpfh6FH7Yqfs= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= +k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= +k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= +k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo= +k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= +k8s.io/kube-openapi v0.0.0-20241009091222-67ed5848f094 h1:MErs8YA0abvOqJ8gIupA1Tz6PKXYUw34XsGlA7uSL1k= +k8s.io/kube-openapi v0.0.0-20241009091222-67ed5848f094/go.mod h1:7ioBJr1A6igWjsR2fxq2EZ0mlMwYLejazSIc2bzMp2U= +k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 h1:b2FmK8YH+QEwq/Sy2uAEhmqL5nPfGYbJOcaqjeYYZoA= +k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y= +nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/src/log.go b/src/log.go new file mode 100644 index 0000000..e24bf22 --- /dev/null +++ b/src/log.go @@ -0,0 +1,91 @@ +package main + +import ( + "encoding/json" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +type LogFormat string + +const ( + LogFormatJSON LogFormat = "json" + LogFormatText LogFormat = "text" +) + +// setLogLevel sets the logrus log level +func setLogLevel() { + switch strings.ToLower(args.LogLevel) { + case "trace": + log.SetLevel(logrus.TraceLevel) + case "debug": + log.SetLevel(logrus.DebugLevel) + case "info": + log.SetLevel(logrus.InfoLevel) + case "warning": + log.SetLevel(logrus.WarnLevel) + case "error": + log.SetLevel(logrus.ErrorLevel) + } +} + +type gcpFormatter struct{} + +func (g gcpFormatter) Format(entry *logrus.Entry) ([]byte, error) { + output := map[string]any{} + output["severity"] = toGcpSeverity(entry.Level) + output["message"] = entry.Message + output["timestamp"] = entry.Time.Format(time.RFC3339Nano) + if len(entry.Data) > 0 { + output["labels"] = entry.Data + } + + b, err := json.Marshal(output) + if err != nil { + return nil, err + } + b = append(b, '\n') + return b, nil +} + +func toGcpSeverity(level logrus.Level) string { + switch level { + case logrus.TraceLevel, logrus.DebugLevel: + return "DEBUG" + case logrus.InfoLevel: + return "INFO" + case logrus.WarnLevel: + return "WARNING" + case logrus.ErrorLevel: + return "ERROR" + case logrus.FatalLevel, logrus.PanicLevel: + return "CRITICAL" + default: + return "DEFAULT" + } +} + +var gcpFormat = &gcpFormatter{} +var textFormatter = &logrus.TextFormatter{} + +// setLogFormat sets the format of the logs +func setLogFormat() { + var formatter logrus.Formatter + invalidFormatter := false + switch args.LogFormat { + case LogFormatJSON: + formatter = gcpFormat + case LogFormatText: + formatter = textFormatter + default: + formatter = textFormatter + invalidFormatter = true + } + + logrus.SetFormatter(formatter) + if invalidFormatter { + log.Errorf("invalid log format, using %s", LogFormatText) + } +} diff --git a/src/lognotifier/lognotifier.go b/src/lognotifier/lognotifier.go index b238a93..3e36389 100644 --- a/src/lognotifier/lognotifier.go +++ b/src/lognotifier/lognotifier.go @@ -2,46 +2,65 @@ package lognotifier import ( "context" + "errors" - "github.com/golang/glog" + "github.com/nianticlabs/modron/src/constants" "github.com/nianticlabs/modron/src/model" + + "github.com/sirupsen/logrus" ) func New() model.NotificationService { return &LogNotifier{} } +var log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "lognotifier") + type LogNotifier struct { exceptions []model.Exception } -func (ln *LogNotifier) CreateNotification(ctx context.Context, notification model.Notification) (model.Notification, error) { - glog.Infof("create notification: %+v", notification) +func (ln *LogNotifier) BatchCreateNotifications(ctx context.Context, notifications []model.Notification) ([]model.Notification, error) { + var resultNotifications []model.Notification + var errArr []error + for _, v := range notifications { + notif, err := ln.CreateNotification(ctx, v) + if err != nil { + errArr = append(errArr, err) + continue + } + resultNotifications = append(resultNotifications, notif) + } + return resultNotifications, errors.Join(errArr...) +} + +func (ln *LogNotifier) CreateNotification(_ context.Context, notification model.Notification) (model.Notification, error) { + log.Infof("create notification: %+v", notification) return notification, nil } -func (ln *LogNotifier) GetException(ctx context.Context, uuid string) (model.Exception, error) { - glog.Infof("get exception called with %q", uuid) - return model.Exception{Uuid: uuid}, nil +func (ln *LogNotifier) GetException(_ context.Context, uuid string) (model.Exception, error) { + log.Infof("get exception called with %q", uuid) + return model.Exception{UUID: uuid}, nil } -func (ln *LogNotifier) CreateException(ctx context.Context, exception model.Exception) (model.Exception, error) { - glog.Infof("create exception %+v", exception) +func (ln *LogNotifier) CreateException(_ context.Context, exception model.Exception) (model.Exception, error) { + log.Infof("create exception %+v", exception) ln.exceptions = append(ln.exceptions, exception) return exception, nil } -func (ln *LogNotifier) UpdateException(ctx context.Context, exception model.Exception) (model.Exception, error) { - glog.Infof("update exception: %+v", exception) +func (ln *LogNotifier) UpdateException(_ context.Context, exception model.Exception) (model.Exception, error) { + log.Infof("update exception: %+v", exception) return exception, nil } -func (ln *LogNotifier) DeleteException(ctx context.Context, id string) error { - glog.Infof("delete exception %q", id) +func (ln *LogNotifier) DeleteException(_ context.Context, id string) error { + log.Infof("delete exception %q", id) return nil } -func (ln *LogNotifier) ListExceptions(ctx context.Context, userEmail string, pageSize int32, pageToken string) ([]model.Exception, error) { - glog.Infof("list exceptions for user %q", userEmail) +func (ln *LogNotifier) ListExceptions(_ context.Context, userEmail string, _ int32, _ string) ([]model.Exception, error) { + log.Infof("list exceptions for user %q", userEmail) return ln.exceptions, nil } diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..76071ef --- /dev/null +++ b/src/main.go @@ -0,0 +1,277 @@ +package main + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/alexflint/go-arg" + "github.com/improbable-eng/grpc-web/go/grpcweb" + "github.com/sirupsen/logrus" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/contrib/instrumentation/runtime" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/nianticlabs/modron/src/collector" + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +var args struct { + AdditionalAdminRoles []string `arg:"--additional-admin-roles,env:ADDITIONAL_ADMIN_ROLES" help:"Comma separated list of roles that are considered administrators of a resource group"` + AdminGroups []string `arg:"--admin-groups,env:ADMIN_GROUPS" help:"Comma separated list of groups that are allowed to see all projects"` + AllowedSccCategories []string `arg:"--allowed-scc-categories,env:ALLOWED_SCC_CATEGORIES" help:"Comma separated list of SCC categories that are allowed to create observations"` + CollectAndScanInterval time.Duration `arg:"--collect-and-scan-interval,env:COLLECT_AND_SCAN_INTERVAL" help:"Interval between collecting and scanning (example: 3h)" default:"6h"` + // TODO: Collector should be a list, as we might want to support more + Collector collector.Type `arg:"--collector,env:COLLECTOR" help:"Specify which collector to use" default:"gcp"` + DbBatchSize int32 `arg:"--db-batch-size,env:DB_BATCH_SIZE" help:"Number of records to insert in a single batch" default:"32"` + DbConnectionMaxIdleTime time.Duration `arg:"--db-connection-max-idle-time,env:DB_CONNECTION_MAX_IDLE_TIME" help:"Maximum amount of time a connection may be idle" default:"30s"` + DbConnectionMaxLifetime time.Duration `arg:"--db-connection-max-lifetime,env:DB_CONNECTION_MAX_LIFETIME" help:"Maximum amount of time a connection may be reused" default:"1h"` + DbMaxConnections int `arg:"--db-max-connections,env:DB_MAX_CONNECTIONS" help:"Maximum number of connections to the database" default:"10"` + DbMaxIdleConnections int `arg:"--db-max-idle-connections,env:DB_MAX_IDLE_CONNECTIONS" help:"Maximum number of idle connections to the database" default:"10"` + DisableTelemetry bool `arg:"--disable-telemetry,env:DISABLE_TELEMETRY" help:"Disable OTEL telemetry" default:"false"` + Environment string `arg:"--environment,env:ENVIRONMENT" help:"Environment (development, production)" default:"development"` + ExcludedRules []string `arg:"--excluded-rules,env:EXCLUDED_RULES" help:"Comma separated list of rules to exclude from the scan."` + ImpactMap string `arg:"--impact-map,env:IMPACT_MAP" help:"JSON map that maps the environment name to the impact level" default:"{}"` + IsE2EGrpcTest bool `arg:"--is-e2e-grpc-test,env:IS_E2E_GRPC_TEST" help:"Is this an end-to-end gRPC test" default:"false"` + LabelToEmailRegexp string `arg:"--label-to-email-regexp,env:LABEL_TO_EMAIL_REGEXP" help:"Regular expression to extract email from labels" default:"(.*)_(.*?)_(.*?)$"` + LabelToEmailSubst string `arg:"--label-to-email-substitution,env:LABEL_TO_EMAIL_SUBSTITUTION" help:"Substitution to apply to the email extracted from labels" default:"$1@$2.$3"` + ListenAddr string `arg:"--listen-addr,env:LISTEN_ADDR" help:"Address to listen on" default:"127.0.0.1"` + LogFormat LogFormat `arg:"--log-format,env:LOG_FORMAT" help:"Log format (json,text)" default:"json"` + LogLevel string `arg:"--log-level,env:LOG_LEVEL" help:"Log level (trace,debug,info,warning,error)" default:"info"` + LogAllSQLQueries bool `arg:"--log-all-sql-queries,env:LOG_ALL_SQL_QUERIES" help:"Log all SQL queries" default:"false"` + NotificationInterval time.Duration `arg:"--notification-interval,env:NOTIFICATION_INTERVAL" help:"Interval between notifications (minimum: 24h)" default:"24h"` + NotificationService string `arg:"--notification-service,env:NOTIFICATION_SERVICE" help:"Address of the notification service"` + NotificationServiceClientID string `arg:"--notification-service-client-id,env:NOTIFICATION_SERVICE_CLIENT_ID" help:"Client ID for the notification service"` + OrgID string `arg:"--org-id,env:ORG_ID,required" help:"Organization ID"` + OrgSuffix string `arg:"--org-suffix,env:ORG_SUFFIX,required" help:"Organization suffix (e.g: @example.com)"` + PersistentCache bool `arg:"--persistent-cache,env:PERSISTENT_CACHE" help:"Use a persistent ACL cache that will be stored on the temporary directory" default:"false"` + PersistentCacheTimeout time.Duration `arg:"--persistent-cache-timeout,env:PERSISTENT_CACHE_TIMEOUT" help:"Amount of time to keep the ACLs on the filesystem before we fetch them again" default:"5m"` + Port int32 `arg:"--port,env:PORT" help:"Port to listen on" default:"8080"` + RuleConfigs string `arg:"--rule-configs,env:RULE_CONFIGS" help:"A map of rule names to their JSON configuration" default:"{}"` + RunAutomatedScans bool `arg:"--run-automated-scans,env:RUN_AUTOMATED_SCANS" help:"Run automated scans" default:"true"` + SelfURL string `arg:"--self-url,env:SELF_URL" help:"URL of Modron - to be used when sending notifications" default:"https://modron"` + SkipIAP bool `arg:"--skip-iap,env:SKIP_IAP" help:"Skip IAP authentication" default:"false"` + SQLBackendDriver string `arg:"--sql-backend-driver,env:SQL_BACKEND_DRIVER" help:"SQL backend driver (postgres)" default:"postgres"` + SQLConnectionString string `arg:"--sql-connection-string,env:SQL_CONNECT_STRING" help:"SQL connection string" default:""` // TODO: Find where SQL_CONNECT_STRING is used and change it to SQL_CONNECTION_STRING for the future + Storage string `arg:"--storage,env:STORAGE" help:"Storage type (memory,sql)" default:"sql"` + TagCustomerData string `arg:"--tag-customer-data,env:TAG_CUSTOMER_DATA,required" help:"Tag to use to define the customer data (e.g: 111111111/customer_data)"` + TagEmployeeData string `arg:"--tag-employee-data,env:TAG_EMPLOYEE_DATA,required" help:"Tag to use to define the employee data (e.g: 111111111/employee_data)"` + TagEnvironment string `arg:"--tag-environment,env:TAG_ENVIRONMENT,required" help:"Tag to use to define the environment (e.g: 111111111/environment)"` +} + +var ( + start = time.Now() + log = logrus.StandardLogger() + tracer trace.Tracer +) + +const ( + ExitCodeOK = iota + ExitCodeInvalidArgs + ExitCodeFailedToListen + ExitCodeFailedToCreateServer + ExitCodeFailedToServeGRPC + ExitCodeFailedToServeHTTP +) + +const ( + HTTPHeaderReadTimeout = 5 * time.Second +) + +func main() { + arg.MustParse(&args) + setLogLevel() + setLogFormat() + + log.Debugf("validating arguments..") + if err := validateArgs(); err != nil { + log.Error(err) + os.Exit(ExitCodeInvalidArgs) + } + log.Debugf("starting") + doMain() +} + +func doMain() { + mainCtx, cancel := context.WithCancel(context.Background()) + tp, mp := initTracer(mainCtx) + defer func() { + _ = tp.Shutdown(mainCtx) + _ = mp.Shutdown(mainCtx) + }() + + defer log.Tracef("net.Listen on port %d", args.Port) + // Handle SIGINT (for Ctrl+C) and SIGTERM (for Cloud Run) signals + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + go func() { + sig := <-c + log.Infof("received signal: %+v", sig) + cancel() + }() + + go func() { + ctx, span := tracer.Start(mainCtx, "setup-grpc") + log.Tracef("Setting up GRPC") + // Use insecure credentials since communication is encrypted and authenticated via + // HTTPS end-to-end (i.e., from client to Cloud Run ingress). + var opts = []grpc.ServerOption{ + grpc.Creds(insecure.NewCredentials()), + } + opts = append( + opts, + grpc.StatsHandler(otelgrpc.NewServerHandler()), + ) + grpcServer := grpc.NewServer(opts...) // nosemgrep: go.grpc.security.grpc-server-insecure-connection.grpc-server-insecure-connection + log.Tracef("Creating newServer") + srv, err := newServer(ctx) + if err != nil { + log.Errorf("server creation: %v", err) + os.Exit(ExitCodeFailedToCreateServer) + } + log.Tracef("Registering Modron Service Server") + pb.RegisterModronServiceServer(grpcServer, srv) + log.Tracef("Registering Modron Notification Service Server") + pb.RegisterNotificationServiceServer(grpcServer, srv) + log.Infof("server starting on port %d", args.Port) + if args.RunAutomatedScans { + log.Tracef("Starting scheduled runner") + go srv.ScheduledRunner(mainCtx) + } + span.End() + if args.IsE2EGrpcTest { + log.Warnf("E2E gRPC test mode enabled") + // TODO: Unfortunately we need this as the GRPC-Web is different from the GRPC protocol. + // This is used only in the integration test that doesn't have a GRPC-Web client. + // We should look into https://github.com/improbable-eng/grpc-web and check how we can implement a golang GRPC-web client. + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", args.Port)) + if err != nil { + log.Errorf("failed to listen: %v", err) + os.Exit(ExitCodeFailedToListen) + } + if err := grpcServer.Serve(lis); err != nil { + log.Errorf("error while listening: %v", err) + os.Exit(ExitCodeFailedToServeGRPC) + } + } else { + log.Infof("starting gRPC-Web server") + grpcWebServer := grpcweb.WrapServer(grpcServer, withCors()...) + log.Debugf("time until start: %v", time.Since(start)) + httpServer := &http.Server{ + Addr: fmt.Sprintf("%s:%d", args.ListenAddr, args.Port), + ReadHeaderTimeout: HTTPHeaderReadTimeout, + Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + switch req.URL.Path { + case "/healthz": + w.WriteHeader(http.StatusOK) + default: + otelhttp.NewMiddleware("grpc-web")(grpcWebServer).ServeHTTP(w, req) + } + }), + } + if err := httpServer.ListenAndServe(); err != nil { + log.Errorf("error while listening: %v", err) + os.Exit(ExitCodeFailedToServeHTTP) + } + } + }() + + <-mainCtx.Done() + log.Infof("server stopped") +} + +func newTraceProvider(exp sdktrace.SpanExporter) *sdktrace.TracerProvider { + r, err := resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName("Modron"), + ), + ) + + if err != nil { + panic(err) + } + + return sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithResource(r), + sdktrace.WithSpanProcessor(sdktrace.NewBatchSpanProcessor(exp)), + ) +} + +func newTraceExporter(ctx context.Context) (*otlptrace.Exporter, error) { + return otlptrace.New(ctx, + otlptracegrpc.NewClient(otlptracegrpc.WithInsecure()), + ) +} + +func newMetricExporter(ctx context.Context) (sdkmetric.Exporter, error) { + return otlpmetricgrpc.New(ctx, + otlpmetricgrpc.WithInsecure(), + ) +} + +func initTracer(ctx context.Context) (*sdktrace.TracerProvider, *sdkmetric.MeterProvider) { + if args.DisableTelemetry { + log.Warnf("telemetry is disabled!") + tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.NeverSample())) + tracer = tp.Tracer("github.com/nianticlabs/modron") + return tp, sdkmetric.NewMeterProvider() + } + traceExp, err := newTraceExporter(ctx) + if err != nil { + log.Fatalf("failed to initialize exporter: %v", err) + } + metricExp, err := newMetricExporter(ctx) + if err != nil { + log.Fatalf("failed to initialize metric exporter: %v", err) + } + // Create a new tracer provider with a batch span processor and the given exporter. + tp := newTraceProvider(traceExp) + mp := newMeterProvider(ctx, metricExp) + + if err := runtime.Start(); err != nil { + log.Fatalf("failed to start runtime metrics: %v", err) + } + + otel.SetTracerProvider(tp) + otel.SetMeterProvider(mp) + otel.SetTextMapPropagator(propagation.TraceContext{}) + + tracer = tp.Tracer("github.com/nianticlabs/modron") + return tp, mp +} + +func newMeterProvider(ctx context.Context, exp sdkmetric.Exporter) *sdkmetric.MeterProvider { + rsrc, err := resource.New(ctx) + if err != nil { + log.Fatalf("failed to create resource: %v", err) + return nil + } + + return sdkmetric.NewMeterProvider( + sdkmetric.WithResource(rsrc), + sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp, + sdkmetric.WithInterval(time.Second), + sdkmetric.WithProducer(runtime.NewProducer()), + )), + ) +} diff --git a/src/metric/keys.go b/src/metric/keys.go new file mode 100644 index 0000000..6d82520 --- /dev/null +++ b/src/metric/keys.go @@ -0,0 +1,13 @@ +package metric + +const ( + KeyCategory = "category" + KeyCount = "count" + KeyMethod = "method" + KeyOffendingPackage = "offending_package" + KeyPath = "path" + KeyRecipient = "recipient" + KeyRule = "rule" + KeySeverity = "severity" + KeyStatus = "status" +) diff --git a/src/metric/status.go b/src/metric/status.go new file mode 100644 index 0000000..bedbb11 --- /dev/null +++ b/src/metric/status.go @@ -0,0 +1,10 @@ +package metric + +type Status = string + +const ( + StatusCancelled Status = "cancelled" + StatusCompleted Status = "completed" + StatusError Status = "error" + StatusSuccess Status = "success" +) diff --git a/src/model/acl.go b/src/model/acl.go index 6ea63f2..c6950a5 100644 --- a/src/model/acl.go +++ b/src/model/acl.go @@ -2,8 +2,13 @@ package model import "golang.org/x/net/context" +// ACLCache is a map of users to a map of resource names: {"user@example.com": {"projects/xyz": {}}} +// the reason why the last part is a struct{} is that we don't care about the value, we only care about the key +// and a struct{} is the smallest value we can use. +type ACLCache map[string]map[string]struct{} + type Checker interface { - GetAcl() map[string]map[string]struct{} + GetACL() ACLCache GetValidatedUser(ctx context.Context) (string, error) ListResourceGroupNamesOwned(ctx context.Context) (map[string]struct{}, error) } diff --git a/src/model/collector.go b/src/model/collector.go index 0b3a0fb..1ee795f 100644 --- a/src/model/collector.go +++ b/src/model/collector.go @@ -2,15 +2,18 @@ package model import ( "golang.org/x/net/context" - "github.com/nianticlabs/modron/src/pb" + + pb "github.com/nianticlabs/modron/src/proto/generated" ) type Collector interface { - CollectAndStoreAllResourceGroupResources(ctx context.Context, collectId string, resourceGroupNames []string) []error - CollectAndStoreResources(ctx context.Context, collectId string, resourecGroupId string) []error - GetResourceGroup(ctx context.Context, collectId string, resourecGroupId string) (*pb.Resource, error) - ListResourceGroups(ctx context.Context, name string) ([]*pb.Resource, error) + CollectAndStoreAll(ctx context.Context, collectID string, resourceGroupNames []string, preCollectedRgs []*pb.Resource) error + + GetResourceGroupWithIamPolicy(ctx context.Context, collectID string, rgName string) (*pb.Resource, error) + ListResourceGroups(ctx context.Context, rgNames []string) ([]*pb.Resource, error) + ListResourceGroupsWithIamPolicies(ctx context.Context, rgNames []string) ([]*pb.Resource, error) ListResourceGroupNames(ctx context.Context) ([]string, error) - ListResourceGroupAdmins(ctx context.Context) (map[string]map[string]struct{}, error) - ListResourceGroupResources(ctx context.Context, collectId string, resourecGroup *pb.Resource) ([]*pb.Resource, []error) + ListResourceGroupAdmins(ctx context.Context) (ACLCache, error) + ListResourceGroupResources(ctx context.Context, collectID string, rgName string) ([]*pb.Resource, []error) + ListResourceGroupObservations(ctx context.Context, collectID string, rgName string) ([]*pb.Observation, []error) } diff --git a/src/model/engine.go b/src/model/engine.go new file mode 100644 index 0000000..9fbca55 --- /dev/null +++ b/src/model/engine.go @@ -0,0 +1,20 @@ +package model + +import ( + "context" + "encoding/json" + + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" +) + +type Engine interface { + GetChildren(ctx context.Context, parent string) ([]*pb.Resource, error) + GetResource(ctx context.Context, resourceName string) (*pb.Resource, error) + GetHierarchy(ctx context.Context, collectionID string) (map[string]*pb.RecursiveResource, error) + GetTagConfig() risk.TagConfig + + CheckRules(ctx context.Context, scanID string, collectID string, groups []string, preCollectedRGs []*pb.Resource) ([]*pb.Observation, []error) + GetRuleConfig(ctx context.Context, ruleName string) (json.RawMessage, error) + GetRules() []Rule +} diff --git a/src/model/exception.go b/src/model/exception.go index befbb1e..a415c55 100644 --- a/src/model/exception.go +++ b/src/model/exception.go @@ -5,11 +5,12 @@ import ( "golang.org/x/net/context" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/pb" + + pb "github.com/nianticlabs/modron/src/proto/generated" ) type Exception struct { - Uuid string `json:"uuid,omitempty"` + UUID string `json:"uuid,omitempty"` SourceSystem string `json:"sourceSystem,omitempty"` UserEmail string `json:"userEmail,omitempty"` NotificationName string `json:"notification_name,omitempty"` @@ -19,7 +20,7 @@ type Exception struct { } type Notification struct { - Uuid string `json:"uuid,omitempty"` + UUID string `json:"uuid,omitempty"` SourceSystem string `json:"sourceSystem,omitempty"` Name string `json:"name,omitempty"` Recipient string `json:"recipient,omitempty"` @@ -31,7 +32,7 @@ type Notification struct { func (e *Exception) ToProto() *pb.NotificationException { return &pb.NotificationException{ - Uuid: e.Uuid, + Uuid: e.UUID, SourceSystem: e.SourceSystem, UserEmail: e.UserEmail, NotificationName: e.NotificationName, @@ -43,7 +44,7 @@ func (e *Exception) ToProto() *pb.NotificationException { func ExceptionFromProto(p *pb.NotificationException) Exception { return Exception{ - Uuid: p.Uuid, + UUID: p.Uuid, SourceSystem: p.SourceSystem, UserEmail: p.UserEmail, NotificationName: p.NotificationName, @@ -54,6 +55,7 @@ func ExceptionFromProto(p *pb.NotificationException) Exception { } type NotificationService interface { + BatchCreateNotifications(ctx context.Context, notifications []Notification) ([]Notification, error) CreateNotification(ctx context.Context, notification Notification) (Notification, error) GetException(ctx context.Context, uuid string) (Exception, error) diff --git a/src/model/model.go b/src/model/model.go index ab68a99..47f5114 100644 --- a/src/model/model.go +++ b/src/model/model.go @@ -3,30 +3,38 @@ package model import ( "context" - "github.com/nianticlabs/modron/src/pb" + "google.golang.org/protobuf/proto" + + pb "github.com/nianticlabs/modron/src/proto/generated" ) -// Base interface to be implemented by rules. A `Rule` takes a resource, checks -// its observed values against an expected reference value, and creates an -// observation if it identifies a discrepancy, which may include a remediation for -// resolving it. +// Rule is the interface that is implemented by the rules. +// A `Rule` takes a resource, checks its observed values against an expected reference value, +// and creates an observation if it identifies a discrepancy, which may include a remediation for resolving it. type Rule interface { - // Performs a rule-dependent check on a resource and, in case it detects an anomaly, + // Check Performs a rule-dependent check on a resource and, in case it detects an anomaly, // returns a list of observations. The method MUST return nil in case either it did // not create any observations or detect any errors. - Check(ctx context.Context, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) - // Returns the associated `RuleInfo` data. + Check(ctx context.Context, engine Engine, rsrc *pb.Resource) (obs []*pb.Observation, errs []error) + // Info returns the associated RuleInfo data. Info() *RuleInfo } type RuleInfo struct { - // Human readable name of the rule, e.g., "EXPOSED_INFRASTRUCTURE_WITH_ADMIN_PRIVILEGES". + // Human-readable name of the rule, e.g., "EXPOSED_INFRASTRUCTURE_WITH_ADMIN_PRIVILEGES". Name string // Types of resource this rule accepts as an input to `Check`. This helps the rule engine // fetch in advance all the resources the rule needs to perform check(s) against. - AcceptedResourceTypes []string + AcceptedResourceTypes []proto.Message } type RuleEngine interface { - CheckRules(ctx context.Context, scanId string, resourceGroups []string) (obs []*pb.Observation, errs []error) + CheckRules( + ctx context.Context, + scanID string, + collectID string, + resourceGroups []string, + preCollectedRgs []*pb.Resource, + ) (obs []*pb.Observation, errs []error) + GetRules() []Rule } diff --git a/src/model/stateManager.go b/src/model/stateManager.go index dc92be0..768149b 100644 --- a/src/model/stateManager.go +++ b/src/model/stateManager.go @@ -1,16 +1,16 @@ package model import ( - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" ) type StateManager interface { - GetCollectState(collectId string) pb.RequestStatus - GetScanState(scanId string) pb.RequestStatus + GetCollectState(collectID string) pb.RequestStatus + GetScanState(scanID string) pb.RequestStatus - AddScan(scanId string, resourceGroupNames []string) []string - EndScan(scanId string, resourceGroupNames []string) + AddScan(scanID string, resourceGroupNames []string) []string + EndScan(scanID string, resourceGroupNames []string) - AddCollect(collectId string, resourceGroupNames []string) []string - EndCollect(collectId string, resourceGroupNames []string) + AddCollect(collectID string, resourceGroupNames []string) []string + EndCollect(collectID string, resourceGroupNames []string) } diff --git a/src/model/storage.go b/src/model/storage.go index 89145fd..9f17604 100644 --- a/src/model/storage.go +++ b/src/model/storage.go @@ -5,7 +5,7 @@ import ( "context" "time" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" ) type StorageFilter struct { @@ -25,8 +25,9 @@ type Storage interface { ListResources(ctx context.Context, filter StorageFilter) ([]*pb.Resource, error) BatchCreateObservations(ctx context.Context, observations []*pb.Observation) ([]*pb.Observation, error) ListObservations(ctx context.Context, filter StorageFilter) ([]*pb.Observation, error) + GetChildrenOfResource(ctx context.Context, collectID string, parentResourceName string, resourceType *string) (map[string]*pb.RecursiveResource, error) - AddOperationLog(ctx context.Context, ops []Operation) error + AddOperationLog(ctx context.Context, ops []*pb.Operation) error FlushOpsLog(ctx context.Context) error PurgeIncompleteOperations(ctx context.Context) error } diff --git a/src/nagatha/convert.go b/src/nagatha/convert.go index bc9f415..3c097cb 100644 --- a/src/nagatha/convert.go +++ b/src/nagatha/convert.go @@ -1,13 +1,15 @@ package nagatha import ( - "google.golang.org/protobuf/types/known/timestamppb" "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/proto/generated/nagatha" + + "google.golang.org/protobuf/types/known/timestamppb" ) -func exceptionModelFromNagathaProto(ex *Exception) model.Exception { +func exceptionModelFromNagathaProto(ex *nagatha.Exception) model.Exception { return model.Exception{ - Uuid: ex.Uuid, + UUID: ex.Uuid, SourceSystem: ex.SourceSystem, UserEmail: ex.UserEmail, NotificationName: ex.NotificationName, @@ -17,9 +19,9 @@ func exceptionModelFromNagathaProto(ex *Exception) model.Exception { } } -func exceptionNagathaProtoFromModel(ex model.Exception) *Exception { - return &Exception{ - Uuid: ex.Uuid, +func exceptionNagathaProtoFromModel(ex model.Exception) *nagatha.Exception { + return &nagatha.Exception{ + Uuid: ex.UUID, SourceSystem: ex.SourceSystem, UserEmail: ex.UserEmail, NotificationName: ex.NotificationName, diff --git a/src/nagatha/nagatha.go b/src/nagatha/nagatha.go index 798d8dd..ac8febb 100644 --- a/src/nagatha/nagatha.go +++ b/src/nagatha/nagatha.go @@ -2,47 +2,129 @@ package nagatha import ( "context" + "errors" "fmt" + "time" - "google.golang.org/protobuf/types/known/durationpb" + "github.com/nianticlabs/modron/src/constants" + modronmetric "github.com/nianticlabs/modron/src/metric" "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/proto/generated/nagatha" + + "github.com/sirupsen/logrus" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + + "golang.org/x/oauth2" + "google.golang.org/protobuf/types/known/durationpb" ) +var meter = otel.Meter("github.com/nianticlabs/modron/src/nagatha") +var tracer = otel.Tracer("github.com/nianticlabs/modron/src/nagatha") +var log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "nagatha") + const ( sourceSystem = "modron" - clientID = "143415353591-bsmr7ii98a2493kts699289n2ommqi07.apps.googleusercontent.com" + batchSize = 1000 ) -func New(ctx context.Context, addr string) (model.NotificationService, error) { - c, err := NewNagathaClient(addr) +func New(addr, modronURL string, tokenSource oauth2.TokenSource) (model.NotificationService, error) { + c, err := newNagathaClient(addr, tokenSource) if err != nil { return nil, err } - return &Service{ - client: c, - }, nil + s := &Service{ + client: c, + modronURL: modronURL, + } + err = s.initMetrics() + return s, err } type Service struct { - model.NotificationService - client *NagathaClient + client *Client + modronURL string + metrics metrics +} + +type metrics struct { + ClientReqDuration metric.Float64Histogram + NotificationDuration metric.Float64Histogram } func (svc *Service) CreateNotification(ctx context.Context, notification model.Notification) (model.Notification, error) { + ctx, span := tracer.Start(ctx, "CreateNotification") + defer span.End() + start := time.Now() if notification.Name == "" { return model.Notification{}, fmt.Errorf("name can't be empty") } - err := svc.client.CreateNotification(ctx, &Notification{ + notif, err := svc.client.CreateNotification(ctx, &nagatha.Notification{ SourceSystem: sourceSystem, Name: notification.Name, - UserEmail: notification.Recipient, + Recipient: notification.Recipient, Content: notification.Content, Interval: durationpb.New(notification.Interval), }) + status := modronmetric.StatusSuccess if err != nil { - return model.Notification{}, err + status = modronmetric.StatusError + } + svc.metrics.NotificationDuration. + Record(ctx, time.Since(start).Seconds(), + metric.WithAttributes( + attribute.String(modronmetric.KeyStatus, status), + attribute.String(modronmetric.KeyRecipient, notification.Recipient), + ), + ) + return notif, err +} + +func (svc *Service) BatchCreateNotifications(ctx context.Context, notifications []model.Notification) ([]model.Notification, error) { + ctx, span := tracer.Start(ctx, "BatchCreateNotifications") + defer span.End() + start := time.Now() + var resultNotifications []model.Notification + var errArr []error + status := "success" + for i := 0; i < len(notifications); i += batchSize { + end := i + batchSize + if end > len(notifications) { + end = len(notifications) + } + notificationsProto := make([]*nagatha.Notification, 0, end-i) + for _, n := range notifications[i:end] { + if n.Recipient == "" { + log.Warnf("recipient empty for notification %s", n.Name) + continue + } + notificationsProto = append(notificationsProto, &nagatha.Notification{ + SourceSystem: sourceSystem, + Name: n.Name, + Recipient: n.Recipient, + Content: n.Content, + Interval: durationpb.New(n.Interval), + }) + } + notif, err := svc.client.BatchCreateNotifications(ctx, notificationsProto) + if err != nil { + status = "error" + log.Warnf("error creating notifications: %v", err) + errArr = append(errArr, err) + continue + } + resultNotifications = append(resultNotifications, notif...) + log.Infof("%d notifications remaining", len(notifications)-end) } - return model.Notification{}, nil + svc.metrics.NotificationDuration. + Record(ctx, time.Since(start).Seconds(), + metric.WithAttributes( + attribute.String(modronmetric.KeyStatus, status), + attribute.Int(modronmetric.KeyCount, len(notifications)), + ), + ) + return resultNotifications, errors.Join(errArr...) } func (svc *Service) GetException(ctx context.Context, uuid string) (model.Exception, error) { @@ -76,3 +158,23 @@ func (svc *Service) ListExceptions(ctx context.Context, userEmail string, pageSi } return exceptions, nil } + +func (svc *Service) initMetrics() error { + notificationDurationHistogram, err := meter.Float64Histogram(constants.MetricsPrefix + "notifications_sent_total") + if err != nil { + return err + } + clientReqDurationHist, err := meter.Float64Histogram( + constants.MetricsPrefix+"client_requests_duration", + metric.WithDescription("Duration of client requests in seconds"), + metric.WithUnit("s"), + ) + if err != nil { + return err + } + svc.metrics = metrics{ + NotificationDuration: notificationDurationHistogram, + ClientReqDuration: clientReqDurationHist, + } + return nil +} diff --git a/src/nagatha/notification.go b/src/nagatha/notification.go new file mode 100644 index 0000000..ca666b5 --- /dev/null +++ b/src/nagatha/notification.go @@ -0,0 +1,39 @@ +package nagatha + +import ( + "time" + + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/proto/generated/nagatha" +) + +func NotificationFromObservation(contact string, interval time.Duration, obs *pb.Observation) model.Notification { + return model.Notification{ + SourceSystem: "modron", + Name: obs.Name, + Recipient: contact, + Content: formatNotificationContent(obs), + Interval: interval, + } +} + +func notificationFromProto(p *nagatha.Notification) model.Notification { + return model.Notification{ + UUID: p.Uuid, + SourceSystem: p.SourceSystem, + Name: p.Name, + Recipient: p.Recipient, + Content: p.Content, + CreatedOn: p.CreatedOn.AsTime(), + SentOn: p.SentOn.AsTime(), + Interval: p.Interval.AsDuration(), + } +} + +func formatNotificationContent(obs *pb.Observation) string { + var out string + out += obs.Remediation.Description + "\n\n" + out += obs.Remediation.Recommendation + " \n \n" + return out +} diff --git a/src/nagatha/notification_test.go b/src/nagatha/notification_test.go new file mode 100644 index 0000000..0532220 --- /dev/null +++ b/src/nagatha/notification_test.go @@ -0,0 +1,43 @@ +package nagatha_test + +import ( + "testing" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/nagatha" + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +func TestNotificationFromObservation(t *testing.T) { + pbObs := pb.Observation{ + Uid: "47fc94f3-b6e9-4ae5-b719-c8dd2157744b", + ScanUid: proto.String("2f86b47d-a386-4f4e-88a1-786f2a572ad8"), + Timestamp: timestamppb.New(time.Now()), + ResourceRef: &pb.ResourceRef{ + Uid: proto.String("79bc4bd2-a454-4837-8a09-0d769cb36d0f"), + GroupName: "projects/some-project", + }, + Name: "DATABASE_ALLOWS_UNENCRYPTED_CONNECTIONS", + Remediation: &pb.Remediation{ + Description: "Database example-psql allows for unencrypted connections.", + Recommendation: "Enable the require SSL setting in the database settings to allow only encrypted connections to example-psql.", + }, + } + + want := model.Notification{ + SourceSystem: "modron", + Name: "DATABASE_ALLOWS_UNENCRYPTED_CONNECTIONS", + Content: "Database example-psql allows for unencrypted connections.\n\nEnable the require SSL setting in the database settings to allow only encrypted connections to example-psql. \n \n", + Recipient: "test@example.com", + Interval: 24 * time.Hour, + } + got := nagatha.NotificationFromObservation("test@example.com", 24*time.Hour, &pbObs) + if diff := cmp.Diff(&got, &want); diff != "" { + t.Errorf("NotificationFromObservation() mismatch (-got +want):\n%s", diff) + } +} diff --git a/src/nagatha/proto/nagatha.proto b/src/nagatha/proto/nagatha.proto new file mode 100644 index 0000000..a189640 --- /dev/null +++ b/src/nagatha/proto/nagatha.proto @@ -0,0 +1,167 @@ +syntax = "proto3"; + +// You may want to read https://google.aip.dev/general first. + +import "google/protobuf/empty.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/longrunning/operations.proto"; + +option go_package = "./nagatha"; +package com.nianticlabs.nagatha; + +message Exception { + string uuid = 1; + string source_system = 2; + string user_email = 3; + string notification_name = 4; + string justification = 5; + google.protobuf.Timestamp created_on_time = 6; + google.protobuf.Timestamp valid_until_time = 7; +} + +message Notification { + string uuid = 1; + string source_system = 2; + string name = 3; + string recipient = 4; + string content = 5; + google.protobuf.Timestamp created_on = 6; + google.protobuf.Timestamp sent_on = 7; + google.protobuf.Duration interval = 8; +} + +message BatchCreateNotificationsRequest { + repeated Notification notifications = 1; +} + +message BatchCreateNotificationsResponse { + repeated Notification notifications = 1; +} + +service Nagatha { + rpc CreateNotification(CreateNotificationRequest) returns (Notification) { + option (google.api.http) = { + post : "/v2/notifications" + body : "notification" + }; + }; + + rpc BatchCreateNotifications(BatchCreateNotificationsRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + post : "/v2/notifications:batchCreate" + body : "*" + }; + option (google.longrunning.operation_info) = { + response_type : "BatchCreateNotificationsResponse" + metadata_type : "OperationMetadata" + }; + }; + + rpc GetException(GetExceptionRequest) returns (Exception) { + option (google.api.http) = { + get : "/v2/exceptions/{uuid}" + }; + }; + rpc CreateException(CreateExceptionRequest) returns (Exception) { + option (google.api.http) = { + post : "/v2/exceptions" + body : "exception" + }; + }; + rpc UpdateException(UpdateExceptionRequest) returns (Exception) { + option (google.api.http) = { + patch : "/v2/exceptions/{exception.uuid}" + body : "exception" + }; + }; + rpc DeleteException(DeleteExceptionRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete : "/v2/exceptions/{uuid}" + }; + }; + rpc ListExceptions(ListExceptionsRequest) returns (ListExceptionsResponse) { + option (google.api.http) = { + get : "/v2/exceptions" + }; + }; + + rpc NotifyUser(NotifyUserRequest) returns (NotifyUserResponse) { + option (google.api.http) = { + post : "/v2/notifyUser" + body : "*" + }; + }; + rpc NotifyAll(NotifyAllRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + get : "/v2/notifyAll" + }; + option (google.longrunning.operation_info) = { + response_type : "NotifyAllResponse" + metadata_type : "OperationMetadata" + }; + }; + + rpc ListOperations(google.longrunning.ListOperationsRequest) returns (google.longrunning.ListOperationsResponse) { + option (google.api.http) = { + post: "/v2/{name=operations}" + }; + option (google.api.method_signature) = "name,filter"; + } + + // Gets the latest state of a long-running operation. Clients can use this + // method to poll the operation result at intervals as recommended by the API + // service. + rpc GetOperation(google.longrunning.GetOperationRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + get: "/v2/{name=operations/**}" + }; + option (google.api.method_signature) = "name"; + } +} + +message CreateNotificationRequest { Notification notification = 1; } + +message GetExceptionRequest { string uuid = 1; } + +message CreateExceptionRequest { Exception exception = 1; } + +message UpdateExceptionRequest { + Exception exception = 1; + + google.protobuf.FieldMask update_mask = 2; +} + +message DeleteExceptionRequest { string uuid = 1; } + +message ListExceptionsRequest { + string user_email = 1; + + int32 page_size = 2; + + string page_token = 3; +} + +message ListExceptionsResponse { + repeated Exception exceptions = 1; + + string next_page_token = 2; +} + +message NotifyAllRequest {} + +// NotifyAll is a long running operation. +// https://google.aip.dev/151 +message NotifyAllResponse { bool has_completed = 1; } + +message NotifyUserRequest { + string user_email = 1; + string title = 2; + string content = 3; + string source_system = 4; +} + +message NotifyUserResponse {} diff --git a/src/nagatha/rest.go b/src/nagatha/rest.go index 7651d25..321e1a6 100644 --- a/src/nagatha/rest.go +++ b/src/nagatha/rest.go @@ -2,38 +2,62 @@ package nagatha import ( "bytes" - "context" "crypto/tls" "crypto/x509" "fmt" "io" "net/http" + "net/url" "time" - "github.com/golang/glog" - "google.golang.org/api/idtoken" + "cloud.google.com/go/longrunning/autogen/longrunningpb" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" + "golang.org/x/net/context" + "golang.org/x/oauth2" "google.golang.org/genproto/protobuf/field_mask" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" + + "github.com/nianticlabs/modron/src/constants" + modronmetric "github.com/nianticlabs/modron/src/metric" + "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/proto/generated/nagatha" ) type ContextKey string const ( authorizationHeader = "Authorization" + httpRequestTimeout = 10 * time.Second + apiVersion = "v2" ) -type NagathaClient struct { - client *http.Client +type clientMetrics struct { + RequestDuration metric.Float64Histogram +} - addr string +type Client struct { + addr string + client *http.Client + tokenSource oauth2.TokenSource + metrics clientMetrics } var ( opts = make([]http.Transport, 0) ) -func NewNagathaClient(addr string) (*NagathaClient, error) { +func apiPath(path string) string { + return "/" + apiVersion + path +} + +func newNagathaClient(addr string, tokenSource oauth2.TokenSource) (*Client, error) { + if tokenSource == nil { + return nil, fmt.Errorf("tokenSource cannot be nil") + } cp, err := x509.SystemCertPool() if err != nil { return nil, fmt.Errorf("cert pool: %w", err) @@ -45,131 +69,229 @@ func NewNagathaClient(addr string) (*NagathaClient, error) { MinVersion: tls.VersionTLS13, }, }) - return &NagathaClient{ - client: &http.Client{Timeout: 10 * time.Second}, - addr: addr, - }, nil + c := &Client{ + client: &http.Client{ + Timeout: httpRequestTimeout, + Transport: otelhttp.NewTransport(http.DefaultTransport), + }, + addr: addr, + tokenSource: tokenSource, + } + err = c.initMetrics() + return c, err +} + +func (c *Client) CreateNotification(ctx context.Context, notification *nagatha.Notification) (model.Notification, error) { + var resultNotification nagatha.Notification + err := c.sendRequest(ctx, http.MethodPost, apiPath("/notifications"), notification, &resultNotification) + return notificationFromProto(&resultNotification), err +} + +func (c *Client) BatchCreateNotifications(ctx context.Context, notifications []*nagatha.Notification) ([]model.Notification, error) { + ctx, span := tracer.Start(ctx, "BatchCreateNotifications") + defer span.End() + var resp longrunningpb.Operation + err := c.sendRequest(ctx, + http.MethodPost, + apiPath("/notifications:batchCreate"), + &nagatha.BatchCreateNotificationsRequest{ + Notifications: notifications, + }, + &resp, + ) + if err != nil { + return nil, err + } + operationID := resp.Name + + deadline := time.Now().Add(5 * time.Minute) //nolint:mnd + for { + if time.Now().After(deadline) { + return nil, fmt.Errorf("operation %q timeout", operationID) + } + op, err := c.GetOperation(ctx, &longrunningpb.GetOperationRequest{ + Name: operationID, + }) + if err != nil { + return nil, fmt.Errorf("get operation %q: %w", operationID, err) + } + if op.Done { + if op.GetError() != nil { + return nil, fmt.Errorf("operation %q failed: %v", operationID, op.GetError()) + } + var result nagatha.BatchCreateNotificationsResponse + if err := op.GetResponse().UnmarshalTo(&result); err != nil { + return nil, fmt.Errorf("unmarshal response: %w", err) + } + var resultNotifications []model.Notification + for _, n := range result.Notifications { + resultNotifications = append(resultNotifications, notificationFromProto(n)) + } + return resultNotifications, nil + } + log.Debugf("waiting for operation %q to complete", operationID) + time.Sleep(1 * time.Second) + } } -func (c *NagathaClient) CreateNotification(ctx context.Context, notification *Notification) error { - return c.sendRequest(ctx, http.MethodPost, "/v1/notification", notification, nil) +func (c *Client) GetOperation(ctx context.Context, req *longrunningpb.GetOperationRequest) (*longrunningpb.Operation, error) { + var operation longrunningpb.Operation + err := c.sendRequest(ctx, http.MethodGet, apiPath("/"+url.PathEscape(req.Name)), nil, &operation) + return &operation, err } -func (c *NagathaClient) GetException(ctx context.Context, uuid string) (*Exception, error) { - request := &GetExceptionRequest{ +func (c *Client) GetException(ctx context.Context, uuid string) (*nagatha.Exception, error) { + request := &nagatha.GetExceptionRequest{ Uuid: uuid, } - response := &Exception{} - if err := c.sendRequest(ctx, http.MethodGet, "/v1/exception", request, response); err != nil { + response := &nagatha.Exception{} + if err := c.sendRequest(ctx, http.MethodGet, apiPath("/exceptions/"+url.PathEscape(uuid)), request, response); err != nil { return nil, err } return response, nil } -func (c *NagathaClient) CreateException(ctx context.Context, exception *Exception) error { - return c.sendRequest(ctx, http.MethodPost, "/v1/exception", exception, nil) +func (c *Client) CreateException(ctx context.Context, exception *nagatha.Exception) error { + return c.sendRequest(ctx, http.MethodPost, apiPath("/exceptions"), exception, nil) } -func (c *NagathaClient) UpdateException(ctx context.Context, exception *Exception, updateMask *field_mask.FieldMask) error { - request := &UpdateExceptionRequest{ +func (c *Client) UpdateException(ctx context.Context, exception *nagatha.Exception, updateMask *field_mask.FieldMask) error { + request := &nagatha.UpdateExceptionRequest{ Exception: exception, UpdateMask: updateMask, } - return c.sendRequest(ctx, http.MethodPatch, "/v1/exception", request, nil) + return c.sendRequest(ctx, http.MethodPatch, apiPath("/exceptions/"+url.PathEscape(exception.Uuid)), request, nil) } -func (c *NagathaClient) DeleteException(ctx context.Context, uuid string) error { - request := &DeleteExceptionRequest{ +func (c *Client) DeleteException(ctx context.Context, uuid string) error { + request := &nagatha.DeleteExceptionRequest{ Uuid: uuid, } - return c.sendRequest(ctx, http.MethodDelete, "/v1/exception", request, nil) + return c.sendRequest(ctx, http.MethodDelete, apiPath("/exceptions/"+url.PathEscape(uuid)), request, nil) } -func (c *NagathaClient) ListExceptions(ctx context.Context, userEmail string, pageSize int32, pageToken string) (*ListExceptionsResponse, error) { - request := &ListExceptionsRequest{ +func (c *Client) ListExceptions(ctx context.Context, userEmail string, pageSize int32, pageToken string) (*nagatha.ListExceptionsResponse, error) { + request := &nagatha.ListExceptionsRequest{ UserEmail: userEmail, PageSize: pageSize, PageToken: pageToken, } - response := &ListExceptionsResponse{} - if err := c.sendRequest(ctx, http.MethodGet, "/v1/exceptions", request, response); err != nil { + response := &nagatha.ListExceptionsResponse{} + if err := c.sendRequest(ctx, http.MethodGet, apiPath("/exceptions"), request, response); err != nil { return nil, err } return response, nil } -func (c *NagathaClient) sendRequest(ctx context.Context, method, path string, request proto.Message, response proto.Message) error { +type RequestError struct { + StatusCode int + Message string +} + +func (r RequestError) Error() string { + return fmt.Sprintf("request failed with status code %d: %s", r.StatusCode, r.Message) +} + +var _ error = (*RequestError)(nil) + +func (c *Client) sendRequest(ctx context.Context, method, path string, request, response proto.Message) error { + ctx, span := tracer.Start(ctx, "sendRequest", + trace.WithAttributes( + attribute.String(constants.TraceKeyMethod, method), + attribute.String(constants.TraceKeyPath, path), + ), + ) + defer span.End() addr := c.addr + path var httpRequest *http.Request var requestBody []byte var err error + reqStart := time.Now() // Serialize request message to JSON if method == http.MethodPost { requestBody, err = protoToJSON(request) if err != nil { - return fmt.Errorf("failed to serialize request message: %v", err) + return fmt.Errorf("failed to serialize request message: %w", err) } - glog.V(10).Infof("nagatha request: %+v", string(requestBody)) - httpRequest, err = http.NewRequest(method, addr, bytes.NewReader(requestBody)) + log.Tracef("nagatha request: %+v", string(requestBody)) + httpRequest, err = http.NewRequestWithContext(ctx, method, addr, bytes.NewReader(requestBody)) if err != nil { - return fmt.Errorf("failed to create HTTP request: %v", err) + return fmt.Errorf("failed to create HTTP request: %w", err) } httpRequest.Header.Set("Content-Type", "application/json") } else if method == http.MethodGet { - httpRequest, err = http.NewRequest(method, addr, nil) + httpRequest, err = http.NewRequestWithContext(ctx, method, addr, nil) if err != nil { - return fmt.Errorf("failed to create HTTP request: %v", err) + return fmt.Errorf("failed to create HTTP request: %w", err) } } - httpResponse, err := c.client.Do(addAuthentication(ctx, httpRequest)) + httpRequest = c.addAuthentication(httpRequest) + httpResponse, err := c.client.Do(httpRequest) if err != nil { - return fmt.Errorf("HTTP request failed: %v", err) + return fmt.Errorf("HTTP request failed: %w", err) } + defer func() { + c.metrics.RequestDuration. + Record(ctx, time.Since(reqStart).Seconds(), + metric.WithAttributes( + attribute.Int(modronmetric.KeyStatus, httpResponse.StatusCode), + attribute.String(modronmetric.KeyMethod, method), + attribute.String(modronmetric.KeyPath, path), + ), + ) + }() defer httpResponse.Body.Close() // Read response body responseBody, err := io.ReadAll(httpResponse.Body) if err != nil { - return fmt.Errorf("failed to read response body: %v", err) + return fmt.Errorf("failed to read response body: %w", err) } // Check HTTP status code if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { - return fmt.Errorf("request failed with status code %d: %s", httpResponse.StatusCode, string(responseBody)) + return RequestError{ + StatusCode: httpResponse.StatusCode, + Message: string(responseBody), + } } // Parse response JSON if response != nil { if err := protojson.Unmarshal(responseBody, response); err != nil { - return fmt.Errorf("failed to parse response JSON: %v", err) + return fmt.Errorf("failed to parse response JSON: %w", err) } } return nil } -func protoToJSON(message proto.Message) ([]byte, error) { - return protojson.Marshal(message) +func (c *Client) addAuthentication(request *http.Request) *http.Request { + token, err := c.tokenSource.Token() + if err != nil { + log.Errorf("TokenSource.Token: %v", err) + } else { + request.Header.Set(authorizationHeader, "Bearer "+token.AccessToken) + } + return request } -func addAuthentication(ctx context.Context, req *http.Request) *http.Request { - // Create an identity token. - // With a global TokenSource tokens would be reused and auto-refreshed at need. - // A given TokenSource is specific to the audience. - tokenSource, err := idtoken.NewTokenSource(ctx, clientID) +func (c *Client) initMetrics() error { + clientReqDurationHist, err := meter.Float64Histogram( + constants.MetricsPrefix+"nagatha_client_request_duration_seconds", + metric.WithDescription("Duration of Nagatha client requests"), + ) if err != nil { - glog.Warningf("idtoken.NewTokenSource: %v", err) - } else { - token, err := tokenSource.Token() - if err != nil { - glog.Warningf("TokenSource.Token: %v", err) - } else { - req.Header.Set(authorizationHeader, "Bearer "+token.AccessToken) - return req - } + return err + } + c.metrics = clientMetrics{ + RequestDuration: clientReqDurationHist, } - glog.Warningf("no authentication added for context") - return req + return nil +} + +func protoToJSON(message proto.Message) ([]byte, error) { + return protojson.Marshal(message) } diff --git a/src/nagatha/rest_test.go b/src/nagatha/rest_test.go new file mode 100644 index 0000000..d072cb6 --- /dev/null +++ b/src/nagatha/rest_test.go @@ -0,0 +1,163 @@ +package nagatha_test + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/h2non/gock" + "github.com/sirupsen/logrus" + "golang.org/x/oauth2" + + "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/nagatha" +) + +const testEndpoint = "https://nagatha.localhost" + +func mockNagathaCreateNotification() func() { + gock.New(testEndpoint). + Post("/v2/notifications"). + MatchHeader("Authorization", "Bearer hunter2"). + Reply(200). + JSON(map[string]any{"uuid": "notification-uuid"}) + return gock.Off +} + +func mockNagathaCreateBatchNotifications() func() { + gock.New(testEndpoint). + Post("/v2/notifications:batchCreate"). + MatchHeader("Authorization", "Bearer hunter2"). + Reply(200). + JSON(map[string]any{ + "name": "operations/my-operation-1", + "done": false, + }) + gock.New(testEndpoint). + Get("/v2/operations/my-operation-1"). + MatchHeader("Authorization", "Bearer hunter2"). + Reply(200). + JSON(map[string]any{ + "name": "my-operation-1", + "done": false, + }) + gock.New(testEndpoint). + Get("/v2/operations/my-operation-1"). + MatchHeader("Authorization", "Bearer hunter2"). + Reply(200). + JSON(map[string]any{ + "name": "my-operation-1", + "done": false, + }) + gock.New(testEndpoint). + Get("/v2/operations/my-operation-1"). + MatchHeader("Authorization", "Bearer hunter2"). + Reply(200). + JSON(map[string]any{ + "name": "operations/my-operation-1", + "done": true, + "response": map[string]any{ + "@type": "com.nianticlabs.nagatha.BatchCreateNotificationsResponse", + "notifications": []map[string]any{ + { + "uuid": "notification-uuid-1", + }, + { + "uuid": "notification-uuid-2", + }, + }, + }, + }) + return gock.Off +} + +func getClient(t *testing.T) model.NotificationService { + t.Helper() + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "hunter2"}) + c, err := nagatha.New(testEndpoint, "modron.localhost", ts) + if err != nil { + t.Fatalf("New: %v", err) + } + return c +} +func TestClient_CreateNagathaNotification_WithToken(t *testing.T) { + ctx := context.Background() + off := mockNagathaCreateNotification() + defer off() + + c := getClient(t) + got, err := c.CreateNotification(ctx, model.Notification{ + SourceSystem: "modron", + Name: "this is a test", + Recipient: "user@example.com", + Content: "notification content", + CreatedOn: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + Interval: 24 * time.Hour, + }) + if err != nil { + t.Fatalf("CreateNotification: %v", err) + } + want := model.Notification{ + UUID: "notification-uuid", + CreatedOn: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), + SentOn: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("notification mismatch (-want, +got): %s", diff) + } +} + +func TestClient_BatchCreateNotifications(t *testing.T) { + ctx := context.Background() + off := mockNagathaCreateBatchNotifications() + defer off() + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + + c := getClient(t) + notifications := []model.Notification{ + { + SourceSystem: "modron", + Name: "this is a test", + Recipient: "user@example.com", + Content: "notification content", + CreatedOn: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "this is another test", + Recipient: "user@example.com", + Content: "notification content", + CreatedOn: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + Interval: 24 * time.Hour, + }, + } + got, err := c.BatchCreateNotifications(ctx, notifications) + if err != nil { + t.Fatalf("BatchCreateNotifications: %v", err) + } + + want := []model.Notification{ + { + UUID: "notification-uuid-1", + SentOn: time.UnixMilli(0), + CreatedOn: time.UnixMilli(0), + }, + { + UUID: "notification-uuid-2", + SentOn: time.UnixMilli(0), + CreatedOn: time.UnixMilli(0), + }, + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("notifications mismatch (-want, +got):\n%s", diff) + } +} + +func TestClient_CreateNagathaNotification_WithoutToken(t *testing.T) { + if _, err := nagatha.New(testEndpoint, "modron.localhost", nil); err == nil { + t.Fatalf("nagatha.New: got nil, want error") + } +} diff --git a/src/proto/.gitignore b/src/proto/.gitignore new file mode 100644 index 0000000..bf9aea0 --- /dev/null +++ b/src/proto/.gitignore @@ -0,0 +1,3 @@ +generated/* +!generated/go.mod +!generated/go.sum diff --git a/src/proto/generated/go.mod b/src/proto/generated/go.mod new file mode 100644 index 0000000..689f8bb --- /dev/null +++ b/src/proto/generated/go.mod @@ -0,0 +1,33 @@ +module github.com/nianticlabs/modron/src/proto/generated + +go 1.23.2 + +require ( + cloud.google.com/go/longrunning v0.6.1 + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 + google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.35.1 + k8s.io/api v0.31.2 + k8s.io/apimachinery v0.31.2 +) + +require ( + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect +) diff --git a/src/proto/generated/go.sum b/src/proto/generated/go.sum new file mode 100644 index 0000000..42446b9 --- /dev/null +++ b/src/proto/generated/go.sum @@ -0,0 +1,129 @@ +cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= +cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= +k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= +k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= +k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= +k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/src/proto/modron.proto b/src/proto/modron.proto index bc84bff..3e9da6f 100644 --- a/src/proto/modron.proto +++ b/src/proto/modron.proto @@ -11,32 +11,85 @@ import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; -option go_package = "./pb"; +import "k8s.io/api/core/v1/generated.proto"; +import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; -message ExportedCredentials { +option go_package = "./"; + +message APIKey {repeated string scopes = 1;} + +// TODO: Consider adding the following: +// - Object versioning policy +message Bucket { + // Object retention policy. + message RetentionPolicy { + // The duration for which objects in the bucket need to be retained. + google.protobuf.Duration period = 1; + // If true, the policy cannot be modified. + bool is_locked = 2; + } + // Server Side Encryption (SSE) policy. + message EncryptionPolicy { + // If true, SSE is enabled for the bucket. Note that SSE is always enabled + // in GCP. + bool is_enabled = 1; + // If true, a Customer-Managed Key (CMK) is used to encrypt objects in the + // bucket instead of a default key provided by a platform Key Management + // Service (KMS). + bool is_key_customer_managed = 2; + } + enum AccessType { + ACCESS_UNKNOWN = 0; + PRIVATE = 1; + PUBLIC = 2; + } + enum AccessControlType { + ACCESS_CONTROL_UNKNOWN = 0; + NON_UNIFORM = 1; + UNIFORM = 2; + } google.protobuf.Timestamp creation_date = 1; - google.protobuf.Timestamp expiration_date = 2; - google.protobuf.Timestamp last_usage = 3; + // The retention policy for objects in the bucket. + optional RetentionPolicy retention_policy = 2; + // The SSE policy for the bucket. + optional EncryptionPolicy encryption_policy = 3; + // If true, the bucket is publicly accessible. + AccessType access_type = 4; + // If true, Access Control Lists (ACLs) are enabled for the bucket. In GCP, + // this entails that uniform bucket-level access is disabled. + AccessControlType access_control_type = 5; } -message VmInstance { - string public_ip = 1; - string private_ip = 2; - // ServiceAccount.Name - string identity = 3; -} +message Certificate { + enum Type { + UNKNOWN = 0; + // Certificate managed by the user and imported into the platform. + IMPORTED = 1; + // Certificate managed by the platform. + MANAGED = 2; + } + Type type = 1; -message Network { - repeated string ips = 1; - bool gcp_private_google_access_v4 = 2; -} + // Fully-qualified domain name bound to the certificate. + string domain_name = 2; -message KubernetesCluster { - repeated string master_authorized_networks = 1; - bool private_cluster = 2; - string master_version = 3; - string nodes_version = 4; - string location = 5; + // The list of alternative domain names bound to the certificate. + // See https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6. + repeated string subject_alternative_names = 3; + + google.protobuf.Timestamp creation_date = 4; + google.protobuf.Timestamp expiration_date = 5; + + // The name of the certificate authority that issued the certificate. + string issuer = 6; + + // The algorithm that was used by the issuer to sign the certificate. + string signature_algorithm = 7; + + // The chain starts with the leaf certificate and continues with the + // remaining endorsing certificates in the chain of trust, if any. + // See https://datatracker.ietf.org/doc/html/rfc1421.html. + string pem_certificate_chain = 8; } message Database { @@ -94,6 +147,12 @@ message Database { bool is_public = 12; } +message ExportedCredentials { + google.protobuf.Timestamp creation_date = 1; + google.protobuf.Timestamp expiration_date = 2; + google.protobuf.Timestamp last_usage = 3; +} + message IamGroup { message EntityKey { @@ -144,96 +203,50 @@ message IamGroup { optional DynamicGroupMetadata dynamic_group_metadata = 9; } -// TODO: Consider adding the following: -// - Object versioning policy -message Bucket { - // Object retention policy. - message RetentionPolicy { - // The duration for which objects in the bucket need to be retained. - google.protobuf.Duration period = 1; - // If true, the policy cannot be modified. - bool is_locked = 2; - } - // Server Side Encryption (SSE) policy. - message EncryptionPolicy { - // If true, SSE is enabled for the bucket. Note that SSE is always enabled - // in GCP. - bool is_enabled = 1; - // If true, a Customer-Managed Key (CMK) is used to encrypt objects in the - // bucket instead of a default key provided by a platform Key Management - // Service (KMS). - bool is_key_customer_managed = 2; - } - enum AccessType { - ACCESS_UNKNOWN = 0; - PRIVATE = 1; - PUBLIC = 2; - } - enum AccessControlType { - ACCESS_CONTROL_UNKNOWN = 0; - NON_UNIFORM = 1; - UNIFORM = 2; - } - google.protobuf.Timestamp creation_date = 1; - // The retention policy for objects in the bucket. - optional RetentionPolicy retention_policy = 2; - // The SSE policy for the bucket. - optional EncryptionPolicy encryption_policy = 3; - // If true, the bucket is publicly accessible. - AccessType access_type = 4; - // If true, Access Control Lists (ACLs) are enabled for the bucket. In GCP, - // this entails that uniform bucket-level access is disabled. - AccessControlType access_control_type = 5; -} - -message APIKey { repeated string scopes = 1; } - -message Permission { - string role = 1; - repeated string principals = 2; -} - message IamPolicy { // Resource this IAM policy is attached to. Resource resource = 1; repeated Permission permissions = 2; } -message SslPolicy { - enum MinTlsVersion { - MinTlsVersion_UNKNOWN = 0; - TLS_1_0 = 1; - TLS_1_1 = 2; - TLS_1_2 = 3; - TLS_1_3 = 4; - } - enum Profile { - Profile_UNKNOWN = 0; - COMPATIBLE = 1; - MODERN = 2; - RESTRICTED = 3; - CUSTOM = 4; - } - google.protobuf.Timestamp creation_date = 1; - string name = 2; - Profile profile = 3; - MinTlsVersion minTlsVersion = 4; - repeated string enabledFeatures = 5; - repeated string customFeatures = 6; -} +message KubernetesCluster { + repeated string master_authorized_networks = 1; + bool private_cluster = 2; + string master_version = 3; + string nodes_version = 4; + string location = 5; -message ServiceAccount { - repeated ExportedCredentials exported_credentials = 1; -} + message Security { + enum VulnScanning { + /* + This enum is used to represent whether Vulnerability Scanning is enabled in the Kubernetes cluster. + Some Cloud Providers provide an option to do scanning at the cluster level (e.g: GCP w/ GKE), others + provide it at the registry level (e.g: AWS ECR image scanning, Azure Defender for Containers). + + This enum is purely related to the scanning capabilities at the cluster level. + We consider "BASIC" the bare minimum check provided by the cloud provider, this is for example a check + for OS vulnerabilities in the container image. + We consider "ADVANCED" to be a more in-depth check of the container image, for example by also scanning + dependencies (e.g: Go dependencies) for known vulnerabilities. + + GCP uses the "Standard" (here BASIC) and "Enterprise" (here ADVANCED) tiers for this. + If a cloud provider has more than these tiers, we consider everything that is not the maximum tier to be "BASIC". + Of course we can extend this enum if needed, but for the moment the current distinction should be enough. + + AWS: https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning.html (not exactly the same, registry-level) + GCP: https://cloud.google.com/kubernetes-engine/docs/how-to/security-posture-vulnerability-scanning#vuln-scanning-tiers + Azure: https://learn.microsoft.com/en-us/azure/defender-for-cloud/defender-for-containers-enable or https://learn.microsoft.com/en-us/azure/defender-for-cloud/support-matrix-defender-for-containers#vulnerability-assessment + */ + VULN_SCAN_UNKNOWN = 0; + VULN_SCAN_DISABLED = 1; + VULN_SCAN_BASIC = 2; // GCP: Standard, AWS: Basic + VULN_SCAN_ADVANCED = 3; // GCP: Enterprise, AWS: Enhanced + } -// ResourceGroup designates the smallest administrative grouping of resources. -message ResourceGroup { - // Environment describes the environment of this resource group. For instance - // prod, dev, etc. - string environment = 1; - // Number describes an ID used by the platform to identify the Resource Group. - // In GCP this is the project number. - string identifier = 2; + VulnScanning vulnerability_scanning = 1; + } + + Security security = 6; } message LoadBalancer { @@ -246,38 +259,158 @@ message LoadBalancer { Type type = 1; repeated Certificate certificates = 2; SslPolicy sslPolicy = 3; + IAP iap = 4; } -message Certificate { - enum Type { +message IAP { + bool enabled = 1; + string cliend_id = 2; +} + +message Namespace { + string cluster = 1; + google.protobuf.Timestamp creation_time = 2; +} + +message Network { + repeated string ips = 1; + bool gcp_private_google_access_v4 = 2; +} + +message Operation { + string id = 1; + string resource_group = 2; + string type = 3; + google.protobuf.Timestamp status_time = 4; + Status status = 5; + string reason = 6; + + enum Status { UNKNOWN = 0; - // Certificate managed by the user and imported into the platform. - IMPORTED = 1; - // Certificate managed by the platform. - MANAGED = 2; + STARTED = 1; + CANCELLED = 2; + COMPLETED = 3; + FAILED = 4; } - Type type = 1; +} - // Fully-qualified domain name bound to the certificate. - string domain_name = 2; +message Observation { + enum Category { + CATEGORY_UNKNOWN = 0; + CATEGORY_VULNERABILITY = 1; + CATEGORY_MISCONFIGURATION = 2; + CATEGORY_TOXIC_COMBINATION = 3; + } - // The list of alternative domain names bound to the certificate. - // See https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6. - repeated string subject_alternative_names = 3; + enum Source { + SOURCE_UNKNOWN = 0; + SOURCE_MODRON = 1; + SOURCE_SCC = 2; + } - google.protobuf.Timestamp creation_date = 4; - google.protobuf.Timestamp expiration_date = 5; - // The name of the certificate authority that issued the certificate. - string issuer = 6; + string uid = 1; + // The identifier of the scan that detected this observation. + // As we can have observations created at the "collection" phase, this field might be empty and the scan_uid + // field will be populated instead. + optional string scan_uid = 2; + google.protobuf.Timestamp timestamp = 3; + Resource deprecated_resource = 4; // Deprecated, we have the resource in the DB already and duplicating it here is useless + // Human readable name of the observation. + string name = 5; + // Value found in the configuration that causes the issue. + google.protobuf.Value expected_value = 6; + google.protobuf.Value observed_value = 7; + Remediation remediation = 8; - // The algorithm that was used by the issuer to sign the certificate. - string signature_algorithm = 7; + // DATABASE FIELDS + // ---------------------------- + // These fields will be stored as columns in the DB or will be populated by the DB + ResourceRef resource_ref = 9; + // When observations are fetched from an external API, we do that at the collection phase, which is why we + // store here a collection_id. + optional string collection_id = 10; + // An ID that can be used in the external system (source) to identify this observation + optional string external_id = 11; + // Source defines where the observation comes from + Source source = 12; + Severity risk_score = 13; + Category category = 14; + + Severity severity = 15; + Impact impact = 16; + // impact_reason defines why we've attributed this impact to the observation + string impact_reason = 17; +} - // The chain starts with the leaf certificate and continues with the - // remaining endorsing certificates in the chain of trust, if any. - // See https://datatracker.ietf.org/doc/html/rfc1421.html. - string pem_certificate_chain = 8; +enum Severity { + SEVERITY_UNKNOWN = 0; + SEVERITY_INFO = 1; + SEVERITY_LOW = 2; + SEVERITY_MEDIUM = 3; + SEVERITY_HIGH = 4; + SEVERITY_CRITICAL = 5; +} + +message ResourceRef { + optional string uid = 1; // This is the UUID of the resource, as stored in the Modron DB + string group_name = 2; // This is the ResourceGroupName identifier + + // This is the identifier used by the cloud platform to uniquely identify the resource, for example + //container.googleapis.com/projects/modron-test/locations/us-central1/clusters/modron-test-cluster/k8s/namespaces/modron-test-namespace + optional string external_id = 3; + + // This is the cloud platform, for example "GCP" or "AWS" + CloudPlatform cloud_platform = 4; +} + +enum CloudPlatform { + PLATFORM_UNKNOWN = 0; + GCP = 1; + AWS = 2; + AZURE = 3; +} + +enum Impact { + IMPACT_UNKNOWN = 0; + IMPACT_LOW = 1; + IMPACT_MEDIUM = 2; + IMPACT_HIGH = 3; +} + +message Permission { + string role = 1; + repeated string principals = 2; +} + +message Pod { + string cluster = 1; + string namespace = 2; + google.protobuf.Timestamp creation_time = 3; + enum Phase { + UNKNOWN_PHASE = 0; + PENDING = 1; + RUNNING = 2; + SUCCEEDED = 3; + FAILED = 4; + UNKNOWN = 5; + } + + Phase phase = 4; + k8s.io.api.core.v1.PodSpec spec = 5; + k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta object_meta = 6; +} + +message RecursiveResource { + string uuid = 1; + string name = 2; + string display_name = 3; + string type = 4; + string parent = 5; + map labels = 6; + map tags = 7; + + repeated RecursiveResource children = 8; } message Resource { @@ -299,8 +432,19 @@ message Resource { string resource_group_name = 8; // IamPolicy describes the IAM policy associated with that resource. IamPolicy iam_policy = 9; + + map labels = 10; + map tags = 11; + + // ancestors is a list of resource groups that are ancestors of this resource. + // Since RGs can only have one parent, this is an UNORDERED list of all the + // ancestors of this resource. The tree must be reconstructed by + // analyzing the relationship between the resource groups. + repeated string ancestors = 12; + // Types should be generic enough that they can match types of different cloud // providers. + reserved 111; // Old IAMGroup oneof type { VmInstance vm_instance = 100; Network network = 101; @@ -313,29 +457,58 @@ message Resource { Bucket bucket = 108; Certificate certificate = 109; Database database = 110; - IamGroup group = 111; + Namespace namespace = 112; + Pod pod = 113; } } +// ResourceGroup designates the smallest administrative grouping of resources. +message ResourceGroup { + reserved 1; + // Number describes an ID used by the platform to identify the Resource Group. + // In GCP this is the project number. + string identifier = 2; + string name = 3; +} + message Remediation { string description = 1; string recommendation = 2; } -message Observation { - string uid = 1; - string scan_uid = 2; - google.protobuf.Timestamp timestamp = 3; - Resource resource = 4; - // Human readable name of the observation. - string name = 5; - // Value found in the configuration that causes the issue. - google.protobuf.Value expected_value = 6; - google.protobuf.Value observed_value = 7; - Remediation remediation = 8; +message ServiceAccount { + repeated ExportedCredentials exported_credentials = 1; } -message ScanResultsList { repeated Observation observations = 1; } +message SslPolicy { + enum MinTlsVersion { + MinTlsVersion_UNKNOWN = 0; + TLS_1_0 = 1; + TLS_1_1 = 2; + TLS_1_2 = 3; + TLS_1_3 = 4; + } + enum Profile { + Profile_UNKNOWN = 0; + COMPATIBLE = 1; + MODERN = 2; + RESTRICTED = 3; + CUSTOM = 4; + } + google.protobuf.Timestamp creation_date = 1; + string name = 2; + Profile profile = 3; + MinTlsVersion minTlsVersion = 4; + repeated string enabledFeatures = 5; + repeated string customFeatures = 6; +} + +message VmInstance { + string public_ip = 1; + string private_ip = 2; + // ServiceAccount.Name + string identity = 3; +} service ModronService { // Scanning a project is a long running operation. We don't expect the user @@ -344,6 +517,8 @@ service ModronService { // here, but it's quite an overhead for the first implementation. Performs a // collection, followed by a scan, on the requested resource groups rpc CollectAndScan(CollectAndScanRequest) returns (CollectAndScanResponse); + rpc CollectAndScanAll(CollectAndScanAllRequest) returns (CollectAndScanResponse); + // List the latest observations resource groups rpc ListObservations(ListObservationsRequest) returns (ListObservationsResponse); @@ -380,7 +555,8 @@ message GetStatusCollectAndScanRequest { string scan_id = 2; } -message CollectAndScanRequest { repeated string resource_group_names = 1; } +message CollectAndScanRequest {repeated string resource_group_names = 1;} +message CollectAndScanAllRequest {} message CollectAndScanResponse { string collect_id = 1; @@ -393,7 +569,12 @@ message ListObservationsRequest { repeated string resource_group_names = 3; } -message CreateObservationRequest { Observation observation = 1; } +message ListObservationsResponse { + repeated ResourceGroupObservationsPair resource_groups_observations = 1; + string next_page_token = 2; +} + +message CreateObservationRequest {Observation observation = 1;} // we use this pair to get information about the rules that have no observations message RuleObservationPair { @@ -408,7 +589,10 @@ message ResourceGroupObservationsPair { repeated RuleObservationPair rules_observations = 2; } -message ListObservationsResponse { - repeated ResourceGroupObservationsPair resource_groups_observations = 1; - string next_page_token = 2; -} +message ScanResultsList {repeated Observation observations = 1;} + +enum ScanType { + SCAN_TYPE_UNKNOWN = 0; + SCAN_TYPE_PARTIAL = 1; + SCAN_TYPE_FULL = 2; +} \ No newline at end of file diff --git a/src/proto/notification.proto b/src/proto/notification.proto index f05e561..9ccd12e 100644 --- a/src/proto/notification.proto +++ b/src/proto/notification.proto @@ -4,7 +4,7 @@ import "google/protobuf/empty.proto"; import "google/protobuf/field_mask.proto"; import "google/protobuf/timestamp.proto"; -option go_package = "./pb"; +option go_package = "./"; message NotificationException { string uuid = 1; diff --git a/src/risk/risk.go b/src/risk/risk.go new file mode 100644 index 0000000..f6207c8 --- /dev/null +++ b/src/risk/risk.go @@ -0,0 +1,206 @@ +package risk + +import ( + "fmt" + "strings" + + "github.com/sirupsen/logrus" + + "github.com/nianticlabs/modron/src/constants" + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +const ( + tagKeyParts = 2 + tagValueParts = 3 +) + +var log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "risk") + +type TagConfig struct { + ImpactMap map[string]pb.Impact + Environment string + EmployeeData string + CustomerData string +} + +func GetRiskScore(impact pb.Impact, severity pb.Severity) pb.Severity { + log.Debugf("risk score: impact=%s, severity=%s", impact, severity) + switch impact { + case pb.Impact_IMPACT_HIGH: + switch severity { + case pb.Severity_SEVERITY_CRITICAL: + return pb.Severity_SEVERITY_CRITICAL + case pb.Severity_SEVERITY_HIGH: + return pb.Severity_SEVERITY_CRITICAL + case pb.Severity_SEVERITY_MEDIUM: + return pb.Severity_SEVERITY_HIGH + case pb.Severity_SEVERITY_LOW: + return pb.Severity_SEVERITY_MEDIUM + case pb.Severity_SEVERITY_INFO: + return pb.Severity_SEVERITY_LOW + } + case pb.Impact_IMPACT_MEDIUM: + return severity + case pb.Impact_IMPACT_LOW: + switch severity { + case pb.Severity_SEVERITY_CRITICAL: + return pb.Severity_SEVERITY_HIGH + case pb.Severity_SEVERITY_HIGH: + return pb.Severity_SEVERITY_MEDIUM + case pb.Severity_SEVERITY_MEDIUM: + return pb.Severity_SEVERITY_LOW + case pb.Severity_SEVERITY_LOW: + return pb.Severity_SEVERITY_INFO + case pb.Severity_SEVERITY_INFO: + return pb.Severity_SEVERITY_INFO + } + } + return pb.Severity_SEVERITY_UNKNOWN +} + +type impactReason struct { + impact pb.Impact + reason string +} + +func GetEnvironment(tagConfig TagConfig, hierarchy map[string]*pb.RecursiveResource, rgName string) string { + parent := rgName + mergedTags := map[string]string{} + for { + if parent == "" { + break + } + v, ok := hierarchy[parent] + if !ok { + break + } + + for k, v := range v.Tags { + if _, ok := mergedTags[k]; !ok { + // Label not found, adding + mergedTags[k] = v + } + } + parent = v.Parent + } + + if v, ok := mergedTags[tagConfig.Environment]; ok { + tagValue := humanReadableTagValue(v) + return tagValue + } + return "" +} + +// humanReadableTagKey converts a tag name in the format "111111111111/employee_data" to employee_data +func humanReadableTagKey(tagKey string) string { + split := strings.SplitN(tagKey, "/", tagKeyParts) + if len(split) != tagKeyParts { + log.Warnf("unexpected tag key format: %q", tagKey) + return tagKey + } + return split[1] +} + +// humanReadableTagValue converts a tag value from the format "111111111111/environment/prod" to prod +func humanReadableTagValue(tagValue string) string { + split := strings.SplitN(tagValue, "/", tagValueParts) + if len(split) != tagValueParts { + log.Warnf("unexpected tag value format: %q", tagValue) + return tagValue + } + return split[2] +} + +// GetImpact computes the impact of a resource group based on the tags set in its hierarchy. +// The label at the deepest level of the hierarchy is the one that will be considered first. +// If a tags is set at the organization label, it might be overwritten by the project label - because they +// take precedence in our impact analysis. +// +// If we find something that has a high impact, we immediately return since this is the highest possible impact. +// In case of multiple impacts, we return the highest one. +func GetImpact(tagConfig TagConfig, hierarchy map[string]*pb.RecursiveResource, rgName string) (impact pb.Impact, reason string) { + parent := rgName + mergedTags := map[string]string{} + for { + if parent == "" { + break + } + v, ok := hierarchy[parent] + if !ok { + break + } + + for k, v := range v.Tags { + if _, ok := mergedTags[k]; !ok { + // Label not found, adding + mergedTags[k] = v + } + } + parent = v.Parent + } + impactLogger := log.WithField("resource_group", rgName) + + var impacts []impactReason + env := GetEnvironment(tagConfig, hierarchy, rgName) + if env != "" { + impacts = append(impacts, impactReason{ + impact: impactFromEnvironment(tagConfig.ImpactMap, env), + reason: fmt.Sprintf("%s=%s", humanReadableTagKey(tagConfig.Environment), env), + }) + } + + if v, ok := mergedTags[tagConfig.EmployeeData]; ok { + tagValue := humanReadableTagValue(v) + switch strings.ToLower(tagValue) { + case "yes": + impacts = append(impacts, impactReason{ + impact: constants.ImpactEmployeeData, + reason: fmt.Sprintf("%s=%s", constants.ResourceLabelEmployeeData, tagValue), + }) + case "no": + // All good + default: + impactLogger.Warnf("unknown value for label %s: %q", constants.ResourceLabelEmployeeData, tagValue) + } + } + + if v, ok := mergedTags[tagConfig.CustomerData]; ok { + tagValue := humanReadableTagValue(v) + switch strings.ToLower(tagValue) { + case "yes": + impacts = append(impacts, impactReason{ + impact: constants.ImpactCustomerData, + reason: fmt.Sprintf("%s=%s", constants.ResourceLabelCustomerData, tagValue), + }) + case "no": + // All good + default: + impactLogger.Warnf("unknown value for label %s: %q", constants.ResourceLabelCustomerData, tagValue) + } + } + + log.Debugf("mergedTags=%+v, impacts=%+v", mergedTags, impacts) + if len(impacts) == 0 { + log.Debugf("no facts that would change the impact") + return pb.Impact_IMPACT_MEDIUM, reason + } + highestImpact := pb.Impact_IMPACT_UNKNOWN + for _, i := range impacts { + if i.impact > highestImpact { + highestImpact = i.impact + reason = i.reason + } + } + return highestImpact, reason +} + +func impactFromEnvironment(impactMap map[string]pb.Impact, env string) pb.Impact { + v, ok := impactMap[env] + if !ok { + defaultImpact := pb.Impact_IMPACT_MEDIUM + log.Warnf("no impact found for environment %q, using %s", env, defaultImpact.String()) + return defaultImpact + } + return v +} diff --git a/src/server.go b/src/server.go index b013092..68a9150 100644 --- a/src/server.go +++ b/src/server.go @@ -1,373 +1,243 @@ -// Binary modron is a Cloud auditing tool. - -// Modron compares the existing state with a set of predefined rules -// and provides ways to crowd source the resolution of issues -// to resource owners. package main import ( "context" - "errors" + "encoding/json" "fmt" - "os" + "regexp" "strings" "time" - "github.com/golang/glog" - "github.com/google/uuid" "golang.org/x/exp/maps" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/emptypb" - "google.golang.org/protobuf/types/known/timestamppb" + "golang.org/x/oauth2" + "google.golang.org/api/idtoken" + + "github.com/improbable-eng/grpc-web/go/grpcweb" + _ "github.com/lib/pq" + + "github.com/nianticlabs/modron/src/acl/fakeacl" + "github.com/nianticlabs/modron/src/acl/gcpacl" + "github.com/nianticlabs/modron/src/collector" + "github.com/nianticlabs/modron/src/collector/gcpcollector" "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/engine" "github.com/nianticlabs/modron/src/engine/rules" + "github.com/nianticlabs/modron/src/lognotifier" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + "github.com/nianticlabs/modron/src/nagatha" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/service" + "github.com/nianticlabs/modron/src/statemanager/reqdepstatemanager" + "github.com/nianticlabs/modron/src/storage" + "github.com/nianticlabs/modron/src/storage/gormstorage" + "github.com/nianticlabs/modron/src/storage/memstorage" ) -// TODO: Implement paginated API -type modronService struct { - checker model.Checker - collector model.Collector - notificationSvc model.NotificationService - ruleEngine model.RuleEngine - stateManager model.StateManager - storage model.Storage - // Required - pb.UnimplementedModronServiceServer - pb.UnimplementedNotificationServiceServer -} - -func (modron *modronService) validateResourceGroupNames(ctx context.Context, resourceGroupNames []string) ([]string, error) { - ownedResourceGroups, err := modron.checker.ListResourceGroupNamesOwned(ctx) - if err != nil { - glog.Warningf("validate resource groups: %v", err) - return nil, status.Error(codes.Unauthenticated, "failed authenticating request") - } - if len(resourceGroupNames) == 0 { - for k := range ownedResourceGroups { - resourceGroupNames = append(resourceGroupNames, k) - } - } else { - for _, rsgName := range resourceGroupNames { - if _, ok := ownedResourceGroups[rsgName]; !ok { - return nil, status.Error(codes.PermissionDenied, "resource group(s) is inaccessible") - } - } - } - return resourceGroupNames, nil -} +const ( + defaultCacheTimeout = 20 * time.Second +) -func (modron *modronService) scan(ctx context.Context, resourceGroupNames []string, scanId string) { - glog.V(5).Infof("request scan %s for %+v", scanId, resourceGroupNames) - filteredGroups := modron.stateManager.AddScan(scanId, resourceGroupNames) - glog.V(5).Infof("filtered scan %s for %+v", scanId, filteredGroups) - if len(filteredGroups) < 1 { - glog.Warningf("no groups to scan, aborting %s", scanId) - return - } else { - glog.Infof("starting scan %s for resource groups %v", scanId, filteredGroups) - } - obs, errs := modron.ruleEngine.CheckRules(ctx, scanId, filteredGroups) - if len(errs) > 0 { - glog.Errorf("scanId %s: %v", scanId, errors.Join(errs...)) - } - glog.V(5).Infof("ending scan %s for %+v", scanId, filteredGroups) - modron.stateManager.EndScan(scanId, filteredGroups) - glog.Infof("scan %s completed", scanId) - if err := modron.storage.FlushOpsLog(ctx); err != nil { - glog.Warningf("flush ops log: %v", err) - } - if len(obs) < 1 { - glog.Warningf("scan %s returned no observations.", scanId) - } - for _, o := range obs { - notifications, err := modron.notificationsFromObservation(ctx, o) +func newServer(ctx context.Context) (*service.Modron, error) { + ctx, span := tracer.Start(ctx, "newServer") + defer span.End() + var st model.Storage + var err error + switch storage.Type(strings.ToLower(args.Storage)) { + case storage.Memory: + log.Warnf("using memory storage: this should never be used in production") + st = memstorage.New() + case storage.SQL: + log.Tracef("setting up SQL") + st, err = gormstorage.NewDB( + args.SQLBackendDriver, + args.SQLConnectionString, + gormstorage.Config{ + BatchSize: args.DbBatchSize, + LogAllQueries: args.LogAllSQLQueries, + }, + ) if err != nil { - glog.Warningf("notifications: %v", err) - continue - } - for _, n := range notifications { - _, err := modron.notificationSvc.CreateNotification(ctx, n) - if err != nil { - if s, ok := status.FromError(err); !ok { - glog.Warningf("notification creation: %v", err) - } else { - if s.Code() == codes.AlreadyExists || s.Code() == codes.FailedPrecondition { - // Failed precondition is returned when an exception exist for the notification. - continue - } else { - glog.Warningf("notification %v creation: %s: %s", n, s.Code(), s.Message()) - } - } - } + return nil, fmt.Errorf("sql storage creation: %w", err) } + default: + return nil, fmt.Errorf("invalid storage \"%s\" specified", args.Storage) } -} -func (modron *modronService) collect(ctx context.Context, resourceGroupNames []string, collectId string) { - glog.V(5).Infof("request collection %s for %+v", collectId, resourceGroupNames) - filteredGroups := modron.stateManager.AddCollect(collectId, resourceGroupNames) - glog.V(5).Infof("filtered collection %s for %+v", collectId, filteredGroups) - if len(filteredGroups) > 0 { - glog.Infof("collect start id: %s for %+v", collectId, filteredGroups) - if err := modron.collector.CollectAndStoreAllResourceGroupResources(ctx, collectId, filteredGroups); len(err) != 0 { - glog.Warningf("collectId %s, errs: %v", collectId, errors.Join(err...)) - } else { - glog.Infof("collect done %s", collectId) - } + // Get Impact map + impactMap, err := getImpactMap(args.ImpactMap) + if err != nil { + return nil, fmt.Errorf("getImpactMap: %w", err) } - modron.stateManager.EndCollect(collectId, filteredGroups) -} - -func (modron *modronService) collectAndScan(ctx context.Context, resourceGroupNames []string) *pb.CollectAndScanResponse { - modronCtx := context.Background() - collectId, scanId := uuid.NewString(), uuid.NewString() - go func(resourceGroupNames []string) { - modron.collect(modronCtx, resourceGroupNames, collectId) - modron.scan(modronCtx, resourceGroupNames, scanId) - }(resourceGroupNames) - return &pb.CollectAndScanResponse{ - CollectId: collectId, - ScanId: scanId, + if len(maps.Keys(impactMap)) == 0 { + log.Warn("Impact map is empty") } -} -// rpc exposed CollectAndScan with resource group ownership validation -func (modron *modronService) CollectAndScan(ctx context.Context, in *pb.CollectAndScanRequest) (*pb.CollectAndScanResponse, error) { - resourceGroupNames, err := modron.validateResourceGroupNames(ctx, in.ResourceGroupNames) + // Parse rule configs + ruleConfigs, err := parseRuleConfigs(args.RuleConfigs) if err != nil { - return nil, status.Errorf(codes.FailedPrecondition, "resource group %+v: %v", in.ResourceGroupNames, err) + return nil, fmt.Errorf("parseRuleConfigs: %w", err) } - return modron.collectAndScan(ctx, resourceGroupNames), nil -} - -func (modron *modronService) scheduledRunner(ctx context.Context) { - intervalS := os.Getenv(collectAndScanInterval) - interval, err := time.ParseDuration(intervalS) - if err != nil || interval < time.Hour { // enforce a minimum of 1 hour interval - interval, _ = time.ParseDuration(defaultCollectAndScanInterval) - glog.Warningf("scan scheduler: env interval: %v . Keeping default value: %s", err, interval) + additionalAdminRoles := map[constants.Role]struct{}{} + for _, v := range args.AdditionalAdminRoles { + theRole := constants.ToRole(v) + additionalAdminRoles[theRole] = struct{}{} } - for { - glog.Infof("scan scheduler: starting") - if ctx.Err() != nil { - glog.Errorf("scan scheduler: %v", ctx.Err()) - return - } - rgs, err := modron.collector.ListResourceGroupNames(ctx) - if err != nil { - glog.Errorf("list resource groups: %v", err) - return - } - r := modron.collectAndScan(ctx, rgs) - glog.Infof("scan scheduler done: collectionID: %s, scanID: %s", r.CollectId, r.ScanId) - time.Sleep(interval) - } -} -func (modron *modronService) ListObservations(ctx context.Context, in *pb.ListObservationsRequest) (*pb.ListObservationsResponse, error) { - groups, err := modron.validateResourceGroupNames(ctx, in.ResourceGroupNames) - if err != nil { - return nil, err - } - obsByGroupByRules := map[string]map[string][]*pb.Observation{} - oneWeekAgo := time.Now().Add(-time.Hour * 24 * 7) - obs, err := modron.storage.ListObservations(ctx, model.StorageFilter{ - ResourceGroupNames: groups, - StartTime: oneWeekAgo, - TimeOffset: time.Since(oneWeekAgo), - }) - if err != nil { - glog.Warningf("list observations: %v", err) - return nil, status.Error(codes.Internal, "failed listing observations") - } - for _, group := range groups { - obsByGroupByRules[group] = map[string][]*pb.Observation{} - for _, rule := range rules.GetRules() { - obsByGroupByRules[group][rule.Info().Name] = []*pb.Observation{} - } - } - for _, ob := range obs { - group := ob.Resource.ResourceGroupName - rule := ob.Name - if obsByGroupByRules[group] == nil { - obsByGroupByRules[group] = map[string][]*pb.Observation{} - } - obsByGroupByRules[group][rule] = append( - obsByGroupByRules[group][rule], - ob, - ) - } - res := []*pb.ResourceGroupObservationsPair{} - for name, ruleObs := range obsByGroupByRules { - val := []*pb.RuleObservationPair{} - for rule, obs := range ruleObs { - val = append(val, &pb.RuleObservationPair{ - Rule: rule, - Observations: obs, - }) - } - res = append(res, &pb.ResourceGroupObservationsPair{ - ResourceGroupName: name, - RulesObservations: val, - }) + log.Tracef("Purging incomplete operations") + if err := st.PurgeIncompleteOperations(ctx); err != nil { + log.Errorf("Purging incomplete operations: %v", err) } - return &pb.ListObservationsResponse{ - ResourceGroupsObservations: res, - }, nil -} -func (modron *modronService) CreateObservation(ctx context.Context, in *pb.CreateObservationRequest) (*pb.Observation, error) { - if in.Observation == nil { - return nil, status.Error(codes.InvalidArgument, "observation is nil") - } - if in.Observation.Name == "" { - return nil, status.Error(codes.InvalidArgument, "observation does not have a name") + tagConfig := risk.TagConfig{ + ImpactMap: impactMap, + Environment: args.TagEnvironment, + EmployeeData: args.TagEmployeeData, + CustomerData: args.TagCustomerData, } - if in.Observation.Resource == nil { - return nil, status.Error(codes.InvalidArgument, "resource to link observation with not defined") - } - if in.Observation.Remediation == nil || in.Observation.Remediation.Recommendation == "" { - return nil, status.Error(codes.InvalidArgument, "cannot create an observation without recommendation") - } - in.Observation.Timestamp = timestamppb.Now() - res, err := modron.storage.ListResources(ctx, model.StorageFilter{ResourceNames: []string{in.Observation.Resource.Name}}) + + log.Tracef("Creating rule engine") + ruleEngine, err := engine.New(st, rules.GetRules(), ruleConfigs, args.ExcludedRules, tagConfig) if err != nil { - return nil, status.Errorf(codes.NotFound, "resource to link observation to not found: %v", err) + return nil, fmt.Errorf("creating rule engine: %w", err) } - if len(res) != 1 { - return nil, status.Errorf(codes.FailedPrecondition, "found %d resources matching %+v", len(res), in.Observation.Resource) - } - in.Observation.Resource = res[0] - if obs, err := modron.storage.BatchCreateObservations(ctx, []*pb.Observation{in.Observation}); err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } else { - if len(obs) != 1 { - return nil, status.Errorf(codes.Internal, "creation returned %d items", len(obs)) - } else { - return obs[0], nil - } - } -} -func (modron *modronService) GetStatusCollectAndScan(ctx context.Context, in *pb.GetStatusCollectAndScanRequest) (*pb.GetStatusCollectAndScanResponse, error) { - collectStatus := modron.stateManager.GetCollectState(in.CollectId) - scanStatus := modron.stateManager.GetScanState(in.ScanId) - return &pb.GetStatusCollectAndScanResponse{ - CollectStatus: collectStatus, - ScanStatus: scanStatus, - }, nil -} - -func (modron *modronService) GetNotificationException(ctx context.Context, req *pb.GetNotificationExceptionRequest) (*pb.NotificationException, error) { - ex, err := modron.validateUserAndGetException(ctx, req.Uuid) + log.Tracef("Creating collector and ACL checker") + coll, checker, err := getCollectorAndChecker(ctx, st, tagConfig) if err != nil { - return nil, err + return nil, fmt.Errorf("getCollectorAndChecker: %w", err) } - return ex.ToProto(), err -} -func (modron *modronService) CreateNotificationException(ctx context.Context, req *pb.CreateNotificationExceptionRequest) (*pb.NotificationException, error) { - if userEmail, err := modron.checker.GetValidatedUser(ctx); err != nil { - return nil, status.Error(codes.Unauthenticated, "failed authenticating request") - } else { - req.Exception.UserEmail = userEmail + log.Tracef("Creating reqdepstate manager") + stateManager, err := reqdepstatemanager.New() + if err != nil { + return nil, fmt.Errorf("creating reqdepstatemanager: %w", err) } - ex, err := modron.notificationSvc.CreateException(ctx, model.ExceptionFromProto(req.Exception)) - return ex.ToProto(), err -} -func (modron *modronService) UpdateNotificationException(ctx context.Context, req *pb.UpdateNotificationExceptionRequest) (*pb.NotificationException, error) { - if ex, err := modron.validateUserAndGetException(ctx, req.Exception.Uuid); err != nil { - return nil, err + log.Tracef("Creating Notification Service") + var notificationSvc model.NotificationService + notificationSvcAddr := args.NotificationService + if notificationSvcAddr != "" { + notificationSvc, err = setupNotificationService(ctx, notificationSvcAddr) + if err != nil { + return nil, fmt.Errorf("unable to setup notification service: %w", err) + } } else { - req.Exception.UserEmail = ex.UserEmail + log.Tracef("Using lognotifier as notification service") + log.Infof("NotificationService argument is empty, logging instead") + notificationSvc = lognotifier.New() } - ex, err := modron.notificationSvc.UpdateException(ctx, model.ExceptionFromProto(req.Exception)) - return ex.ToProto(), err -} -func (modron *modronService) DeleteNotificationException(ctx context.Context, req *pb.DeleteNotificationExceptionRequest) (*emptypb.Empty, error) { - if _, err := modron.validateUserAndGetException(ctx, req.Uuid); err != nil { - return nil, err - } - return &emptypb.Empty{}, modron.notificationSvc.DeleteException(ctx, req.Uuid) -} - -func (modron *modronService) ListNotificationExceptions(ctx context.Context, req *pb.ListNotificationExceptionsRequest) (*pb.ListNotificationExceptionsResponse, error) { - if userEmail, err := modron.checker.GetValidatedUser(ctx); err != nil { - return nil, status.Error(codes.Unauthenticated, "failed authenticating request") - } else { - req.UserEmail = userEmail - } - ex, err := modron.notificationSvc.ListExceptions(ctx, req.UserEmail, req.PageSize, req.PageToken) - exList := []*pb.NotificationException{} - for _, e := range ex { - exList = append(exList, e.ToProto()) + labelToEmailRegexp, err := regexp.Compile(args.LabelToEmailRegexp) + if err != nil { + return nil, fmt.Errorf("regexp.Compile failed for contact label regex: %w", err) + } + + return service.New( + checker, + args.CollectAndScanInterval, + coll, + args.NotificationInterval, + notificationSvc, + args.OrgSuffix, + ruleEngine, + args.SelfURL, + stateManager, + st, + additionalAdminRoles, + labelToEmailRegexp, + args.LabelToEmailSubst, + ) +} + +func parseRuleConfigs(configs string) (map[string]json.RawMessage, error) { + var ruleConfigs map[string]json.RawMessage + if err := json.Unmarshal([]byte(configs), &ruleConfigs); err != nil { + return nil, fmt.Errorf("unable to decode rule configs: %w", err) + } + return ruleConfigs, nil +} + +func getImpactMap(impactMapJSON string) (map[string]pb.Impact, error) { + var impactMap map[string]string + if err := json.Unmarshal([]byte(impactMapJSON), &impactMap); err != nil { + return nil, fmt.Errorf("unable to decode impact map: %w", err) + } + finalImpactMap := map[string]pb.Impact{} + for k, v := range impactMap { + impactValue, ok := pb.Impact_value[v] + if !ok { + return nil, fmt.Errorf("invalid impact value: %s", v) + } + finalImpactMap[k] = pb.Impact(impactValue) } - return &pb.ListNotificationExceptionsResponse{Exceptions: exList}, err + return finalImpactMap, nil } -// TODO: Allow security admins to bypass the checks -func (modron *modronService) validateUserAndGetException(ctx context.Context, notificationUuid string) (model.Exception, error) { - userEmail, err := modron.checker.GetValidatedUser(ctx) - if err != nil { - return model.Exception{}, status.Error(codes.Unauthenticated, "failed authenticating request") - } - if ex, err := modron.notificationSvc.GetException(ctx, notificationUuid); err != nil { - return model.Exception{}, err - } else if ex.UserEmail != userEmail { - return model.Exception{}, status.Error(codes.Unauthenticated, "failed authenticating request") +func setupNotificationService(ctx context.Context, notSvcAddr string) (notSvc model.NotificationService, err error) { + log.Tracef("Using Nagatha as notification service") + var tokenSource oauth2.TokenSource + if args.IsE2EGrpcTest { + tokenSource = oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: "e2e-test-token", + }) } else { - return ex, nil + tokenSource, err = idtoken.NewTokenSource(ctx, args.NotificationServiceClientID) + if err != nil { + return nil, fmt.Errorf("idtoken.NewTokenSource: %w", err) + } } -} - -func (modron *modronService) notificationsFromObservation(ctx context.Context, ob *pb.Observation) ([]model.Notification, error) { - rg, err := modron.storage.ListResources(ctx, model.StorageFilter{ResourceNames: []string{ob.Resource.ResourceGroupName}, Limit: 1}) + notSvc, err = nagatha.New(notSvcAddr, args.SelfURL, tokenSource) if err != nil { - return nil, err - } - if len(rg) < 1 { - return nil, fmt.Errorf("no resource found %+v", ob.Resource.Uid) - } - if len(rg) > 1 { - glog.Warningf("multiple resources group found for %+v, using the first one", ob.Resource.Uid) - } - if rg[0].IamPolicy == nil { - glog.Warningf("no iam policy found for %s", rg[0].Name) - } - // We can have the same contacts in owners and labels, de-duplicate. - uniqueContacts := make(map[string]struct{}, 0) - for _, b := range rg[0].IamPolicy.Permissions { - for r := range constants.AdminRoles { - if strings.EqualFold(b.Role, r) { - for _, m := range b.Principals { - if strings.HasSuffix(m, orgSuffix) { - uniqueContacts[strings.TrimPrefix(m, constants.GCPUserAccountPrefix)] = struct{}{} - } - } - } + return nil, fmt.Errorf("nagatha service: %w", err) + } + return notSvc, nil +} + +func getCollectorAndChecker(ctx context.Context, st model.Storage, tagConfig risk.TagConfig) (c model.Collector, aclChecker model.Checker, err error) { + switch collector.Type(strings.ToLower(string(args.Collector))) { + case collector.Fake: + log.Warnf("Using fake collector") + c = gcpcollector.NewFake(ctx, st, tagConfig) + log.Warnf("Using fake ACL") + aclChecker = fakeacl.New() + case collector.Gcp: + log.Tracef("Creating GCP collector") + c, err = gcpcollector.New( + ctx, + st, + args.OrgID, + args.OrgSuffix, + args.AdditionalAdminRoles, + tagConfig, + args.AllowedSccCategories, + ) + if err != nil { + return nil, nil, fmt.Errorf("NewGCPCollector: %w", err) + } + log.Tracef("Creating GCP ACL checker") + if aclChecker, err = gcpacl.New(ctx, c, gcpacl.Config{ + AdminGroups: args.AdminGroups, + CacheTimeout: defaultCacheTimeout, + PersistentCache: args.PersistentCache, + PersistentCacheTimeout: args.PersistentCacheTimeout, + SkipIap: args.SkipIAP, + }); err != nil { + return nil, nil, fmt.Errorf("NewGcpChecker: %w", err) } + default: + return nil, nil, fmt.Errorf("invalid collector \"%s\" specified, use one of: %s", + args.Collector, strings.Join(collector.ValidCollectors(), ", ")) } + return +} - contacts := maps.Keys(uniqueContacts) - if len(contacts) < 1 { - return nil, fmt.Errorf("no contacts found for observation %q, resource group: %q", ob.Uid, ob.Resource.ResourceGroupName) - } - notifications := make([]model.Notification, 0) - for _, c := range contacts { - notifications = append(notifications, - model.Notification{ - SourceSystem: "modron", - Name: ob.Name, - Recipient: c, - Content: ob.Remediation.Recommendation, - Interval: notificationInterval, - }) +func withCors() []grpcweb.Option { + return []grpcweb.Option{ + grpcweb.WithOriginFunc(func(_ string) bool { + return true + }), + grpcweb.WithAllowedRequestHeaders([]string{"*"}), } - return notifications, nil } diff --git a/src/service/mock_notifier_test.go b/src/service/mock_notifier_test.go new file mode 100644 index 0000000..b092469 --- /dev/null +++ b/src/service/mock_notifier_test.go @@ -0,0 +1,70 @@ +package service_test + +import ( + "context" + "errors" + "sync" + + "github.com/nianticlabs/modron/src/model" +) + +type mockNotifier struct { + notificationLock sync.Mutex + notifications []model.Notification +} + +func (m *mockNotifier) BatchCreateNotifications(ctx context.Context, notifications []model.Notification) ([]model.Notification, error) { + var createdNotifications []model.Notification + var errArr []error + for _, n := range notifications { + created, err := m.CreateNotification(ctx, n) + if err != nil { + errArr = append(errArr, err) + continue + } + createdNotifications = append(createdNotifications, created) + } + return createdNotifications, errors.Join(errArr...) +} + +func (m *mockNotifier) CreateNotification(_ context.Context, notification model.Notification) (model.Notification, error) { + m.notificationLock.Lock() + defer m.notificationLock.Unlock() + m.notifications = append(m.notifications, notification) + return notification, nil +} + +func (m *mockNotifier) GetException(context.Context, string) (model.Exception, error) { + panic("implement me") +} + +func (m *mockNotifier) CreateException(context.Context, model.Exception) (model.Exception, error) { + panic("implement me") +} + +func (m *mockNotifier) UpdateException(context.Context, model.Exception) (model.Exception, error) { + panic("implement me") +} + +func (m *mockNotifier) DeleteException(context.Context, string) error { + panic("implement me") +} + +func (m *mockNotifier) ListExceptions(context.Context, string, int32, string) ([]model.Exception, error) { + panic("implement me") +} + +var _ model.NotificationService = (*mockNotifier)(nil) + +func newMockNotifier() *mockNotifier { + return &mockNotifier{} +} + +func (m *mockNotifier) getNotifications() []model.Notification { + // Clone the notifications + m.notificationLock.Lock() + defer m.notificationLock.Unlock() + notifications := make([]model.Notification, len(m.notifications)) + copy(notifications, m.notifications) + return notifications +} diff --git a/src/service/service.go b/src/service/service.go new file mode 100644 index 0000000..8cb2323 --- /dev/null +++ b/src/service/service.go @@ -0,0 +1,694 @@ +package service + +import ( + "context" + "errors" + "fmt" + "regexp" + "sort" + "strings" + "time" + + "github.com/google/uuid" + "github.com/sirupsen/logrus" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + otelcodes "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" + "golang.org/x/exp/maps" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/engine" + "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/nagatha" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" +) + +var ( + tracer = otel.Tracer("github.com/nianticlabs/modron/src/service") + meter = otel.Meter("github.com/nianticlabs/modron/src/service") +) + +// TODO: Implement paginated API +type Modron struct { + Checker model.Checker + CollectAndScanInterval time.Duration + Collector model.Collector + NotificationInterval time.Duration + NotificationSvc model.NotificationService + OrgSuffix string + RuleEngine model.Engine + SelfURL string + StateManager model.StateManager + Storage model.Storage + + additionalAdminRolesMap map[constants.Role]struct{} + labelToEmailRegexp *regexp.Regexp + labelToEmailSubst string + + metrics metrics + // Required + pb.UnimplementedModronServiceServer + pb.UnimplementedNotificationServiceServer +} + +var ( + log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "service") +) + +type metrics struct { + CollectDuration metric.Float64Histogram + Observations metric.Int64Counter + ScanDuration metric.Float64Histogram +} + +const ( + oneDay = time.Hour * 24 + oneWeek = oneDay * 7 +) + +func (modron *Modron) validateResourceGroupNames(ctx context.Context, resourceGroupNames []string) ([]string, error) { + ownedResourceGroups, err := modron.Checker.ListResourceGroupNamesOwned(ctx) + if err != nil { + log.Warnf("validate resource groups: %v", err) + return nil, status.Error(codes.Unauthenticated, "failed authenticating request") + } + if len(resourceGroupNames) == 0 { + for k := range ownedResourceGroups { + resourceGroupNames = append(resourceGroupNames, k) + } + } else { + for _, rsgName := range resourceGroupNames { + if _, ok := ownedResourceGroups[rsgName]; !ok { + return nil, status.Error(codes.PermissionDenied, "resource group(s) is inaccessible") + } + } + } + return resourceGroupNames, nil +} + +// preCollect retrieves and stores all the Resource Groups (projects, folders, org) _without their IAM policies_. +// We don't collect the IAM policies because we only need those of the RGs that we need to analyze, +// but we need the entire set of RGs to perform cross-environment checks +func (modron *Modron) preCollect(ctx context.Context, resourceGroupNames []string) ([]*pb.Resource, error) { + ctx, span := tracer.Start(ctx, "preCollect") + defer span.End() + collectID, ok := ctx.Value(constants.CollectIDKey).(string) + if !ok { + return nil, fmt.Errorf("collectID not found in context") + } + pcLog := log. + WithField("collect_id", collectID). + WithField("resource_group_names", resourceGroupNames) + pcLog.Info("starting pre-collect") + defer pcLog.Info("pre-collect done") + + rgs, err := modron.Collector.ListResourceGroups(ctx, nil) + if err != nil { + return nil, fmt.Errorf("list resource groups: %w", err) + } + for i := range rgs { + rgs[i].CollectionUid = collectID + } + return rgs, nil +} + +func (modron *Modron) collect(ctx context.Context, resourceGroupNames []string, preCollectedRgs []*pb.Resource) []*pb.Observation { + start := time.Now() + collectID, ok := ctx.Value(constants.CollectIDKey).(string) + if !ok { + log.Errorf("collectID not found in context") + return nil + } + ctx, span := tracer.Start(ctx, "collect", + trace.WithAttributes( + attribute.String(constants.TraceKeyCollectID, collectID), + attribute.StringSlice(constants.TraceKeyResourceGroupNames, resourceGroupNames), + ), + ) + defer span.End() + collectLogger := log. + WithField("collect_id", collectID). + WithField("resource_group_names", resourceGroupNames) + collectLogger.Debug("request collection") + filteredGroups := modron.StateManager.AddCollect(collectID, resourceGroupNames) + collectLogger = collectLogger.WithField("filtered_groups", filteredGroups) + collectLogger.Debugf("filtered collection") + if len(filteredGroups) > 0 { + collectLogger.Infof("collect start") + if err := modron.Collector.CollectAndStoreAll(ctx, collectID, filteredGroups, preCollectedRgs); err != nil { + collectLogger. + WithError(err). + Warnf("some errors during collect: %v", err) + } else { + collectLogger.Info("collect done") + } + } + modron.StateManager.EndCollect(collectID, filteredGroups) + modron.metrics.CollectDuration.Record(ctx, time.Since(start).Seconds()) + + // Create notifications for collected observations + collectedObs, err := modron.getCollectedObservations(ctx, collectID, filteredGroups) + if err != nil { + collectLogger. + WithError(err). + Warnf("get collected observations: %v", err) + return nil + } + return collectedObs +} + +func (modron *Modron) scan(ctx context.Context, resourceGroupNames []string, preCollectedRgs []*pb.Resource) []*pb.Observation { + start := time.Now() + scanID, ok := ctx.Value(constants.ScanIDKey).(string) + if !ok { + log.Errorf("scanID not found in context") + return nil + } + ctx, span := tracer.Start(ctx, "scan", + trace.WithAttributes( + attribute.String(constants.TraceKeyScanID, scanID), + attribute.StringSlice(constants.TraceKeyResourceGroupNames, resourceGroupNames), + ), + ) + defer span.End() + collectID, ok := ctx.Value(constants.CollectIDKey).(string) + if !ok { + log.Errorf("collectID not found in context") + return nil + } + scanLogger := log. + WithField("collect_id", collectID). + WithField("scan_id", scanID). + WithField("resource_group_names", resourceGroupNames) + scanLogger.Debugf("requested scan") + filteredGroups := modron.StateManager.AddScan(scanID, resourceGroupNames) + scanLogger = scanLogger.WithField("filtered_groups", filteredGroups) + scanLogger.Debug("filtered scan") + if len(filteredGroups) < 1 { + scanLogger.Warnf("no groups to scan, aborting") + return nil + } + scanLogger.Info("starting scan") + obs, errs := modron.RuleEngine.CheckRules(ctx, scanID, collectID, filteredGroups, preCollectedRgs) + tookSeconds := time.Since(start).Seconds() + scanLogger = scanLogger. + WithFields(logrus.Fields{ + "observations": len(obs), + "took": tookSeconds, + }) + if len(errs) > 0 { + scanLogger. + WithError(errors.Join(errs...)). + Errorf("scan completed with errors: %v", errors.Join(errs...)) + } + scanLogger.Debug("ending scan") + modron.StateManager.EndScan(scanID, filteredGroups) + scanLogger.Info("scan completed") + modron.metrics.ScanDuration.Record(ctx, tookSeconds) + if err := modron.Storage.FlushOpsLog(ctx); err != nil { + scanLogger.WithError(err). + Warnf("flush ops log: %v", err) + } + modron.metrics.Observations.Add(ctx, int64(len(obs))) + if len(obs) < 1 { + scanLogger.Warnf("scan returned no observations") + } + return obs +} + +func (modron *Modron) createNotifications(ctx context.Context, obs []*pb.Observation) { + ctx, span := tracer.Start(ctx, "createNotifications") + defer span.End() + var allNotifications []model.Notification + for _, o := range obs { + ctx, span := tracer.Start(ctx, "createNotificationFromObservation", + trace.WithAttributes( + attribute.String(constants.TraceKeyObservationUID, o.Uid), + ), + ) + notifications, err := modron.notificationsFromObservation(ctx, o) + if err != nil { + log.Warnf("notifications from observation: %v", err) + continue + } + allNotifications = append(allNotifications, notifications...) + span.End() + } + span.SetAttributes( + attribute.Int(constants.TraceKeyNumNotifications, len(allNotifications)), + ) + log.Infof("Creating %d notifications in batch", len(allNotifications)) + _, err := modron.NotificationSvc.BatchCreateNotifications(ctx, allNotifications) + if err != nil { + log.Warnf("notifications: %v", err) + span.RecordError(err) + } +} + +func (modron *Modron) collectAndScan(ctx context.Context, rgs []string, scanType pb.ScanType) (*pb.CollectAndScanResponse, error) { + ctx = context.WithoutCancel(ctx) + collectID, scanID := uuid.NewString(), uuid.NewString() + ctx = context.WithValue(ctx, constants.CollectIDKey, collectID) + ctx = context.WithValue(ctx, constants.ScanIDKey, scanID) + ctx, span := tracer.Start(ctx, "collectAndScan", + trace.WithAttributes( + attribute.String(constants.TraceKeyCollectID, collectID), + attribute.String(constants.TraceKeyScanID, scanID), + attribute.String(constants.TraceKeyScanType, scanType.String()), + attribute.StringSlice(constants.TraceKeyResourceGroupNames, rgs), + ), + ) + defer span.End() + + switch scanType { + case pb.ScanType_SCAN_TYPE_PARTIAL: + // We use rgs + case pb.ScanType_SCAN_TYPE_FULL: + var err error + if rgs, err = modron.Collector.ListResourceGroupNames(ctx); err != nil { + return nil, fmt.Errorf("list resource groups: %w", err) + } + } + + obsChan := make(chan []*pb.Observation) + go func(resourceGroupNames []string) { + ctx, span := tracer.Start(ctx, "collectAndScanAsync", trace.WithNewRoot()) + defer span.End() + + preCollectedRgs, err := modron.preCollect(ctx, resourceGroupNames) + if err != nil { + log.Errorf("pre-collect failed: %v", err) + modron.StateManager.EndCollect(collectID, resourceGroupNames) + modron.StateManager.EndScan(scanID, resourceGroupNames) + span.SetStatus(otelcodes.Error, err.Error()) + span.End() + return + } + + obsChan <- modron.collect(ctx, resourceGroupNames, preCollectedRgs) + obsChan <- modron.scan(ctx, resourceGroupNames, preCollectedRgs) + close(obsChan) + }(rgs) + go func() { + // Process observations / notifications + for v := range obsChan { + if v == nil { + continue + } + ctx, span := tracer.Start(ctx, "processObservations", + trace.WithNewRoot(), + trace.WithLinks(trace.LinkFromContext(ctx)), + trace.WithAttributes( + attribute.Int(constants.TraceKeyNumObservations, len(v)), + ), + ) + modron.createNotifications(ctx, v) + span.End() + } + }() + return &pb.CollectAndScanResponse{ + CollectId: collectID, + ScanId: scanID, + }, nil +} + +// getCollectedObservations retrieves all the observations collected during the collection phase +// so that we can use them to create notifications +func (modron *Modron) getCollectedObservations(ctx context.Context, collectID string, groups []string) ([]*pb.Observation, error) { + obs, err := modron.Storage.ListObservations(ctx, model.StorageFilter{ + OperationID: collectID, + ResourceGroupNames: groups, + }) + if err != nil { + return nil, fmt.Errorf("list observations: %w", err) + } + return obs, nil +} + +func (modron *Modron) CollectAndScan(ctx context.Context, req *pb.CollectAndScanRequest) (*pb.CollectAndScanResponse, error) { + return modron.collectAndScan(ctx, req.ResourceGroupNames, pb.ScanType_SCAN_TYPE_PARTIAL) +} + +func (modron *Modron) CollectAndScanAll(ctx context.Context, _ *pb.CollectAndScanAllRequest) (*pb.CollectAndScanResponse, error) { + return modron.collectAndScan(ctx, nil, pb.ScanType_SCAN_TYPE_FULL) +} + +func (modron *Modron) ScheduledRunner(ctx context.Context) { + interval := modron.CollectAndScanInterval + log.Tracef("starting scheduler with interval %v", interval) + for { + ctx, span := tracer.Start(ctx, "ScheduledRunner", trace.WithNewRoot()) + log.Infof("scan scheduler: starting") + if ctx.Err() != nil { + log.Errorf("scan scheduler: %v", ctx.Err()) + return + } + r, err := modron.collectAndScan(ctx, nil, pb.ScanType_SCAN_TYPE_FULL) + if err != nil { + log.Errorf("scan scheduler: %v", err) + } + log.Infof("scan scheduler done: collectionID: %s, scanID: %s", r.CollectId, r.ScanId) + span.End() + time.Sleep(interval) + } +} + +func (modron *Modron) ListObservations(ctx context.Context, in *pb.ListObservationsRequest) (*pb.ListObservationsResponse, error) { + groups, err := modron.validateResourceGroupNames(ctx, in.ResourceGroupNames) + if err != nil { + return nil, err + } + obsByGroupByRules := map[string]map[string][]*pb.Observation{} + oneWeekAgo := time.Now().Add(-oneWeek) + obs, err := modron.Storage.ListObservations(ctx, model.StorageFilter{ + ResourceGroupNames: groups, + StartTime: oneWeekAgo, + TimeOffset: time.Since(oneWeekAgo), + }) + if err != nil { + log.Warnf("list observations: %v", err) + return nil, status.Error(codes.Internal, "failed listing observations") + } + for _, group := range groups { + obsByGroupByRules[group] = map[string][]*pb.Observation{} + for _, rule := range modron.RuleEngine.GetRules() { + obsByGroupByRules[group][rule.Info().Name] = []*pb.Observation{} + } + } + for _, ob := range obs { + group := ob.ResourceRef.GroupName + // TODO: Remove in the future when all of our observations do not use this field anymore: + ob.DeprecatedResource = nil + rule := ob.Name + if obsByGroupByRules[group] == nil { + obsByGroupByRules[group] = map[string][]*pb.Observation{} + } + obsByGroupByRules[group][rule] = append( + obsByGroupByRules[group][rule], + ob, + ) + } + var res []*pb.ResourceGroupObservationsPair + keys := maps.Keys(obsByGroupByRules) + sort.Strings(keys) + for _, name := range keys { + ruleObs := obsByGroupByRules[name] + var val []*pb.RuleObservationPair + ruleObsKeys := maps.Keys(ruleObs) + sort.Strings(ruleObsKeys) + for _, key := range ruleObsKeys { + obs := ruleObs[key] + val = append(val, &pb.RuleObservationPair{ + Rule: key, + Observations: obs, + }) + } + res = append(res, &pb.ResourceGroupObservationsPair{ + ResourceGroupName: name, + RulesObservations: val, + }) + } + return &pb.ListObservationsResponse{ + ResourceGroupsObservations: res, + }, nil +} + +func (modron *Modron) CreateObservation(ctx context.Context, in *pb.CreateObservationRequest) (*pb.Observation, error) { + if in.Observation == nil { + return nil, status.Error(codes.InvalidArgument, "observation is nil") + } + if in.Observation.Name == "" { + return nil, status.Error(codes.InvalidArgument, "observation does not have a name") + } + if in.Observation.ResourceRef == nil { + return nil, status.Error(codes.InvalidArgument, "resource to link observation with not defined") + } + if in.Observation.Remediation == nil || in.Observation.Remediation.Recommendation == "" { + return nil, status.Error(codes.InvalidArgument, "cannot create an observation without recommendation") + } + in.Observation.Timestamp = timestamppb.Now() + externalID := "" + if in.Observation.ResourceRef.ExternalId != nil { + externalID = *in.Observation.ResourceRef.ExternalId + } + res, err := modron.Storage.ListResources(ctx, model.StorageFilter{ResourceNames: []string{externalID}}) + if err != nil { + return nil, status.Errorf(codes.NotFound, "resource to link observation to not found: %v", err) + } + if len(res) != 1 { + return nil, status.Errorf(codes.FailedPrecondition, "found %d resources matching %+v", len(res), in.Observation.ResourceRef) + } + in.Observation.ResourceRef = utils.GetResourceRef(res[0]) + in.Observation.Uid = uuid.NewString() + obs, err := modron.Storage.BatchCreateObservations(ctx, []*pb.Observation{in.Observation}) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + if len(obs) != 1 { + return nil, status.Errorf(codes.Internal, "creation returned %d items", len(obs)) + } + return obs[0], nil +} + +func (modron *Modron) GetStatusCollectAndScan(_ context.Context, in *pb.GetStatusCollectAndScanRequest) (*pb.GetStatusCollectAndScanResponse, error) { + collectStatus := modron.StateManager.GetCollectState(in.CollectId) + scanStatus := modron.StateManager.GetScanState(in.ScanId) + return &pb.GetStatusCollectAndScanResponse{ + CollectStatus: collectStatus, + ScanStatus: scanStatus, + }, nil +} + +func (modron *Modron) GetNotificationException(ctx context.Context, req *pb.GetNotificationExceptionRequest) (*pb.NotificationException, error) { + ex, err := modron.validateUserAndGetException(ctx, req.Uuid) + if err != nil { + return nil, err + } + return ex.ToProto(), err +} + +func (modron *Modron) CreateNotificationException(ctx context.Context, req *pb.CreateNotificationExceptionRequest) (*pb.NotificationException, error) { + userEmail, err := modron.Checker.GetValidatedUser(ctx) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "failed authenticating request") + } + req.Exception.UserEmail = userEmail + ex, err := modron.NotificationSvc.CreateException(ctx, model.ExceptionFromProto(req.Exception)) + if err != nil { + return nil, err + } + return ex.ToProto(), err +} + +func (modron *Modron) UpdateNotificationException(ctx context.Context, req *pb.UpdateNotificationExceptionRequest) (*pb.NotificationException, error) { + ex, err := modron.validateUserAndGetException(ctx, req.Exception.Uuid) + if err != nil { + return nil, err + } + req.Exception.UserEmail = ex.UserEmail + ex, err = modron.NotificationSvc.UpdateException(ctx, model.ExceptionFromProto(req.Exception)) + if err != nil { + return nil, err + } + return ex.ToProto(), err +} + +func (modron *Modron) DeleteNotificationException(ctx context.Context, req *pb.DeleteNotificationExceptionRequest) (*emptypb.Empty, error) { + if _, err := modron.validateUserAndGetException(ctx, req.Uuid); err != nil { + return nil, err + } + return &emptypb.Empty{}, modron.NotificationSvc.DeleteException(ctx, req.Uuid) +} + +func (modron *Modron) ListNotificationExceptions(ctx context.Context, req *pb.ListNotificationExceptionsRequest) (*pb.ListNotificationExceptionsResponse, error) { + userEmail, err := modron.Checker.GetValidatedUser(ctx) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "failed authenticating request") + } + req.UserEmail = userEmail + ex, err := modron.NotificationSvc.ListExceptions(ctx, req.UserEmail, req.PageSize, req.PageToken) + var exList []*pb.NotificationException + for _, e := range ex { + exList = append(exList, e.ToProto()) + } + return &pb.ListNotificationExceptionsResponse{Exceptions: exList}, err +} + +// TODO: Allow security admins to bypass the checks +func (modron *Modron) validateUserAndGetException(ctx context.Context, notificationUUID string) (model.Exception, error) { + userEmail, err := modron.Checker.GetValidatedUser(ctx) + if err != nil { + return model.Exception{}, status.Error(codes.Unauthenticated, "failed authenticating request") + } + ex, err := modron.NotificationSvc.GetException(ctx, notificationUUID) + if err != nil { + return model.Exception{}, err + } + if ex.UserEmail != userEmail { + return model.Exception{}, status.Error(codes.Unauthenticated, "failed authenticating request") + } + return ex, nil +} + +func (modron *Modron) notificationsFromObservation(ctx context.Context, ob *pb.Observation) ([]model.Notification, error) { + log := log.WithFields(logrus.Fields{ + constants.LogKeyResourceGroup: ob.ResourceRef.GroupName, + constants.LogKeyObservationUID: ob.Uid, + constants.LogKeyObservationName: ob.Name, + }) + ty, err := utils.TypeFromResource(&pb.Resource{Type: &pb.Resource_ResourceGroup{}}) + if err != nil { + return nil, fmt.Errorf("type from resource: %w", err) + } + collectionID, ok := ctx.Value(constants.CollectIDKey).(string) + if !ok { + return nil, fmt.Errorf("collectID not found in context") + } + rg, err := modron.Storage.ListResources(ctx, model.StorageFilter{ + ResourceNames: []string{ob.ResourceRef.GroupName}, + OperationID: collectionID, + ResourceTypes: []string{ty}, + Limit: 1, + }) + if err != nil { + return nil, err + } + if len(rg) < 1 { + log.Errorf("no resource found") + return nil, fmt.Errorf("no resource found %+v", ob.ResourceRef.Uid) + } + if len(rg) > 1 { + log.Warnf("multiple resources group found for %+v, using the first one", ob.ResourceRef.Uid) + } + // We can have the same contacts in owners and labels, de-duplicate. + contacts := modron.contactsFromRG(rg[0]) + if len(contacts) < 1 { + log.Errorf("no contacts found for observation") + return nil, fmt.Errorf("no contacts found for observation %q, resource group: %q", ob.Uid, ob.ResourceRef.GroupName) + } + notifications := make([]model.Notification, 0) + for _, c := range contacts { + if c == "" { + log.Warnf("empty contact") + continue + } + notifications = append(notifications, + nagatha.NotificationFromObservation(c, modron.NotificationInterval, ob), + ) + } + return notifications, nil +} + +func (modron *Modron) contactsFromRG(rg *pb.Resource) []string { + uniqueContacts := make(map[string]struct{}) + if rg.IamPolicy != nil { + for _, b := range rg.IamPolicy.Permissions { + theRole := constants.ToRole(b.Role) + _, isAdminRole := constants.AdminRoles[theRole] + _, isAdditionalAdminRole := modron.additionalAdminRolesMap[theRole] + if isAdminRole || isAdditionalAdminRole { + for _, m := range b.Principals { + if strings.HasSuffix(m, modron.OrgSuffix) { + uniqueContacts[strings.TrimPrefix(m, constants.GCPUserAccountPrefix)] = struct{}{} + } + } + } + } + } + log.Debugf("contacts from IAM policy: %v", uniqueContacts) + + contact1, ok := rg.Labels[constants.LabelContact1] + if ok && contact1 != "" { + uniqueContacts[modron.LabelToEmail(contact1)] = struct{}{} + } + contact2, ok := rg.Labels[constants.LabelContact2] + if ok && contact2 != "" { + uniqueContacts[modron.LabelToEmail(contact2)] = struct{}{} + } + + contacts := maps.Keys(uniqueContacts) + return contacts +} + +// LabelToEmail converts a contact1,contact2 label into an email address +// these labels are formatted as firstname_lastname_example_com, which is the representation of +// firstname.lastname@example.com. +// Due to the _ replacement, we do not support emails like noreply_test@example.com. +func (modron *Modron) LabelToEmail(contact string) string { + contact = modron.labelToEmailRegexp.ReplaceAllString(contact, modron.labelToEmailSubst) + contact = strings.ReplaceAll(contact, "_", ".") + return contact +} + +func (modron *Modron) initMetrics() error { + collectDurationHist, err := meter.Float64Histogram( + constants.MetricsPrefix+"collections_duration", + metric.WithDescription("Duration of the collection process"), + metric.WithUnit("s"), + ) + if err != nil { + return err + } + observationsCounter, err := meter.Int64Counter( + constants.MetricsPrefix+"observations_total", + metric.WithDescription("Total number of observations created"), + ) + if err != nil { + return err + } + scanDurationHist, err := meter.Float64Histogram( + constants.MetricsPrefix+"scan_duration", + metric.WithDescription("Duration of the scan process"), + metric.WithUnit("s"), + ) + if err != nil { + return err + } + modron.metrics = metrics{ + CollectDuration: collectDurationHist, + Observations: observationsCounter, + ScanDuration: scanDurationHist, + } + return err +} + +func New( + checker model.Checker, + collectAndScanInterval time.Duration, + coll model.Collector, + notificationInterval time.Duration, + svc model.NotificationService, + suffix string, + engine *engine.RuleEngine, + url string, + manager model.StateManager, + st model.Storage, + additionalAdminRoles map[constants.Role]struct{}, + labelToEmailRegexp *regexp.Regexp, + labelToEmailSubst string, +) (*Modron, error) { + s := Modron{ + Checker: checker, + CollectAndScanInterval: collectAndScanInterval, + Collector: coll, + NotificationInterval: notificationInterval, + NotificationSvc: svc, + OrgSuffix: suffix, + RuleEngine: engine, + SelfURL: url, + StateManager: manager, + Storage: st, + additionalAdminRolesMap: additionalAdminRoles, + labelToEmailRegexp: labelToEmailRegexp, + labelToEmailSubst: labelToEmailSubst, + } + err := s.initMetrics() + return &s, err +} diff --git a/src/service/service_test.go b/src/service/service_test.go new file mode 100644 index 0000000..fe7a503 --- /dev/null +++ b/src/service/service_test.go @@ -0,0 +1,527 @@ +package service_test + +import ( + "context" + "encoding/json" + "regexp" + "sort" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/sirupsen/logrus" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" + + "github.com/nianticlabs/modron/src/acl/fakeacl" + "github.com/nianticlabs/modron/src/collector/gcpcollector" + "github.com/nianticlabs/modron/src/engine" + "github.com/nianticlabs/modron/src/engine/rules" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/risk" + "github.com/nianticlabs/modron/src/service" + "github.com/nianticlabs/modron/src/statemanager/reqdepstatemanager" + "github.com/nianticlabs/modron/src/storage/memstorage" +) + +const ( + bucketPublicRemediationDesc = "Bucket [\"bucket-public\"](https://console.cloud.google.com/storage/browser/bucket-public) is publicly accessible" + bucketPublicRemediationRecom = "Unless strictly needed, restrict the IAM policy of bucket [\"bucket-public\"](https://console.cloud.google.com/storage/browser/bucket-public) to prevent unconditional access by anyone. For more details, see [here](https://cloud.google.com/storage/docs/using-public-access-prevention)" + + bucketPublicAllUsersRemediationDesc = "Bucket [\"bucket-public-allusers\"](https://console.cloud.google.com/storage/browser/bucket-public-allusers) is publicly accessible" + bucketPublicAllUsersRemediationRecom string = "Unless strictly needed, restrict the IAM policy of bucket [\"bucket-public-allusers\"](https://console.cloud.google.com/storage/browser/bucket-public-allusers) to prevent unconditional access by anyone. For more details, see [here](https://cloud.google.com/storage/docs/using-public-access-prevention)" + + sqlRemediationDesc = "To lower your attack surface, Cloud SQL databases should not have public IPs. Private IPs provide improved network security and lower latency for your application." + sqlRemediationRecom = "Go to https://console.cloud.google.com/sql/instances/xyz/connections?project=project-id and click the \"Networking\" tab. Uncheck the \"Public IP\" checkbox and click \"SAVE\". If your instance is not configured to use a private IP, you will first have to enable private IP by following the instructions here: https://cloud.google.com/sql/docs/mysql/configure-private-ip#existing-private-instance" +) + +var impactMap = map[string]pb.Impact{ + "prod": pb.Impact_IMPACT_HIGH, + "pre-prod": pb.Impact_IMPACT_MEDIUM, + "dev": pb.Impact_IMPACT_LOW, + "playground": pb.Impact_IMPACT_LOW, +} + +func getService(ctx context.Context, t *testing.T) (*service.Modron, *mockNotifier) { + t.Helper() + st := memstorage.New() + engineRules := []model.Rule{ + rules.NewBucketIsPublicRule(), + } + notifier := newMockNotifier() + sm, err := reqdepstatemanager.New() + tagConfig := risk.TagConfig{ + ImpactMap: impactMap, + Environment: "111111111111/environment", + EmployeeData: "111111111111/employee_data", + CustomerData: "111111111111/customer_data", + } + if err != nil { + t.Fatalf("reqdepstatemanager.New: %v", err) + } + e, err := engine.New(st, engineRules, map[string]json.RawMessage{}, nil, tagConfig) + if err != nil { + t.Fatalf("engine.New: %v", err) + } + svc, err := service.New( + fakeacl.New(), + 10*time.Second, + gcpcollector.NewFake(ctx, st, tagConfig), + 24*time.Hour, + notifier, + "example.com", + e, + "https://modron.example.com", + sm, + st, + nil, + regexp.MustCompile("(.*)_(.*?)_(.*?)$"), + "$1@$2.$3", + ) + if err != nil { + t.Fatalf("service.New: %v", err) + } + return svc, notifier +} + +func TestService_CollectAndScan(t *testing.T) { + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + logrus.StandardLogger().SetFormatter(&logrus.TextFormatter{ForceColors: true}) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + svc, notifier := getService(ctx, t) + res, err := svc.CollectAndScan(ctx, &pb.CollectAndScanRequest{ + ResourceGroupNames: []string{"projects/modron-test"}, + }) + if err != nil { + t.Fatalf("CollectAndScan: %v", err) + } + + scanID := res.ScanId + if scanID == "" { + t.Fatalf("empty scan ID") + } + collectID := res.CollectId + if collectID == "" { + t.Fatalf("empty collect ID") + } + + // Wait for the scan to complete + tick := time.NewTicker(1 * time.Second) + for { + done := false + select { + case <-ctx.Done(): + t.Fatalf("timeout waiting for scan to complete") + case <-tick.C: + status, err := svc.GetStatusCollectAndScan(ctx, &pb.GetStatusCollectAndScanRequest{ + CollectId: collectID, + ScanId: scanID, + }) + if err != nil { + t.Fatalf("GetStatusCollectAndScan: %v", err) + } + if status.CollectStatus == pb.RequestStatus_DONE && status.ScanStatus == pb.RequestStatus_DONE { + t.Log("Collect and scan completed") + done = true + break + } else { + t.Logf("collect=%s, scan=%s", status.CollectStatus.String(), status.ScanStatus.String()) + } + } + if done { + break + } + } + + // Done + obs, err := svc.ListObservations(ctx, &pb.ListObservationsRequest{}) + if err != nil { + t.Fatalf("ListObservations: %v", err) + } + got := obs.ResourceGroupsObservations + want := []*pb.ResourceGroupObservationsPair{ + { + ResourceGroupName: "projects/modron-test", + RulesObservations: []*pb.RuleObservationPair{ + { + Rule: "BUCKET_IS_PUBLIC", + Observations: []*pb.Observation{ + { + Name: "BUCKET_IS_PUBLIC", + Remediation: &pb.Remediation{ + Description: bucketPublicRemediationDesc, + Recommendation: bucketPublicRemediationRecom, + }, + ObservedValue: structpb.NewStringValue("PUBLIC"), + ExpectedValue: structpb.NewStringValue("PRIVATE"), + ResourceRef: &pb.ResourceRef{ + GroupName: "projects/modron-test", + ExternalId: proto.String("bucket-public"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + Source: pb.Observation_SOURCE_MODRON, + Severity: pb.Severity_SEVERITY_MEDIUM, + Impact: pb.Impact_IMPACT_HIGH, + ImpactReason: "environment=prod", + RiskScore: pb.Severity_SEVERITY_HIGH, + ScanUid: proto.String(scanID), + }, + { + Name: "BUCKET_IS_PUBLIC", + Remediation: &pb.Remediation{ + Description: bucketPublicAllUsersRemediationDesc, + Recommendation: bucketPublicAllUsersRemediationRecom, + }, + ResourceRef: &pb.ResourceRef{ + GroupName: "projects/modron-test", + ExternalId: proto.String("bucket-public-allusers"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + ScanUid: proto.String(scanID), + ObservedValue: structpb.NewStringValue("PUBLIC"), + ExpectedValue: structpb.NewStringValue("PRIVATE"), + Source: pb.Observation_SOURCE_MODRON, + Severity: pb.Severity_SEVERITY_MEDIUM, + Impact: pb.Impact_IMPACT_HIGH, + ImpactReason: "environment=prod", + RiskScore: pb.Severity_SEVERITY_HIGH, + }, + }, + }, + { + Rule: "SQL_PUBLIC_IP", + Observations: []*pb.Observation{ + { + Name: "SQL_PUBLIC_IP", + ResourceRef: &pb.ResourceRef{ + GroupName: "projects/modron-test", + ExternalId: proto.String("//cloudsql.googleapis.com/projects/project-id/instances/xyz"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + Remediation: &pb.Remediation{ + Description: sqlRemediationDesc, + Recommendation: sqlRemediationRecom, + }, + CollectionId: proto.String(collectID), + Category: pb.Observation_CATEGORY_MISCONFIGURATION, + ExternalId: proto.String("//securitycenter.googleapis.com/projects/12345/sources/123/findings/48230f1978594ffb9d09a3cb1fe5e1b3"), + Source: pb.Observation_SOURCE_SCC, + Impact: pb.Impact_IMPACT_HIGH, + ImpactReason: "environment=prod", + Severity: pb.Severity_SEVERITY_MEDIUM, + RiskScore: pb.Severity_SEVERITY_HIGH, + }, + }, + }, + }, + }, + } + + if diff := cmp.Diff(want, got, + protocmp.Transform(), + protocmp.IgnoreFields(&pb.Observation{}, "uid", "timestamp"), + protocmp.IgnoreFields(&pb.ResourceRef{}, "uid"), + ); diff != "" { + t.Fatalf("ListObservations: diff (-want +got):\n%s", diff) + } + + user1 := "user-1@example.com" + user2 := "user-2@example.com" + owner1 := "owner1@example.com" + owner2 := "owner2@example.com" + + notificationContent := func(desc, rec string) string { + return desc + "\n\n" + rec + " \n \n" + } + wantNotif := []model.Notification{ + { + SourceSystem: "modron", + Name: "BUCKET_IS_PUBLIC", + Recipient: owner1, + Content: notificationContent(bucketPublicRemediationDesc, bucketPublicRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "BUCKET_IS_PUBLIC", + Recipient: owner2, + Content: notificationContent(bucketPublicRemediationDesc, bucketPublicRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "BUCKET_IS_PUBLIC", + Recipient: user1, + Content: notificationContent(bucketPublicRemediationDesc, bucketPublicRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "BUCKET_IS_PUBLIC", + Recipient: user2, + Content: notificationContent(bucketPublicRemediationDesc, bucketPublicRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "BUCKET_IS_PUBLIC", + Recipient: owner1, + Content: notificationContent(bucketPublicAllUsersRemediationDesc, bucketPublicAllUsersRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "BUCKET_IS_PUBLIC", + Recipient: owner2, + Content: notificationContent(bucketPublicAllUsersRemediationDesc, bucketPublicAllUsersRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "BUCKET_IS_PUBLIC", + Recipient: user1, + Content: notificationContent(bucketPublicAllUsersRemediationDesc, bucketPublicAllUsersRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "BUCKET_IS_PUBLIC", + Recipient: user2, + Content: notificationContent(bucketPublicAllUsersRemediationDesc, bucketPublicAllUsersRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "SQL_PUBLIC_IP", + Recipient: owner1, + Content: notificationContent(sqlRemediationDesc, sqlRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "SQL_PUBLIC_IP", + Recipient: owner2, + Content: notificationContent(sqlRemediationDesc, sqlRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "SQL_PUBLIC_IP", + Recipient: user1, + Content: notificationContent(sqlRemediationDesc, sqlRemediationRecom), + Interval: 24 * time.Hour, + }, + { + SourceSystem: "modron", + Name: "SQL_PUBLIC_IP", + Recipient: user2, + Content: notificationContent(sqlRemediationDesc, sqlRemediationRecom), + Interval: 24 * time.Hour, + }, + } + + gotNotif := notifier.getNotifications() + sort.Sort(sortNotifications(gotNotif)) + if diff := cmp.Diff(wantNotif, gotNotif); diff != "" { + t.Fatalf("notifications diff (-want +got):\n%s", diff) + } +} + +type sortNotifications []model.Notification + +func (s sortNotifications) Len() int { + return len(s) +} + +func (s sortNotifications) Less(i, j int) bool { + if s[i].Name < s[j].Name { + return true + } else if s[i].Name > s[j].Name { + return false + } + if s[i].Content < s[j].Content { + return true + } else if s[i].Content > s[j].Content { + return false + } + return s[i].Recipient < s[j].Recipient +} + +func (s sortNotifications) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +var _ sort.Interface = (*sortNotifications)(nil) + +func TestCrossEnvRule(t *testing.T) { + ctx := context.Background() + svc, _ := getService(ctx, t) + rgNames := []string{"projects/modron-test"} + svc.RuleEngine, _ = engine.New(svc.Storage, []model.Rule{ + rules.NewCrossEnvironmentPermissionsRule(), + }, + map[string]json.RawMessage{}, + nil, + risk.TagConfig{ + ImpactMap: impactMap, + Environment: "111111111111/environment", + EmployeeData: "111111111111/employee_data", + CustomerData: "111111111111/customer_data", + }) + csRes, err := svc.CollectAndScan(ctx, &pb.CollectAndScanRequest{ + ResourceGroupNames: rgNames, + }) + if err != nil { + t.Fatalf("CollectAndScan: %v", err) + } + collectID := csRes.CollectId + scanID := csRes.ScanId + for { + status, err := svc.GetStatusCollectAndScan(ctx, &pb.GetStatusCollectAndScanRequest{ + CollectId: collectID, + ScanId: scanID, + }) + if err != nil { + t.Fatalf("GetStatusCollectAndScan: %v", err) + } + if status.CollectStatus == pb.RequestStatus_DONE && status.ScanStatus == pb.RequestStatus_DONE { + break + } + time.Sleep(100 * time.Millisecond) + } + t.Logf("Scan done") + + observations, err := svc.ListObservations(ctx, &pb.ListObservationsRequest{ + ResourceGroupNames: rgNames, + }) + if err != nil { + t.Fatalf("ListObservations: %v", err) + } + + want := []*pb.ResourceGroupObservationsPair{ + { + ResourceGroupName: "projects/modron-test", + RulesObservations: []*pb.RuleObservationPair{ + { + Rule: "CROSS_ENVIRONMENT_PERMISSIONS", + Observations: []*pb.Observation{ + { + Name: "CROSS_ENVIRONMENT_PERMISSIONS", + Category: pb.Observation_CATEGORY_MISCONFIGURATION, + ObservedValue: structpb.NewStringValue(""), + ExpectedValue: structpb.NewStringValue("prod"), + Remediation: &pb.Remediation{ + Description: "account-3@modron-other-test.iam.gserviceaccount.com is in a different environment than the resource \"bucket-accessible-from-other-project\"", + Recommendation: "Revoke the access of \"account-3@modron-other-test.iam.gserviceaccount.com\" to the resource \"bucket-accessible-from-other-project\"", + }, + ResourceRef: &pb.ResourceRef{ + GroupName: "projects/modron-test", + CloudPlatform: pb.CloudPlatform_GCP, + ExternalId: proto.String("bucket-accessible-from-other-project"), + }, + Source: pb.Observation_SOURCE_MODRON, + Severity: pb.Severity_SEVERITY_HIGH, + Impact: pb.Impact_IMPACT_HIGH, + RiskScore: pb.Severity_SEVERITY_CRITICAL, + ImpactReason: "environment=prod", + }, + { + Name: "CROSS_ENVIRONMENT_PERMISSIONS", + Category: pb.Observation_CATEGORY_MISCONFIGURATION, + ObservedValue: structpb.NewStringValue(""), + ExpectedValue: structpb.NewStringValue("prod"), + Remediation: &pb.Remediation{ + Description: "account-3@modron-other-test.iam.gserviceaccount.com is in a different environment than the resource \"projects/modron-test\"", + Recommendation: "Revoke the access of \"account-3@modron-other-test.iam.gserviceaccount.com\" to the resource \"projects/modron-test\"", + }, + ResourceRef: &pb.ResourceRef{ + GroupName: "projects/modron-test", + CloudPlatform: pb.CloudPlatform_GCP, + ExternalId: proto.String("projects/modron-test"), + }, + Source: pb.Observation_SOURCE_MODRON, + Severity: pb.Severity_SEVERITY_HIGH, + Impact: pb.Impact_IMPACT_HIGH, + RiskScore: pb.Severity_SEVERITY_CRITICAL, + ImpactReason: "environment=prod", + }, + }, + }, + { + Rule: "SQL_PUBLIC_IP", + Observations: []*pb.Observation{ + { + Name: "SQL_PUBLIC_IP", + ResourceRef: &pb.ResourceRef{ + GroupName: "projects/modron-test", + ExternalId: proto.String("//cloudsql.googleapis.com/projects/project-id/instances/xyz"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + Remediation: &pb.Remediation{ + Description: "To lower your attack surface, Cloud SQL databases should not have public IPs. Private IPs provide improved network security and lower latency for your application.", + Recommendation: "Go to https://console.cloud.google.com/sql/instances/xyz/connections?project=project-id and click the \"Networking\" tab. Uncheck the \"Public IP\" checkbox and click \"SAVE\". If your instance is not configured to use a private IP, you will first have to enable private IP by following the instructions here: https://cloud.google.com/sql/docs/mysql/configure-private-ip#existing-private-instance", + }, + CollectionId: proto.String(collectID), + Category: pb.Observation_CATEGORY_MISCONFIGURATION, + ExternalId: proto.String("//securitycenter.googleapis.com/projects/12345/sources/123/findings/48230f1978594ffb9d09a3cb1fe5e1b3"), + Source: pb.Observation_SOURCE_SCC, + Impact: pb.Impact_IMPACT_HIGH, + ImpactReason: "environment=prod", + Severity: pb.Severity_SEVERITY_MEDIUM, + RiskScore: pb.Severity_SEVERITY_HIGH, + }, + }, + }, + }, + }, + } + if diff := cmp.Diff( + want, + observations.ResourceGroupsObservations, + protocmp.Transform(), + protocmp.IgnoreFields(&pb.Observation{}, "uid", "timestamp", "scan_uid"), + protocmp.IgnoreFields(&pb.ResourceRef{}, "uid"), + ); diff != "" { + t.Fatalf("ListObservations: diff (-want +got):\n%s", diff) + } +} + +func TestLabelToEmail(t *testing.T) { + s, _ := getService(context.Background(), t) + tests := []struct { + labelContent string + want string + }{ + { + labelContent: "user_example_com", + want: "user@example.com", + }, + { + labelContent: "first_last_example_com", + want: "first.last@example.com", + }, + { + labelContent: "first_second_third_example_tokyo", + want: "first.second.third@example.tokyo", + }, + { + labelContent: "user.test_example_com", + want: "user.test@example.com", + }, + { + labelContent: "test", + want: "test", + }, + } + for _, tt := range tests { + t.Run(tt.labelContent, func(t *testing.T) { + got := s.LabelToEmail(tt.labelContent) + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Fatalf("LabelToEmail: diff (-want +got):\n%s", diff) + } + }) + } +} diff --git a/src/statemanager/reqdepstatemanager/requestDependenciesStateManager.go b/src/statemanager/reqdepstatemanager/requestDependenciesStateManager.go index ff93f02..52de146 100644 --- a/src/statemanager/reqdepstatemanager/requestDependenciesStateManager.go +++ b/src/statemanager/reqdepstatemanager/requestDependenciesStateManager.go @@ -4,32 +4,32 @@ import ( "sync" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" ) type RequestStateManager struct { - scanIds sync.Map - scanIdsDependencies map[string]map[string]struct{} - collectIds sync.Map - collectIdsDependencies map[string]map[string]struct{} + scanIDs sync.Map + scanIDsDependencies map[string]map[string]struct{} + collectIDs sync.Map + collectIDsDependencies map[string]map[string]struct{} resourceGroupsScanning map[string]string resourceGroupsCollecting map[string]string } func New() (model.StateManager, error) { return &RequestStateManager{ - scanIds: sync.Map{}, - scanIdsDependencies: map[string]map[string]struct{}{}, - collectIds: sync.Map{}, - collectIdsDependencies: map[string]map[string]struct{}{}, + scanIDs: sync.Map{}, + scanIDsDependencies: map[string]map[string]struct{}{}, + collectIDs: sync.Map{}, + collectIDsDependencies: map[string]map[string]struct{}{}, resourceGroupsScanning: map[string]string{}, resourceGroupsCollecting: map[string]string{}, }, nil } -func (manager *RequestStateManager) GetCollectState(collectId string) pb.RequestStatus { +func (manager *RequestStateManager) GetCollectState(collectID string) pb.RequestStatus { status := pb.RequestStatus_UNKNOWN - if v, ok := manager.collectIds.Load(collectId); ok { + if v, ok := manager.collectIDs.Load(collectID); ok { status = v.(pb.RequestStatus) } if status == pb.RequestStatus_CANCELLED || @@ -37,15 +37,15 @@ func (manager *RequestStateManager) GetCollectState(collectId string) pb.Request return status } if status == pb.RequestStatus_ALREADY_RUNNING { - manager.collectIds.Store(collectId, pb.RequestStatus_DONE) + manager.collectIDs.Store(collectID, pb.RequestStatus_DONE) status = pb.RequestStatus_DONE } - if mapDep, ok := manager.collectIdsDependencies[collectId]; ok { + if mapDep, ok := manager.collectIDsDependencies[collectID]; ok { for dep := range mapDep { state := manager.GetCollectState(dep) if state == pb.RequestStatus_UNKNOWN || state == pb.RequestStatus_CANCELLED { - manager.collectIds.Store(collectId, pb.RequestStatus_CANCELLED) + manager.collectIDs.Store(collectID, pb.RequestStatus_CANCELLED) } else if state == pb.RequestStatus_RUNNING { return pb.RequestStatus_RUNNING } @@ -54,9 +54,9 @@ func (manager *RequestStateManager) GetCollectState(collectId string) pb.Request return status } -func (manager *RequestStateManager) GetScanState(scanId string) pb.RequestStatus { +func (manager *RequestStateManager) GetScanState(scanID string) pb.RequestStatus { status := pb.RequestStatus_UNKNOWN - if v, ok := manager.scanIds.Load(scanId); ok { + if v, ok := manager.scanIDs.Load(scanID); ok { status = v.(pb.RequestStatus) } if status == pb.RequestStatus_CANCELLED || @@ -64,15 +64,15 @@ func (manager *RequestStateManager) GetScanState(scanId string) pb.RequestStatus return status } if status == pb.RequestStatus_ALREADY_RUNNING { - manager.scanIds.Store(scanId, pb.RequestStatus_DONE) + manager.scanIDs.Store(scanID, pb.RequestStatus_DONE) status = pb.RequestStatus_DONE } - if mapDep, ok := manager.scanIdsDependencies[scanId]; ok { + if mapDep, ok := manager.scanIDsDependencies[scanID]; ok { for dep := range mapDep { state := manager.GetScanState(dep) if state == pb.RequestStatus_UNKNOWN || state == pb.RequestStatus_CANCELLED { - manager.scanIds.Store(scanId, pb.RequestStatus_CANCELLED) + manager.scanIDs.Store(scanID, pb.RequestStatus_CANCELLED) } else if state == pb.RequestStatus_RUNNING { return pb.RequestStatus_RUNNING } @@ -81,62 +81,62 @@ func (manager *RequestStateManager) GetScanState(scanId string) pb.RequestStatus return status } -func (manager *RequestStateManager) AddScan(scanId string, resourceGroupNames []string) []string { - manager.scanIds.Store(scanId, pb.RequestStatus_RUNNING) +func (manager *RequestStateManager) AddScan(scanID string, resourceGroupNames []string) []string { + manager.scanIDs.Store(scanID, pb.RequestStatus_RUNNING) filteredRG := []string{} for _, rs := range resourceGroupNames { scan, ok := manager.resourceGroupsScanning[rs] if !ok { - manager.resourceGroupsScanning[rs] = scanId + manager.resourceGroupsScanning[rs] = scanID filteredRG = append(filteredRG, rs) } else { - if _, ok := manager.scanIdsDependencies[scanId]; !ok { - manager.scanIdsDependencies[scanId] = map[string]struct{}{} + if _, ok := manager.scanIDsDependencies[scanID]; !ok { + manager.scanIDsDependencies[scanID] = map[string]struct{}{} } - manager.scanIdsDependencies[scanId][scan] = struct{}{} + manager.scanIDsDependencies[scanID][scan] = struct{}{} } } if len(filteredRG) < 1 { - manager.scanIds.Store(scanId, pb.RequestStatus_ALREADY_RUNNING) + manager.scanIDs.Store(scanID, pb.RequestStatus_ALREADY_RUNNING) } return filteredRG } -func (manager *RequestStateManager) EndScan(scanId string, resourceGroupNames []string) { - if _, ok := manager.scanIds.Load(scanId); ok { +func (manager *RequestStateManager) EndScan(scanID string, resourceGroupNames []string) { + if _, ok := manager.scanIDs.Load(scanID); ok { for _, rs := range resourceGroupNames { delete(manager.resourceGroupsScanning, rs) } - manager.scanIds.Store(scanId, pb.RequestStatus_DONE) + manager.scanIDs.Store(scanID, pb.RequestStatus_DONE) } } -func (manager *RequestStateManager) AddCollect(collectId string, resourceGroupNames []string) []string { - manager.collectIds.Store(collectId, pb.RequestStatus_RUNNING) +func (manager *RequestStateManager) AddCollect(collectID string, resourceGroupNames []string) []string { + manager.collectIDs.Store(collectID, pb.RequestStatus_RUNNING) filteredRG := []string{} for _, rs := range resourceGroupNames { collect, ok := manager.resourceGroupsCollecting[rs] if !ok { - manager.resourceGroupsCollecting[rs] = collectId + manager.resourceGroupsCollecting[rs] = collectID filteredRG = append(filteredRG, rs) } else { - if _, ok := manager.collectIdsDependencies[collectId]; !ok { - manager.collectIdsDependencies[collectId] = map[string]struct{}{} + if _, ok := manager.collectIDsDependencies[collectID]; !ok { + manager.collectIDsDependencies[collectID] = map[string]struct{}{} } - manager.collectIdsDependencies[collectId][collect] = struct{}{} + manager.collectIDsDependencies[collectID][collect] = struct{}{} } } if len(filteredRG) < 1 { - manager.collectIds.Store(collectId, pb.RequestStatus_ALREADY_RUNNING) + manager.collectIDs.Store(collectID, pb.RequestStatus_ALREADY_RUNNING) } return filteredRG } -func (manager *RequestStateManager) EndCollect(collectId string, resourceGroupNames []string) { - if _, ok := manager.collectIds.Load(collectId); ok { +func (manager *RequestStateManager) EndCollect(collectID string, resourceGroupNames []string) { + if _, ok := manager.collectIDs.Load(collectID); ok { for _, rs := range resourceGroupNames { delete(manager.resourceGroupsCollecting, rs) } - manager.collectIds.Store(collectId, pb.RequestStatus_DONE) + manager.collectIDs.Store(collectID, pb.RequestStatus_DONE) } } diff --git a/src/statemanager/reqdepstatemanager/requestDependenciesStateManager_test.go b/src/statemanager/reqdepstatemanager/requestDependenciesStateManager_test.go index 11dd34f..aeb6a7d 100644 --- a/src/statemanager/reqdepstatemanager/requestDependenciesStateManager_test.go +++ b/src/statemanager/reqdepstatemanager/requestDependenciesStateManager_test.go @@ -3,7 +3,7 @@ package reqdepstatemanager import ( "testing" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" ) func TestSimpleStateManager(t *testing.T) { @@ -12,55 +12,55 @@ func TestSimpleStateManager(t *testing.T) { t.Fatal(err) } - collectId1 := "collect-id-1" - collectId2 := "collect-id-2" - scanId1 := "scan-id-1" - scanId2 := "scan-id-2" + collectID1 := "collect-id-1" + collectID2 := "collect-id-2" + scanID1 := "scan-id-1" + scanID2 := "scan-id-2" - if state := stateManager.GetCollectState(collectId1); state != pb.RequestStatus_UNKNOWN { - t.Errorf("GetCollectState(%s) got %s, want %s", collectId1, state, pb.RequestStatus_UNKNOWN) + if state := stateManager.GetCollectState(collectID1); state != pb.RequestStatus_UNKNOWN { + t.Errorf("GetCollectState(%s) got %s, want %s", collectID1, state, pb.RequestStatus_UNKNOWN) } - if state := stateManager.GetScanState(scanId1); state != pb.RequestStatus_UNKNOWN { - t.Errorf("GetScanState(%s) got %s, want %s", scanId1, state, pb.RequestStatus_UNKNOWN) + if state := stateManager.GetScanState(scanID1); state != pb.RequestStatus_UNKNOWN { + t.Errorf("GetScanState(%s) got %s, want %s", scanID1, state, pb.RequestStatus_UNKNOWN) } resourceGroups := []string{"projects/p1", "projects/p2", "projects/p3"} - collecting := stateManager.AddCollect(collectId1, resourceGroups) + collecting := stateManager.AddCollect(collectID1, resourceGroups) if len(collecting) != 3 { t.Errorf("AddCollect(%v): got len %d, want %d", resourceGroups, len(collecting), 3) } - scanning := stateManager.AddScan(scanId1, resourceGroups) + scanning := stateManager.AddScan(scanID1, resourceGroups) if len(scanning) != 3 { t.Errorf("AddScan(%v): got len %d, want %d", resourceGroups, len(scanning), 3) } - if state := stateManager.GetCollectState(collectId1); state != pb.RequestStatus_RUNNING { - t.Errorf("GetCollectState(%s): got %s, want %s", collectId1, state, pb.RequestStatus_RUNNING) + if state := stateManager.GetCollectState(collectID1); state != pb.RequestStatus_RUNNING { + t.Errorf("GetCollectState(%s): got %s, want %s", collectID1, state, pb.RequestStatus_RUNNING) } - if state := stateManager.GetCollectState(collectId2); state != pb.RequestStatus_UNKNOWN { - t.Errorf("GetCollectState(%s): got %s, want %s", collectId2, state, pb.RequestStatus_UNKNOWN) + if state := stateManager.GetCollectState(collectID2); state != pb.RequestStatus_UNKNOWN { + t.Errorf("GetCollectState(%s): got %s, want %s", collectID2, state, pb.RequestStatus_UNKNOWN) } - if state := stateManager.GetScanState(scanId1); state != pb.RequestStatus_RUNNING { - t.Errorf("GetScanState(%s): got %s, want %s", scanId1, state, pb.RequestStatus_RUNNING) + if state := stateManager.GetScanState(scanID1); state != pb.RequestStatus_RUNNING { + t.Errorf("GetScanState(%s): got %s, want %s", scanID1, state, pb.RequestStatus_RUNNING) } - if state := stateManager.GetScanState(scanId2); state != pb.RequestStatus_UNKNOWN { - t.Errorf("GetScanState(%s): got %s, want %s", scanId2, state, pb.RequestStatus_UNKNOWN) + if state := stateManager.GetScanState(scanID2); state != pb.RequestStatus_UNKNOWN { + t.Errorf("GetScanState(%s): got %s, want %s", scanID2, state, pb.RequestStatus_UNKNOWN) } - stateManager.EndCollect(collectId2, resourceGroups) - stateManager.EndScan(scanId2, resourceGroups) + stateManager.EndCollect(collectID2, resourceGroups) + stateManager.EndScan(scanID2, resourceGroups) - stateManager.EndCollect(collectId1, resourceGroups) - stateManager.EndScan(scanId1, resourceGroups) + stateManager.EndCollect(collectID1, resourceGroups) + stateManager.EndScan(scanID1, resourceGroups) - if state := stateManager.GetCollectState(collectId1); state != pb.RequestStatus_DONE { - t.Errorf("GetCollectState(%s) got %s, want %s", collectId1, state, pb.RequestStatus_DONE) + if state := stateManager.GetCollectState(collectID1); state != pb.RequestStatus_DONE { + t.Errorf("GetCollectState(%s) got %s, want %s", collectID1, state, pb.RequestStatus_DONE) } - if state := stateManager.GetScanState(scanId1); state != pb.RequestStatus_DONE { - t.Errorf("GetScanState(%s): got %s, want %s", scanId1, state, pb.RequestStatus_DONE) + if state := stateManager.GetScanState(scanID1); state != pb.RequestStatus_DONE { + t.Errorf("GetScanState(%s): got %s, want %s", scanID1, state, pb.RequestStatus_DONE) } } @@ -70,78 +70,78 @@ func TestDepSateManager(t *testing.T) { t.Fatal(err) } - collectId1 := "collect-id-1" - collectId2 := "collect-id-2" - collectId3 := "collect-id-3" - scanId1 := "scan-id-1" - scanId2 := "scan-id-2" - scanId3 := "scan-id-3" + collectID1 := "collect-id-1" + collectID2 := "collect-id-2" + collectID3 := "collect-id-3" + scanID1 := "scan-id-1" + scanID2 := "scan-id-2" + scanID3 := "scan-id-3" resourceGroups := []string{"projects/p1", "projects/p2", "projects/p3"} - if collecting := stateManager.AddCollect(collectId1, resourceGroups); len(collecting) != 3 { + if collecting := stateManager.AddCollect(collectID1, resourceGroups); len(collecting) != 3 { t.Errorf("AddCollect(%v): got len %d, want %d", resourceGroups, len(collecting), 3) } overlappingResourceGroups := []string{"projects/p0", "projects/p1", "projects/p2"} if collecting := stateManager.AddCollect("collect-id-3", overlappingResourceGroups); len(collecting) != 1 { - t.Errorf("AddCollect(%s): got len %d, want %d", collectId1, len(collecting), 1) + t.Errorf("AddCollect(%s): got len %d, want %d", collectID1, len(collecting), 1) } - if scanning := stateManager.AddScan(scanId1, resourceGroups); len(scanning) != 3 { - t.Errorf("AddScan(%s): got len %d, want %d", scanId1, len(scanning), 1) + if scanning := stateManager.AddScan(scanID1, resourceGroups); len(scanning) != 3 { + t.Errorf("AddScan(%s): got len %d, want %d", scanID1, len(scanning), 1) } - if scanning := stateManager.AddScan(scanId3, []string{"projects/p1", "projects/p2"}); len(scanning) != 0 { - t.Errorf("AddScan(%s): got len %d, want %d", scanId3, len(scanning), 0) + if scanning := stateManager.AddScan(scanID3, []string{"projects/p1", "projects/p2"}); len(scanning) != 0 { + t.Errorf("AddScan(%s): got len %d, want %d", scanID3, len(scanning), 0) } - if state := stateManager.GetCollectState(collectId1); state != pb.RequestStatus_RUNNING { - t.Errorf("GetCollectState(%s): got %s, want %s", collectId1, state, pb.RequestStatus_RUNNING) + if state := stateManager.GetCollectState(collectID1); state != pb.RequestStatus_RUNNING { + t.Errorf("GetCollectState(%s): got %s, want %s", collectID1, state, pb.RequestStatus_RUNNING) } - if state := stateManager.GetCollectState(collectId2); state != pb.RequestStatus_UNKNOWN { - t.Errorf("GetCollectState(%s): got %s, want %s", collectId2, state, pb.RequestStatus_UNKNOWN) + if state := stateManager.GetCollectState(collectID2); state != pb.RequestStatus_UNKNOWN { + t.Errorf("GetCollectState(%s): got %s, want %s", collectID2, state, pb.RequestStatus_UNKNOWN) } - if state := stateManager.GetCollectState(collectId3); state != pb.RequestStatus_RUNNING { - t.Errorf("GetCollectState(%s): got %s, want %s", collectId3, state, pb.RequestStatus_RUNNING) + if state := stateManager.GetCollectState(collectID3); state != pb.RequestStatus_RUNNING { + t.Errorf("GetCollectState(%s): got %s, want %s", collectID3, state, pb.RequestStatus_RUNNING) } - if state := stateManager.GetScanState(scanId1); state != pb.RequestStatus_RUNNING { - t.Errorf("GetScanState(%s): got %s, want %s", scanId1, state, pb.RequestStatus_RUNNING) + if state := stateManager.GetScanState(scanID1); state != pb.RequestStatus_RUNNING { + t.Errorf("GetScanState(%s): got %s, want %s", scanID1, state, pb.RequestStatus_RUNNING) } - if state := stateManager.GetScanState(scanId2); state != pb.RequestStatus_UNKNOWN { - t.Errorf("GetScanState(%s): got %s, want %s", scanId2, state, pb.RequestStatus_UNKNOWN) + if state := stateManager.GetScanState(scanID2); state != pb.RequestStatus_UNKNOWN { + t.Errorf("GetScanState(%s): got %s, want %s", scanID2, state, pb.RequestStatus_UNKNOWN) } - if state := stateManager.GetScanState(scanId3); state != pb.RequestStatus_RUNNING { - t.Errorf("GetScanState(%s): got %s, want %s", scanId3, state, pb.RequestStatus_RUNNING) + if state := stateManager.GetScanState(scanID3); state != pb.RequestStatus_RUNNING { + t.Errorf("GetScanState(%s): got %s, want %s", scanID3, state, pb.RequestStatus_RUNNING) } - stateManager.EndCollect(collectId1, resourceGroups) - stateManager.EndCollect(collectId2, resourceGroups) - stateManager.EndScan(scanId1, resourceGroups) - stateManager.EndScan(scanId2, resourceGroups) + stateManager.EndCollect(collectID1, resourceGroups) + stateManager.EndCollect(collectID2, resourceGroups) + stateManager.EndScan(scanID1, resourceGroups) + stateManager.EndScan(scanID2, resourceGroups) - if state := stateManager.GetCollectState(collectId1); state != pb.RequestStatus_DONE { - t.Errorf("GetCollectState(%s): got %s, want %s", collectId1, state, pb.RequestStatus_DONE) + if state := stateManager.GetCollectState(collectID1); state != pb.RequestStatus_DONE { + t.Errorf("GetCollectState(%s): got %s, want %s", collectID1, state, pb.RequestStatus_DONE) } - if state := stateManager.GetCollectState(collectId2); state != pb.RequestStatus_UNKNOWN { - t.Errorf("GetCollectState(%s): got %s, want %s", collectId2, state, pb.RequestStatus_UNKNOWN) + if state := stateManager.GetCollectState(collectID2); state != pb.RequestStatus_UNKNOWN { + t.Errorf("GetCollectState(%s): got %s, want %s", collectID2, state, pb.RequestStatus_UNKNOWN) } - if state := stateManager.GetScanState(scanId1); state != pb.RequestStatus_DONE { - t.Errorf("GetScanState(%s): got %s, want %s", scanId1, state, pb.RequestStatus_DONE) + if state := stateManager.GetScanState(scanID1); state != pb.RequestStatus_DONE { + t.Errorf("GetScanState(%s): got %s, want %s", scanID1, state, pb.RequestStatus_DONE) } - if state := stateManager.GetScanState(scanId2); state != pb.RequestStatus_UNKNOWN { - t.Errorf("GetScanState(%s): got %s, want %s", scanId2, state, pb.RequestStatus_UNKNOWN) + if state := stateManager.GetScanState(scanID2); state != pb.RequestStatus_UNKNOWN { + t.Errorf("GetScanState(%s): got %s, want %s", scanID2, state, pb.RequestStatus_UNKNOWN) } - if state := stateManager.GetScanState(scanId3); state != pb.RequestStatus_DONE { - t.Errorf("GetScanState(%s): got %s, want %s", scanId3, state, pb.RequestStatus_DONE) + if state := stateManager.GetScanState(scanID3); state != pb.RequestStatus_DONE { + t.Errorf("GetScanState(%s): got %s, want %s", scanID3, state, pb.RequestStatus_DONE) } - if state := stateManager.GetCollectState(collectId3); state != pb.RequestStatus_RUNNING { - t.Errorf("GetCollectState(%s): got %s, want %s", collectId3, state, pb.RequestStatus_RUNNING) + if state := stateManager.GetCollectState(collectID3); state != pb.RequestStatus_RUNNING { + t.Errorf("GetCollectState(%s): got %s, want %s", collectID3, state, pb.RequestStatus_RUNNING) } - stateManager.EndCollect(collectId3, overlappingResourceGroups) - if state := stateManager.GetCollectState(collectId3); state != pb.RequestStatus_DONE { - t.Errorf("GetCollectState(%s): got %s, want %s", collectId3, state, pb.RequestStatus_DONE) + stateManager.EndCollect(collectID3, overlappingResourceGroups) + if state := stateManager.GetCollectState(collectID3); state != pb.RequestStatus_DONE { + t.Errorf("GetCollectState(%s): got %s, want %s", collectID3, state, pb.RequestStatus_DONE) } } diff --git a/src/storage/gormstorage/gorm.go b/src/storage/gormstorage/gorm.go new file mode 100644 index 0000000..afc0463 --- /dev/null +++ b/src/storage/gormstorage/gorm.go @@ -0,0 +1,598 @@ +package gormstorage + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "sort" + "strings" + "time" + + "github.com/sirupsen/logrus" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "google.golang.org/protobuf/proto" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + gormlogger "gorm.io/gorm/logger" + gormtracing "gorm.io/plugin/opentelemetry/tracing" + + "github.com/nianticlabs/modron/src/common" + "github.com/nianticlabs/modron/src/constants" + "github.com/nianticlabs/modron/src/model" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" +) + +const ( + createBatchSize = 500 + listResourcesLimit = 1_000_000 + listObservationsLimit = 1_000_000 + maxUUIDRetries = 3 + sqlDBConnectionTimeout = 3 * time.Second + sqlDBConnectionTimeoutFactor = 3 + dbMaxBootUpWaitTime = 30 * time.Second + + collectionOp opType = "collection" + scanOp opType = "scan" +) + +type opType string + +var ( + log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "gormstorage") + tracer = otel.Tracer("github.com/nianticlabs/modron/src/storage/gormstorage") +) + +// BatchCreateResources creates a batch of resources. +func (svc *Service) BatchCreateResources(ctx context.Context, resources []*pb.Resource) ([]*pb.Resource, error) { + ctx, span := tracer.Start(ctx, "BatchCreateResources") + defer span.End() + span.SetAttributes( + attribute.Int("num_resources", len(resources)), + ) + var toCreateResources []Resource + for _, res := range resources { + if res.Uid == "" { + res.Uid = common.GetUUID(maxUUIDRetries) + } + resPb, err := proto.Marshal(res) + if err != nil { + log.Warnf("proto marshal %q: %v", res.Uid, err) + continue + } + t, err := utils.TypeFromResource(res) + if err != nil { + log.Warnf("type of %q: %v", res.Uid, err) + } + var recordTime *time.Time + ts := res.Timestamp + if ts != nil { + pbTime := ts.AsTime() + recordTime = &pbTime + } + labelsBytes, err := json.Marshal(res.Labels) + if err != nil { + log.Errorf("failed to marshal labels: %v", err) + return nil, err + } + tagsBytes, err := json.Marshal(res.Tags) + if err != nil { + log.Errorf("failed to marshal tags: %v", err) + return nil, err + } + toCreateResources = append(toCreateResources, Resource{ + ID: res.Uid, + Name: res.Name, + DisplayName: res.DisplayName, + ResourceGroupName: res.ResourceGroupName, + CollectionID: res.CollectionUid, + RecordTime: recordTime, + ParentName: res.Parent, + Type: t, + Proto: resPb, + Labels: labelsBytes, + Tags: tagsBytes, + }) + } + if len(toCreateResources) > 0 { + if err := svc.db.WithContext(ctx).CreateInBatches(&toCreateResources, createBatchSize).Error; err != nil { + return nil, fmt.Errorf("insert: %w", err) + } + } + return resources, nil +} + +// ListResources retrieves resources based on the provided filter. +func (svc *Service) ListResources(ctx context.Context, filter model.StorageFilter) (resources []*pb.Resource, err error) { + var sqlResourceRows []Resource + if filter.Limit == 0 { + filter.Limit = listResourcesLimit + } + whereFilter, params := getFilter(filter, collectionOp) + + maxRetries := 3 + for i := 0; i < maxRetries; i++ { + err = + svc.db.WithContext(ctx). + Model(&Resource{}). + Where(whereFilter, params...). + Limit(filter.Limit). + Order("resourcename ASC"). + Find(&sqlResourceRows).Error + if err == nil { + break + } + if errors.Is(err, io.ErrUnexpectedEOF) { + log.Warnf("retrying with exponential backoff (%d/%d): %v", i+1, maxRetries, err) + time.Sleep(time.Duration(math.Pow(2.0, float64(i))) * time.Second) //nolint:mnd + continue + } + break + } + if err != nil { + return nil, fmt.Errorf("select: %w", err) + } + for _, row := range sqlResourceRows { + res, err := row.ToResourceProto() + if err != nil { + log.Warnf("unmarshal: %v", err) + } + resources = append(resources, res) + } + return resources, nil +} + +func getFilter(filter model.StorageFilter, operationName opType) (string, []any) { + var allFilters []string + var params []any + + if filter.OperationID == "" { + // When we don't have the operation ID, we have to get the latest one for the resource group + // This is an expensive operation! + addFilter := "" + if len(filter.ResourceGroupNames) > 0 { + addFilter = "AND resourcegroupname IN ?\n" + } + operationFilter := fmt.Sprintf(`(%s,resourcegroupname) IN ( + SELECT DISTINCT operationid,resourcegroupname FROM operations WHERE (resourcegroupname, endtime) IN ( + SELECT resourcegroupname, timestamp FROM ( + SELECT resourcegroupname, max(endtime) as timestamp + FROM operations AS ts + WHERE + opstype = '%s' AND + status = 'COMPLETED' %s + GROUP BY resourcegroupname + ) + AS ts1 + ) + )`, + operationName+"id", + operationName, + addFilter, + ) + allFilters = append(allFilters, operationFilter) + if addFilter != "" { + params = append(params, filter.ResourceGroupNames) + } + } + + paramFilters, sqlFilterParams := sqlFilterFromModel(filter, operationName) + params = append(params, sqlFilterParams...) + allFilters = append(allFilters, paramFilters...) + finalFilter := strings.Join(allFilters, " AND ") + return finalFilter, params +} + +// BatchCreateObservations creates a batch of observations. +func (svc *Service) BatchCreateObservations(ctx context.Context, observations []*pb.Observation) ([]*pb.Observation, error) { + ctx, span := tracer.Start(ctx, "BatchCreateObservations") + defer span.End() + span.SetAttributes( + attribute.Int("num_observations", len(observations)), + ) + var dbObs []Observation + for _, obs := range observations { + myObs := obs + obsPb, err := proto.Marshal(myObs) + if err != nil { + log.Warnf("proto marshal %q: %v", myObs.Uid, err) + continue + } + rsrcRef := myObs.ResourceRef + var groupName, cloudPlatform string + var externalID, resourceID *string + if rsrcRef != nil { + groupName = rsrcRef.GroupName + resourceID = rsrcRef.Uid + cloudPlatform = rsrcRef.CloudPlatform.String() + externalID = rsrcRef.ExternalId + } + + dbObs = append(dbObs, Observation{ + ID: myObs.Uid, + Name: myObs.Name, + Resource: nil, + ResourceID: resourceID, + ResourceGroupName: groupName, + ResourceCloudPlatform: cloudPlatform, + ResourceExternalID: externalID, + ScanID: myObs.ScanUid, + CollectionID: myObs.CollectionId, + RecordTime: myObs.Timestamp.AsTime(), + Proto: obsPb, + ExternalID: myObs.ExternalId, + Source: ObservationSource(myObs.Source), + Category: ObservationCategory(myObs.Category), + SeverityScore: FromSeverityPb(myObs.Severity), + Impact: Impact(myObs.Impact), + RiskScore: FromSeverityPb(myObs.Severity), + }) + + } + if len(dbObs) > 0 { + if err := svc.db.WithContext(ctx).CreateInBatches(&dbObs, createBatchSize).Error; err != nil { + return nil, fmt.Errorf("insert: %w", err) + } + } + return observations, nil +} + +func validObservation(db *gorm.DB) *gorm.DB { + return db.Where("severity_score >= 0") +} + +// ListObservations retrieves observations based on the provided filter. +func (svc *Service) ListObservations(ctx context.Context, filter model.StorageFilter) (observations []*pb.Observation, err error) { + ctx, span := tracer.Start(ctx, "ListObservations") + defer span.End() + var sqlObservationRows []Observation + if filter.Limit > listObservationsLimit { + return nil, fmt.Errorf("limit too high: %d", filter.Limit) + } + if filter.Limit == 0 { + filter.Limit = listObservationsLimit + } + // Observations generated from a scan (Modron) + scanFilter, scanParams := getFilter(filter, scanOp) + // Observations generated from a collection (SCC, external integrations) + collectFilter, collectParams := getFilter(filter, collectionOp) + err = + svc.db.WithContext(ctx). + Model(&Observation{}). + Scopes(validObservation). + Where( + svc.db.Where(scanFilter, scanParams...).Or(collectFilter, collectParams...), + ). + Order("risk_score DESC, source ASC"). + Limit(filter.Limit). + Find(&sqlObservationRows). + Error + if err != nil { + return nil, fmt.Errorf("select: %w", err) + } + span.SetAttributes( + attribute.Int(constants.TraceKeyNumObservations, len(sqlObservationRows)), + ) + for _, row := range sqlObservationRows { + obs, err := row.ToObservationProto() + if err != nil { + log.Warnf("unmarshal: %v", err) + } + observations = append(observations, obs) + } + return observations, nil +} + +func fromPbOperation(o *pb.Operation) Operation { + var startTime time.Time + var endTime *time.Time + + switch o.Status { + case pb.Operation_STARTED: + startTime = o.StatusTime.AsTime() + case pb.Operation_COMPLETED, pb.Operation_FAILED: + t1 := o.StatusTime.AsTime() + endTime = &t1 + default: + log.Warnf("unknown status: %v", o.Status) + } + + return Operation{ + ID: o.Id, + ResourceGroup: o.ResourceGroup, + OpsType: o.Type, + Status: OperationStatus(o.Status), + StartTime: startTime, + EndTime: endTime, + Reason: o.Reason, + } +} + +// AddOperationLog adds a new operation log entry. +func (svc *Service) AddOperationLog(ctx context.Context, operations []*pb.Operation) error { + var errArr []error + var addOps []Operation + for _, o := range operations { + if o.ResourceGroup == "" { + log.WithField("operation_id", o.Id).Warn("missing resource group for operation") + } + if o.Status != pb.Operation_STARTED { + // We first add new ops + continue + } + addOps = append(addOps, fromPbOperation(o)) + } + if len(addOps) > 0 { + if err := svc.db.WithContext(ctx).Create(&addOps).Error; err != nil { + log.WithError(err).Error("failed to insert operations") + errArr = append(errArr, err) + } + } + + for _, o := range operations { + if o.Status == pb.Operation_STARTED { + // Insert ops were already added + continue + } + opLogger := log.WithFields(logrus.Fields{ + "operation_id": o.Id, + "status": o.Status, + }) + var foundOp Operation + err := svc.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + tx.Find(&foundOp, + "operationid = ? AND opstype = ? AND resourcegroupname = ?", + o.Id, o.Type, o.ResourceGroup, + ) + if errors.Is(tx.Error, gorm.ErrRecordNotFound) { + return fmt.Errorf("operation %q not found", o.Id) + } else if tx.Error != nil { + return fmt.Errorf("unable to find operation: %w", tx.Error) + } + + foundOp.Status = OperationStatus(o.Status) + if o.StatusTime != nil { + t := o.StatusTime.AsTime() + foundOp.EndTime = &t + } + foundOp.Reason = o.Reason + + if err := tx.Save(&foundOp).Error; err != nil { + return fmt.Errorf("failed to update operation: %w", err) + } + return nil + }) + if err != nil { + opLogger.WithError(err).Errorf("failed to perform operation") + errArr = append(errArr, err) + continue + } + } + return errors.Join(errArr...) +} + +// PurgeIncompleteOperations purges incomplete operations from the database. +func (svc *Service) PurgeIncompleteOperations(ctx context.Context) error { + return svc.db.WithContext(ctx). + Model(&Operation{}). + Where("endtime IS NULL"). + Update("endtime", time.Now()). + Update("status", pb.Operation_FAILED).Error +} + +// Deprecated FlushOpsLog flushes the operation log cache to the database. +func (svc *Service) FlushOpsLog(_ context.Context) error { + // NO-OP + return nil +} + +func sqlFilterFromModel(filter model.StorageFilter, operationName opType) (filters []string, params []any) { + if len(filter.ResourceNames) > 0 { + filters = append(filters, "resourceName IN ?") + params = append(params, filter.ResourceNames) + } + if len(filter.ResourceTypes) > 0 { + filters = append(filters, "resourceType IN ?") + params = append(params, filter.ResourceTypes) + } + if len(filter.ResourceGroupNames) > 0 { + filters = append(filters, "resourceGroupName IN ?") + params = append(params, filter.ResourceGroupNames) + } + if len(filter.ResourceIDs) > 0 { + filters = append(filters, "resourceID IN ?") + params = append(params, filter.ResourceIDs) + } + if len(filter.ParentNames) > 0 { + filters = append(filters, "parentName IN ?") + params = append(params, filter.ParentNames) + } + if filter.OperationID != "" { + switch operationName { + case collectionOp: + filters = append(filters, "collectionID = ?") + params = append(params, filter.OperationID) + case scanOp: + filters = append(filters, "scanID = ?") + params = append(params, filter.OperationID) + default: + log.Warnf("unknown operation type: %v", operationName) + } + } + if !filter.StartTime.IsZero() { + filters = append(filters, "recordTime >= ?") + params = append(params, filter.StartTime) + } + return +} + +type Service struct { + db *gorm.DB + cfg Config +} + +// GetChildrenOfResource gets the _children_ of a given resource ID +// when parentResourceName is empty, the whole tree (from the root) is returned +func (svc *Service) GetChildrenOfResource( + ctx context.Context, + collectID string, + parentResourceName string, + resourceType *string, +) (map[string]*pb.RecursiveResource, error) { + var resources []Resource + tx := svc.db.WithContext(ctx) + if resourceType != nil { + tx = tx.Where("resourcetype = ?", *resourceType) + } + if parentResourceName == "" { + tx = tx.Find(&resources, "collectionid = ?", collectID) + } else { + // We use a recursive CTE only in case we have a parentResourceName, otherwise the call is too expensive + tx = tx. + Raw(`WITH RECURSIVE resource_hierarchy(id) AS ( + VALUES(?) + UNION ALL + SELECT resourcename FROM + resources, resource_hierarchy + WHERE + resources.parentname = resource_hierarchy.id AND + resources.collectionid = ? + ) + SELECT resourcename,display_name,parentname,resourcetype,labels + FROM resources + WHERE resourcename IN (SELECT * FROM resource_hierarchy) AND + collectionid = ?`, + parentResourceName, collectID, collectID) + tx = tx.Scan(&resources) + } + + if tx.Error != nil { + return nil, tx.Error + } + + var pbResources []*pb.Resource + for _, r := range resources { + pbRes, err := r.ToResourceProto() + if err != nil { + return nil, err + } + pbResources = append(pbResources, pbRes) + } + return utils.ComputeRgHierarchy(pbResources) +} + +type byTypeAndName []*pb.RecursiveResource + +func (b byTypeAndName) Len() int { + return len(b) +} + +func (b byTypeAndName) Less(i, j int) bool { + // We're lucky that folders/ comes before projects/ (in the alphabet), so it's + // enough to just sort them by their name. Folders will always come before projects. + return b[i].Name < b[j].Name +} + +func (b byTypeAndName) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +var _ sort.Interface = byTypeAndName{} + +type Config struct { + BatchSize int32 + LogAllQueries bool +} + +func waitForSQLDatabase(db *gorm.DB) (err error) { + var lastError error + timeOut := sqlDBConnectionTimeout + sqlDb, err := db.DB() + if err != nil { + return err + } + for timeOut < dbMaxBootUpWaitTime { + if err = sqlDb.Ping(); err == nil { + return nil + } + time.Sleep(timeOut) + timeOut = sqlDBConnectionTimeoutFactor * timeOut + } + return lastError +} + +func New(db *gorm.DB, cfg Config) (model.Storage, error) { + if err := db.Use(gormtracing.NewPlugin()); err != nil { + return nil, fmt.Errorf("setup tracing for gorm: %w", err) + } + if cfg.BatchSize <= 0 { + return nil, fmt.Errorf("batch size must be greater than 0") + } + if err := waitForSQLDatabase(db); err != nil { + return nil, err + } + sqlDb, err := db.DB() + if err != nil { + return nil, fmt.Errorf("failed to get database: %w", err) + } + err = sqlDb.Ping() + if err != nil { + return nil, fmt.Errorf("failed to ping database: %w", err) + } + // Automigrate + for _, v := range []any{ + &Resource{}, + &Observation{}, + &Operation{}, + } { + if err := db.AutoMigrate(v); err != nil { + return nil, fmt.Errorf("auto migrate: %w", err) + } + } + + if cfg.LogAllQueries { + db.Config.Logger = gormlogger.Default.LogMode(gormlogger.Info) + } + + return &Service{ + db: db, + cfg: cfg, + }, nil +} + +func NewSQLite(cfg Config, dbPath string) (model.Storage, error) { + db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) + if err != nil { + return nil, fmt.Errorf("failed to connect to SQLite: %w", err) + } + phyDB, err := db.DB() + if err != nil { + return nil, fmt.Errorf("failed to get DB: %w", err) + } + phyDB.SetMaxOpenConns(1) // https://github.com/mattn/go-sqlite3/issues/274#issuecomment-191597862 + return New(db, cfg) +} + +// NewPostgres creates a new SQL storage service using PostgreSQL. +func NewPostgres(cfg Config, dsn string) (model.Storage, error) { + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + return nil, fmt.Errorf("failed to connect to PostgreSQL: %w", err) + } + return New(db, cfg) +} + +func NewDB(driver string, connectionString string, config Config) (model.Storage, error) { + switch driver { + case "sqlite3": + return NewSQLite(config, connectionString) + case "postgres": + return NewPostgres(config, connectionString) + } + return nil, fmt.Errorf("unsupported driver: %s", driver) +} diff --git a/src/storage/gormstorage/gorm_integration_test.go b/src/storage/gormstorage/gorm_integration_test.go new file mode 100644 index 0000000..82480c8 --- /dev/null +++ b/src/storage/gormstorage/gorm_integration_test.go @@ -0,0 +1,65 @@ +//go:build integration + +package gormstorage + +import ( + "context" + "testing" + "time" + + _ "github.com/lib/pq" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" + + "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/storage/test" +) + +func getPostgresDB(ctx context.Context, t *testing.T) (*postgres.PostgresContainer, error) { + t.Helper() + return postgres.Run(ctx, "postgres:16-alpine", + postgres.WithDatabase("modron"), + testcontainers.WithLogger(testcontainers.TestLogger(t)), + testcontainers.WithWaitStrategy( + wait.ForAll( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2). + WithStartupTimeout(6*time.Second), + wait.ForListeningPort("5432"), + ), + ), + testcontainers.WithHostPortAccess(5432), + ) +} + +func newPostgresTestDb(ctx context.Context, t *testing.T) model.Storage { + t.Helper() + pgDb, err := getPostgresDB(ctx, t) + if err != nil { + t.Fatalf("unable to create postgres container: %v", err) + } + connStr, err := pgDb.ConnectionString(ctx) + if err != nil { + t.Fatalf("unable to get connection string: %v", err) + } + st, err := NewPostgres(Config{ + BatchSize: 10, + }, connStr) + if err != nil { + t.Fatalf("failed to create storage: %v", err) + } + return st +} + +func TestPostgresStorageResource(t *testing.T) { + test.StorageResource(t, newPostgresTestDb(context.Background(), t)) +} + +func TestPostgresStorageObservation(t *testing.T) { + test.StorageObservation(t, newPostgresTestDb(context.Background(), t)) +} + +func TestPostgresStorageListObservationsActive(t *testing.T) { + test.StorageListObservations2(t, newPostgresTestDb(context.Background(), t)) +} diff --git a/src/storage/gormstorage/gorm_test.go b/src/storage/gormstorage/gorm_test.go new file mode 100644 index 0000000..ce3da03 --- /dev/null +++ b/src/storage/gormstorage/gorm_test.go @@ -0,0 +1,32 @@ +package gormstorage + +import ( + "testing" + + "github.com/nianticlabs/modron/src/model" + "github.com/nianticlabs/modron/src/storage/test" + storageutils "github.com/nianticlabs/modron/src/storage/utils" +) + +func newTestDb(t *testing.T) model.Storage { + st, err := NewSQLite(Config{ + BatchSize: 100, + LogAllQueries: true, + }, storageutils.GetSqliteMemoryDbPath()) + if err != nil { + t.Fatalf("failed to create storage: %v", err) + } + return st +} + +func TestStorageResource(t *testing.T) { + test.StorageResource(t, newTestDb(t)) +} + +func TestStorageObservation(t *testing.T) { + test.StorageObservation(t, newTestDb(t)) +} + +func TestStorageListObservationsActive(t *testing.T) { + test.StorageListObservations2(t, newTestDb(t)) +} diff --git a/src/storage/gormstorage/impact.go b/src/storage/gormstorage/impact.go new file mode 100644 index 0000000..31f5d7b --- /dev/null +++ b/src/storage/gormstorage/impact.go @@ -0,0 +1,31 @@ +package gormstorage + +import ( + "database/sql" + "database/sql/driver" + "fmt" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +type Impact pb.Impact + +func (i *Impact) Scan(src any) error { + str, ok := src.(string) + if !ok { + return fmt.Errorf("expected string, got %T", src) + } + v, ok := pb.Impact_value[str] + if !ok { + return fmt.Errorf("invalid Impact: %q", str) + } + *i = Impact(v) + return nil +} + +func (i Impact) Value() (driver.Value, error) { + return pb.Impact_name[int32(i)], nil +} + +var _ sql.Scanner = (*Impact)(nil) +var _ driver.Valuer = (*Impact)(nil) diff --git a/src/storage/gormstorage/model.go b/src/storage/gormstorage/model.go new file mode 100644 index 0000000..be6fc2d --- /dev/null +++ b/src/storage/gormstorage/model.go @@ -0,0 +1,133 @@ +package gormstorage + +import ( + "encoding/json" + "fmt" + "time" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +// Resource represents a resource entry in the database. +type Resource struct { + ID string `gorm:"column:resourceid;primaryKey"` + Name string `gorm:"column:resourcename;index:idx_resources_resourcename"` + DisplayName string `gorm:"column:display_name"` + ResourceGroupName string `gorm:"column:resourcegroupname;index:idx_resources_resourcegroupname;index:idx_resources_resourcetype_resourcegroupname;index:idx_collectionid_rgname"` + CollectionID string `gorm:"column:collectionid;index:idx_resources_collectionid;index:idx_collectionid_rgname"` + RecordTime *time.Time `gorm:"column:recordtime;index:idx_resource_recordtime"` + ParentName string `gorm:"column:parentname"` + Type string `gorm:"column:resourcetype;index:idx_resource_resourcetype;index:idx_resources_resourcetype_resourcegroupname"` + Labels json.RawMessage `gorm:"column:labels;type:jsonb"` + Tags json.RawMessage `gorm:"column:tags;type:jsonb"` + Proto []byte `gorm:"column:resourceproto"` +} + +// Observation represents an observation entry in the database. +type Observation struct { + ID string `gorm:"column:observationid;primaryKey"` + Name string `gorm:"column:observationname;not null"` + + // Observations can be associated with either a scan (result of a Modron rule engine execution) or + // a collection (result of fetching them from an external source) + ScanID *string `gorm:"column:scanid;index:idx_observation_scanid;index:idx_observation_resourcegroupname_scan"` + CollectionID *string `gorm:"column:collectionid;index:idx_observation_collectionid"` + + RecordTime time.Time `gorm:"column:recordtime;not null;index:idx_observation_recordtime"` + Proto []byte `gorm:"column:observationproto;not null"` + + // ResourceID is the UUID of the resource, as per the Resource table. + Resource *Resource `gorm:"foreignKey:ResourceID;references:ID"` + ResourceID *string `gorm:"column:resourceid"` + ResourceGroupName string `gorm:"column:resourcegroupname;not null;index:idx_observation_resourcegroupname;index:idx_observation_resourcegroupname_scan"` + ResourceExternalID *string `gorm:"default:null;index:idx_observation_resource_external_id"` + ResourceCloudPlatform string `gorm:"column:resourcecloudplatform;not null;default:'GCP'"` + ExternalID *string `gorm:"index:idx_observation_external_id"` + Source ObservationSource `gorm:"column:source;index:idx_observation_source"` + Category ObservationCategory `gorm:"column:category"` + // SeverityScore represents the original severity (as set by the rule / external observation provider) without taking into account he impact + SeverityScore *SeverityScore `gorm:"column:severity_score;default:null;index:idx_observation_severity_score"` + // Impact is calculated by looking at the Resource Group details (e.g: environment, tags) so that it can be used to calculate the Risk Score + Impact Impact `gorm:"column:impact;default:null"` + // RiskScore represents the final risk score calculated by using the SeverityScore and Impact + RiskScore *SeverityScore `gorm:"column:risk_score;default:null;index:idx_observation_risk_score"` +} + +// ToObservationProto converts an Observation to a pb.Observation. +func (row Observation) ToObservationProto() (*pb.Observation, error) { + obs := &pb.Observation{} + err := proto.Unmarshal(row.Proto, obs) + if err != nil { + return nil, fmt.Errorf("unmarshal observation proto: %w", err) + } + obs.Uid = row.ID + obs.Name = row.Name + obs.ScanUid = row.ScanID + obs.Timestamp = timestamppb.New(row.RecordTime) + + // DB fields + obs.ResourceRef = &pb.ResourceRef{ + Uid: row.ResourceID, + GroupName: row.ResourceGroupName, + CloudPlatform: cloudPlatformFromString(row.ResourceCloudPlatform), + ExternalId: row.ResourceExternalID, + } + obs.Severity = ToSeverity(row.SeverityScore) + obs.Source = pb.Observation_Source(row.Source) + obs.Category = pb.Observation_Category(row.Category) + return obs, nil +} + +func ToSeverity(score *SeverityScore) pb.Severity { + if score == nil { + return pb.Severity_SEVERITY_UNKNOWN + } + return score.ToSeverity() +} + +func cloudPlatformFromString(platform string) pb.CloudPlatform { + switch platform { + case "GCP": + return pb.CloudPlatform_GCP + case "AWS": + return pb.CloudPlatform_AWS + case "AZURE": + return pb.CloudPlatform_AZURE + } + return pb.CloudPlatform_PLATFORM_UNKNOWN +} + +// ToResourceProto converts a Resource to a pb.Resource. +func (row Resource) ToResourceProto() (*pb.Resource, error) { + res := &pb.Resource{} + err := proto.Unmarshal(row.Proto, res) + if err != nil { + return nil, fmt.Errorf("unmarshal resource proto: %w", err) + } + res.Uid = row.ID + res.Name = row.Name + res.ResourceGroupName = row.ResourceGroupName + res.CollectionUid = row.CollectionID + if row.RecordTime != nil { + res.Timestamp = timestamppb.New(*row.RecordTime) + } + res.Parent = row.ParentName + var labels map[string]string + if len(row.Labels) != 0 { + if err := json.Unmarshal(row.Labels, &labels); err != nil { + return nil, fmt.Errorf("unmarshal labels: %w", err) + } + res.Labels = labels + } + var tags map[string]string + if len(row.Tags) != 0 { + if err := json.Unmarshal(row.Tags, &tags); err != nil { + return nil, fmt.Errorf("unmarshal tags: %w", err) + } + res.Tags = tags + } + return res, nil +} diff --git a/src/storage/gormstorage/observation_category.go b/src/storage/gormstorage/observation_category.go new file mode 100644 index 0000000..f5f31f6 --- /dev/null +++ b/src/storage/gormstorage/observation_category.go @@ -0,0 +1,31 @@ +package gormstorage + +import ( + "database/sql" + "database/sql/driver" + "fmt" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +type ObservationCategory pb.Observation_Category + +func (o ObservationCategory) Value() (driver.Value, error) { + return pb.Observation_Category_name[int32(o)], nil +} + +func (o *ObservationCategory) Scan(src any) error { + str, ok := src.(string) + if !ok { + return fmt.Errorf("expected string, got %T", src) + } + v, ok := pb.Observation_Category_value[str] + if !ok { + return fmt.Errorf("invalid ObservationCategory: %q", str) + } + *o = ObservationCategory(v) + return nil +} + +var _ sql.Scanner = (*ObservationCategory)(nil) +var _ driver.Valuer = (*ObservationCategory)(nil) diff --git a/src/storage/gormstorage/observation_source.go b/src/storage/gormstorage/observation_source.go new file mode 100644 index 0000000..ecfb7ed --- /dev/null +++ b/src/storage/gormstorage/observation_source.go @@ -0,0 +1,31 @@ +package gormstorage + +import ( + "database/sql" + "database/sql/driver" + "fmt" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +type ObservationSource pb.Observation_Source + +func (o ObservationSource) Value() (driver.Value, error) { + return pb.Observation_Source_name[int32(o)], nil +} + +func (o *ObservationSource) Scan(src any) error { + str, ok := src.(string) + if !ok { + return fmt.Errorf("expected string, got %T", src) + } + v, ok := pb.Observation_Source_value[str] + if !ok { + return fmt.Errorf("invalid ObservationSource: %q", str) + } + *o = ObservationSource(v) + return nil +} + +var _ sql.Scanner = (*ObservationSource)(nil) +var _ driver.Valuer = (*ObservationSource)(nil) diff --git a/src/storage/gormstorage/operation.go b/src/storage/gormstorage/operation.go new file mode 100644 index 0000000..81a0c9f --- /dev/null +++ b/src/storage/gormstorage/operation.go @@ -0,0 +1,40 @@ +package gormstorage + +import ( + "database/sql/driver" + "fmt" + "time" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +var _ driver.Valuer = (*OperationStatus)(nil) + +type OperationStatus pb.Operation_Status + +func (o OperationStatus) Value() (driver.Value, error) { + return pb.Operation_Status(o).String(), nil +} + +func (o *OperationStatus) Scan(src interface{}) error { + str, ok := src.(string) + if !ok { + return fmt.Errorf("expected string, got %T", src) + } + v, ok := pb.Operation_Status_value[str] + if !ok { + return fmt.Errorf("invalid OperationStatus: %q", str) + } + *o = OperationStatus(v) + return nil +} + +type Operation struct { + ID string `gorm:"column:operationid;primaryKey;index:operations_idx"` + ResourceGroup string `gorm:"column:resourcegroupname;index;primaryKey;index:operations_idx;index:operations_rgname_endtime"` + OpsType string `gorm:"column:opstype;index;primaryKey;index:operations_idx"` + StartTime time.Time `gorm:"column:starttime;index"` + EndTime *time.Time `gorm:"column:endtime;index;index:operations_idx;index:operations_rgname_endtime"` + Status OperationStatus `gorm:"index;index:operations_idx"` + Reason string +} diff --git a/src/storage/gormstorage/severity.go b/src/storage/gormstorage/severity.go new file mode 100644 index 0000000..de953ef --- /dev/null +++ b/src/storage/gormstorage/severity.go @@ -0,0 +1,59 @@ +package gormstorage + +import ( + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +// SeverityScore represents a score for the severity - this number is in the 0.0 - 10.0 range. +type SeverityScore float32 + +const ( + SeverityLowMax = 3.9 + SeverityMediumMax = 6.9 + SeverityHighMax = 8.9 + + SeverityMin = 0.0 + SeverityMax = 10.0 +) + +func (s SeverityScore) ToSeverity() pb.Severity { + if s < 0 { + // This should never happen (SeverityScore should be nil if anything) + return pb.Severity_SEVERITY_UNKNOWN + } + if s == SeverityMin { + return pb.Severity_SEVERITY_INFO + } + if s <= SeverityLowMax { + return pb.Severity_SEVERITY_LOW + } + if s <= SeverityMediumMax { + return pb.Severity_SEVERITY_MEDIUM + } + if s <= SeverityHighMax { + return pb.Severity_SEVERITY_HIGH + } + return pb.Severity_SEVERITY_CRITICAL +} + +func FromSeverityPb(severity pb.Severity) *SeverityScore { + switch severity { + case pb.Severity_SEVERITY_INFO: + info := SeverityScore(0.0) + return &info + case pb.Severity_SEVERITY_LOW: + low := SeverityScore(SeverityLowMax) + return &low + case pb.Severity_SEVERITY_MEDIUM: + medium := SeverityScore(SeverityMediumMax) + return &medium + case pb.Severity_SEVERITY_HIGH: + high := SeverityScore(SeverityHighMax) + return &high + case pb.Severity_SEVERITY_CRITICAL: + critical := SeverityScore(SeverityMax) + return &critical + } + unknownScore := SeverityScore(-1) + return &unknownScore +} diff --git a/src/storage/memstorage/memstorage.go b/src/storage/memstorage/memstorage.go index 7d618f3..aa2c60e 100644 --- a/src/storage/memstorage/memstorage.go +++ b/src/storage/memstorage/memstorage.go @@ -1,300 +1,29 @@ -// Package memstorage provides a storage backend that runs locally in memory. It is supposed to be used primarily for API testing. package memstorage import ( - "context" - "fmt" - "sync" - "time" + "os" + + "github.com/sirupsen/logrus" - "github.com/golang/glog" - "github.com/nianticlabs/modron/src/common" "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + "github.com/nianticlabs/modron/src/storage/gormstorage" + storageutils "github.com/nianticlabs/modron/src/storage/utils" ) -type MemStorage struct { - resources sync.Map - observations sync.Map - operations sync.Map - mostRecentScanID sync.Map -} - -func New() model.Storage { - return &MemStorage{ - resources: sync.Map{}, - observations: sync.Map{}, - operations: sync.Map{}, - mostRecentScanID: sync.Map{}, - } -} - -func (mem *MemStorage) BatchCreateResources(ctx context.Context, resources []*pb.Resource) ([]*pb.Resource, error) { - for _, resource := range resources { - existingRes, ok := mem.resources.Load(resource.ResourceGroupName) - if !ok { - existingRes = []*pb.Resource{} - } - mem.resources.Store(resource.ResourceGroupName, append(existingRes.([]*pb.Resource), resource)) - } - return resources, nil -} - -func (mem *MemStorage) BatchCreateObservations(ctx context.Context, observations []*pb.Observation) ([]*pb.Observation, error) { - for _, o := range observations { - if o.Resource == nil { - glog.Warningf("can't store observation with no attached resource: %+v", o) - continue - } - existingObs, ok := mem.observations.Load(o.Resource.ResourceGroupName) - if !ok { - existingObs = []*pb.Observation{} - } - mem.observations.Store(o.Resource.ResourceGroupName, append(existingObs.([]*pb.Observation), o)) - } - return observations, nil -} - -func (mem *MemStorage) ListResources(ctx context.Context, filter model.StorageFilter) ([]*pb.Resource, error) { - // Resource group filter - latestRes := map[string][]*pb.Resource{} - if filter.ResourceGroupNames == nil { - mem.resources.Range(func(k, v any) bool { - if filteredV, err := filterRes(v.([]*pb.Resource), filter); err != nil { - return false - } else { - latestRes[k.(string)] = filteredV - } - return true - }) - } else { - for _, n := range filter.ResourceGroupNames { - res, ok := mem.resources.Load(n) - if !ok { - continue - } - if filteredV, err := filterRes(res.([]*pb.Resource), filter); err != nil { - return nil, err - } else { - latestRes[n] = filteredV - } - } - } - result := flatValues(latestRes) - if filter.Limit > 0 { - if len(result) > filter.Limit { - return result[:filter.Limit], nil - } - } - return result, nil -} - -func (mem *MemStorage) ListObservations(ctx context.Context, filter model.StorageFilter) ([]*pb.Observation, error) { - // Resource group filter - latestObs := map[string][]*pb.Observation{} - if filter.ResourceGroupNames == nil { - mem.observations.Range(func(k, v any) bool { - if filteredV, err := mem.filterObs(v.([]*pb.Observation), filter); err != nil { - return false - } else { - latestObs[k.(string)] = filteredV - } - return true - }) - } else { - for _, n := range filter.ResourceGroupNames { - ob, ok := mem.observations.Load(n) - if !ok { - continue - } - if filteredV, err := mem.filterObs(ob.([]*pb.Observation), filter); err != nil { - return nil, err - } else { - latestObs[n] = filteredV - } - } - } - - result := flatValues(latestObs) - if filter.Limit > 0 { - if len(result) > filter.Limit { - fmt.Println(filter.Limit) - return result[:filter.Limit], nil - } - } - return result, nil -} - -func (mem *MemStorage) AddOperationLog(ctx context.Context, ops []model.Operation) error { - for _, o := range ops { - mem.operations.Store(o.ID, o) - // Here we assume that operation are always added in chronological order. - if o.OpsType == "scan" && o.Status == model.OperationCompleted { - mem.mostRecentScanID.Store(o.ResourceGroup, o.ID) - } - } - return nil -} - -func (mem *MemStorage) PurgeIncompleteOperations(ctx context.Context) error { - mem.operations = sync.Map{} - return nil -} - -func (mem *MemStorage) FlushOpsLog(ctx context.Context) error { - return nil -} - -func (mem *MemStorage) filterObs(obs []*pb.Observation, filter model.StorageFilter) ([]*pb.Observation, error) { - res := []*pb.Observation{} +const DefaultBatchSize = 100 - if len(obs) == 0 { - return res, nil - } - - // Here we assume that the observations are sorted by date. - for i := len(obs) - 1; i >= 0; i-- { - // TODO: This will fail if we insert observation without a corresponding scan. - mostRecentScanID, ok := mem.mostRecentScanID.Load(obs[i].Resource.ResourceGroupName) - if !ok { - glog.Warningf("no scan found, but observation exist: %v", obs[i]) - continue - } - appendResource := true - if obs[i].ScanUid != mostRecentScanID { - continue - } - if filter.ResourceTypes != nil { - t, err := common.TypeFromResourceAsString(obs[i].Resource) - if err != nil { - return nil, err - } - if _, ok := toSet(filter.ResourceTypes)[t]; !ok { - appendResource = false - } - } - if filter.ResourceIDs != nil { - if _, ok := toSet(filter.ResourceIDs)[obs[i].Resource.Uid]; !ok { - appendResource = false - } - } - if filter.ResourceNames != nil && len(filter.ResourceNames) > 0 { - if _, ok := toSet(filter.ResourceNames)[obs[i].Resource.Name]; !ok { - appendResource = false - } - } - if filter.ParentNames != nil { - if _, ok := toSet(filter.ParentNames)[obs[i].Resource.Parent]; !ok { - appendResource = false - } - } - if filter.ResourceGroupNames != nil { - if _, ok := toSet(filter.ResourceGroupNames)[obs[i].Resource.ResourceGroupName]; !ok { - appendResource = false - } - } - if !filter.StartTime.IsZero() || filter.TimeOffset != 0 { - if !filter.StartTime.IsZero() && filter.TimeOffset != 0 { - timeStamp := obs[i].Timestamp.AsTime() - start, end := extractStartAndEndTimes(filter) - if !timeStamp.After(start) || !timeStamp.Before(end) { - appendResource = false - } - } else { - return nil, fmt.Errorf("StartTime and TimeOffset must both be set") - } - } - if appendResource { - res = append(res, obs[i]) - } - } - return res, nil -} - -func filterRes(resources []*pb.Resource, filter model.StorageFilter) ([]*pb.Resource, error) { - res := []*pb.Resource{} - if len(resources) == 0 { - return res, nil - } +var logger = logrus.StandardLogger() - // Here we assume that the resources are sorted by date. - mostRecentCollectID := resources[len(resources)-1].CollectionUid - for i := len(resources) - 1; i >= 0; i-- { - appendResource := true - if mostRecentCollectID != resources[i].CollectionUid { - break - } - if filter.ResourceTypes != nil { - t, err := common.TypeFromResourceAsString(resources[i]) - if err != nil { - return nil, err - } - if _, ok := toSet(filter.ResourceTypes)[t]; !ok { - appendResource = false - } - } - if filter.ResourceIDs != nil { - if _, ok := toSet(filter.ResourceIDs)[resources[i].Uid]; !ok { - appendResource = false - } - } - if filter.ResourceNames != nil { - if _, ok := toSet(filter.ResourceNames)[resources[i].Name]; !ok { - appendResource = false - } - } - if filter.ParentNames != nil { - if _, ok := toSet(filter.ParentNames)[resources[i].Parent]; !ok { - appendResource = false - } - } - if filter.ResourceGroupNames != nil { - if _, ok := toSet(filter.ResourceGroupNames)[resources[i].ResourceGroupName]; !ok { - appendResource = false - } - } - if !filter.StartTime.IsZero() || filter.TimeOffset != 0 { - if !filter.StartTime.IsZero() && filter.TimeOffset != 0 { - timeStamp := resources[i].GetTimestamp().AsTime() - start, end := extractStartAndEndTimes(filter) - if !timeStamp.After(start) || !timeStamp.Before(end) { - appendResource = false - } - } else { - return nil, fmt.Errorf("StartTime and TimeOffset must both be set") - } - } - if appendResource { - res = append(res, resources[i]) - } - } - return res, nil -} - -func extractStartAndEndTimes(filter model.StorageFilter) (start time.Time, end time.Time) { - startTimeF, offsetTimeF := filter.StartTime, filter.StartTime.Add(filter.TimeOffset) - if startTimeF.Before(offsetTimeF) { - start = startTimeF - end = offsetTimeF - } else { - end = startTimeF - start = offsetTimeF - } - return start, end -} - -func flatValues[T interface{}](m map[string][]T) []T { - res := []T{} - for _, v := range m { - res = append(res, v...) - } - return res -} - -func toSet[T comparable](arr []T) map[T]struct{} { - res := map[T]struct{}{} - for _, e := range arr { - res[e] = struct{}{} - } - return res +func New() model.Storage { + dbPath := storageutils.GetSqliteMemoryDbPath() + logger.Debugf("Using SQLite storage with path: %s", dbPath) + st, err := gormstorage.NewSQLite(gormstorage.Config{ + BatchSize: DefaultBatchSize, + LogAllQueries: os.Getenv("LOG_ALL_SQL_QUERIES") == "true", + }, dbPath) + if err != nil { + // It's fine to panic here, memstorage should only be used in tests + panic(err) + } + return st } diff --git a/src/storage/storage.go b/src/storage/storage.go new file mode 100644 index 0000000..820312b --- /dev/null +++ b/src/storage/storage.go @@ -0,0 +1,8 @@ +package storage + +type Type string + +const ( + SQL Type = "sql" + Memory Type = "memory" +) diff --git a/src/storage/test/test.go b/src/storage/test/test.go index 073d517..5720833 100644 --- a/src/storage/test/test.go +++ b/src/storage/test/test.go @@ -1,4 +1,4 @@ -// Package storage provides a storage backend +// Package test is a collection of test utils for storage tests package test import ( @@ -8,8 +8,11 @@ import ( "testing" "time" + "google.golang.org/protobuf/types/known/structpb" + "github.com/nianticlabs/modron/src/model" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" + "github.com/nianticlabs/modron/src/utils" "github.com/google/go-cmp/cmp" "github.com/google/uuid" @@ -17,39 +20,42 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) -// this function checks if the two ResourceEntry objects are equal +const ( + oneDay = 24 * time.Hour +) + func AreEqualResources(t *testing.T, want []*pb.Resource, got []*pb.Resource) { t.Helper() - sort := cmp.Transformer("sort", func(in []*pb.Resource) []*pb.Resource { + sortResources := cmp.Transformer("sort", func(in []*pb.Resource) []*pb.Resource { out := append([]*pb.Resource{}, in...) sort.SliceStable(out, func(i, j int) bool { return out[i].Uid < out[j].Uid }) return out }) - if diff := cmp.Diff(want, got, protocmp.Transform(), sort); diff != "" { + if diff := cmp.Diff(want, got, protocmp.Transform(), sortResources); diff != "" { t.Errorf("unexpected diff (-want, +got): %v", diff) } } func AreEqualObservations(t *testing.T, want []*pb.Observation, got []*pb.Observation) { t.Helper() - sort := cmp.Transformer("sort", func(in []*pb.Observation) []*pb.Observation { + sortObservations := cmp.Transformer("sort", func(in []*pb.Observation) []*pb.Observation { out := append([]*pb.Observation{}, in...) sort.SliceStable(out, func(i, j int) bool { return out[i].Uid < out[j].Uid }) return out }) - if diff := cmp.Diff(want, got, protocmp.Transform(), sort); diff != "" { + if diff := cmp.Diff(want, got, protocmp.Transform(), sortObservations); diff != "" { t.Errorf("unexpected diff (-want, +got): %v", diff) } } // TODO: don't check length of list, but compare the actual returned arrays -func TestStorageResource(t *testing.T, storage model.Storage) { +func StorageResource(t *testing.T, storage model.Storage) { ctx := context.Background() - collectionId := uuid.NewString() + collectionID := uuid.NewString() testResourceName := fmt.Sprintf("test-%s", uuid.NewString()) testResourceName2 := fmt.Sprintf("test2-%s", uuid.NewString()) parentResourceName := fmt.Sprintf("test-parent-%s", uuid.NewString()) @@ -58,8 +64,8 @@ func TestStorageResource(t *testing.T, storage model.Storage) { Name: testResourceName, Parent: parentResourceName, ResourceGroupName: resourceGroupName1, - CollectionUid: collectionId, - Timestamp: timestamppb.New(time.Now().Add(-time.Hour * 24)), + CollectionUid: collectionID, + Timestamp: timestamppb.New(time.Now().Add(-oneDay)), Type: &pb.Resource_ApiKey{ ApiKey: &pb.APIKey{ Scopes: []string{"TEST1"}, @@ -72,8 +78,8 @@ func TestStorageResource(t *testing.T, storage model.Storage) { Name: testResourceName2, Parent: parentResourceName, ResourceGroupName: resourceGroupName2, - CollectionUid: collectionId, - Timestamp: timestamppb.New(time.Now().Add(-time.Hour * 24)), + CollectionUid: collectionID, + Timestamp: timestamppb.New(time.Now().Add(-oneDay)), Type: &pb.Resource_ApiKey{ ApiKey: &pb.APIKey{ Scopes: []string{"TEST2"}, @@ -81,34 +87,35 @@ func TestStorageResource(t *testing.T, storage model.Storage) { }, } - testOps := []model.Operation{ + now := time.Now() + testOps := []*pb.Operation{ { - ID: collectionId, + Id: collectionID, ResourceGroup: resourceGroupName1, - OpsType: "collection", - StatusTime: time.Now(), - Status: model.OperationStarted, + Type: "collection", + StatusTime: timestamppb.New(now), + Status: pb.Operation_STARTED, }, { - ID: collectionId, + Id: collectionID, ResourceGroup: resourceGroupName2, - OpsType: "collection", - StatusTime: time.Now(), - Status: model.OperationStarted, + Type: "collection", + StatusTime: timestamppb.New(now), + Status: pb.Operation_STARTED, }, { - ID: collectionId, + Id: collectionID, ResourceGroup: resourceGroupName1, - OpsType: "collection", - StatusTime: time.Now().Add(time.Second * 60), - Status: model.OperationCompleted, + Type: "collection", + StatusTime: timestamppb.New(now), + Status: pb.Operation_COMPLETED, }, { - ID: collectionId, + Id: collectionID, ResourceGroup: resourceGroupName2, - OpsType: "collection", - StatusTime: time.Now().Add(time.Second * 60), - Status: model.OperationCompleted, + Type: "collection", + StatusTime: timestamppb.New(now), + Status: pb.Operation_COMPLETED, }, } @@ -176,8 +183,8 @@ func TestStorageResource(t *testing.T, storage model.Storage) { if err != nil { t.Errorf("ListResources(ctx, filter) error: %v", err) } - if len(allResources) != 2 { - t.Errorf("len(allResources) got %d, want %d", len(allResources), 2) + if len(allResources) != 2 { //nolint:mnd + t.Errorf("len(allResources) got %d, want %d", len(allResources), 2) //nolint:mnd } // only get one element @@ -218,8 +225,8 @@ func TestStorageResource(t *testing.T, storage model.Storage) { if err != nil { t.Errorf("ListResources(ctx, %v) error: %v", resourceNameFilter, err) } - if len(allResources) != 2 { - t.Errorf("len(allResources) got %d, want %d", len(allResources), 2) + if len(allResources) != 2 { //nolint:mnd + t.Errorf("len(allResources) got %d, want %d", len(allResources), 2) //nolint:mnd } // filter non-existing resourceType @@ -236,8 +243,8 @@ func TestStorageResource(t *testing.T, storage model.Storage) { if err != nil { t.Errorf("ListResources(ctx, %v) error: %v", resourceGroup1Filter, err) } - if len(allResources) != 2 { - t.Errorf("len(allResources) got %d, want %d", len(allResources), 2) + if len(allResources) != 2 { //nolint:mnd + t.Errorf("len(allResources) got %d, want %d", len(allResources), 2) //nolint:mnd } allResources, err = storage.ListResources(ctx, resourceGroup2AndNameFilter) @@ -249,7 +256,7 @@ func TestStorageResource(t *testing.T, storage model.Storage) { } } -func TestStorageObservation(t *testing.T, storage model.Storage) { +func StorageObservation(t *testing.T, storage model.Storage) { ctx := context.Background() parentResourceName := "test-parent" testResourceName := "testResourceName" @@ -284,19 +291,27 @@ func TestStorageObservation(t *testing.T, storage model.Storage) { } testObservation1 := &pb.Observation{ - Uid: "observation1", - Resource: testResource, - Name: "testObservation1", - ScanUid: firstScanUID, - Timestamp: timestamppb.Now(), + Uid: "observation1", + ResourceRef: utils.GetResourceRef(testResource), + Name: "testObservation1", + ScanUid: utils.RefOrNull(firstScanUID), + Timestamp: timestamppb.Now(), + Source: pb.Observation_SOURCE_MODRON, + Severity: pb.Severity_SEVERITY_HIGH, + RiskScore: pb.Severity_SEVERITY_HIGH, + Impact: pb.Impact_IMPACT_MEDIUM, } testObservation2 := &pb.Observation{ - Uid: "observation2", - Resource: testResource2, - Name: "testObservation2", - ScanUid: firstScanUID, - Timestamp: timestamppb.Now(), + Uid: "observation2", + ResourceRef: utils.GetResourceRef(testResource2), + Name: "testObservation2", + ScanUid: utils.RefOrNull(firstScanUID), + Timestamp: timestamppb.Now(), + Source: pb.Observation_SOURCE_MODRON, + Severity: pb.Severity_SEVERITY_HIGH, + RiskScore: pb.Severity_SEVERITY_HIGH, + Impact: pb.Impact_IMPACT_MEDIUM, } // Filters @@ -305,39 +320,44 @@ func TestStorageObservation(t *testing.T, storage model.Storage) { ResourceGroupNames: []string{testResourceGroupName2}, } - testOps := []model.Operation{ + testOps := []*pb.Operation{ { - ID: firstScanUID, + Id: firstScanUID, ResourceGroup: testResourceGroupName1, - OpsType: "scan", - StatusTime: firstScanTime, - Status: model.OperationStarted, + Type: "scan", + StatusTime: timestamppb.New(firstScanTime), + Status: pb.Operation_STARTED, }, { - ID: firstScanUID, + Id: firstScanUID, ResourceGroup: testResourceGroupName2, - OpsType: "scan", - StatusTime: firstScanTime, - Status: model.OperationStarted, + Type: "scan", + StatusTime: timestamppb.New(firstScanTime), + Status: pb.Operation_STARTED, }, { - ID: firstScanUID, + Id: firstScanUID, ResourceGroup: testResourceGroupName1, - OpsType: "scan", - StatusTime: firstScanTime, - Status: model.OperationCompleted, + Type: "scan", + StatusTime: timestamppb.New(firstScanTime), + Status: pb.Operation_COMPLETED, }, { - ID: firstScanUID, + Id: firstScanUID, ResourceGroup: testResourceGroupName2, - OpsType: "scan", - StatusTime: firstScanTime, - Status: model.OperationCompleted, + Type: "scan", + StatusTime: timestamppb.New(firstScanTime), + Status: pb.Operation_COMPLETED, }, } addOps(ctx, t, storage, testOps) + _, err := storage.BatchCreateResources(ctx, []*pb.Resource{testResource, testResource2}) + if err != nil { + t.Fatalf("BatchCreateResources: %v", err) + } + // should not error with empty storage allObservations, err := storage.ListObservations(ctx, model.StorageFilter{ ResourceGroupNames: []string{testResourceGroupName1, testResourceGroupName2}, @@ -392,30 +412,32 @@ func TestStorageObservation(t *testing.T, storage model.Storage) { AreEqualObservations(t, []*pb.Observation{testObservation2}, allObservations) // Run a second scan - secondScanOps := []model.Operation{ + secondScanOps := []*pb.Operation{ { - ID: secondScanUID, + Id: secondScanUID, ResourceGroup: testResourceGroupName1, - OpsType: "scan", - StatusTime: secondScanTime, - Status: model.OperationStarted, + Type: "scan", + StatusTime: timestamppb.New(secondScanTime), + Status: pb.Operation_STARTED, }, { - ID: secondScanUID, + Id: secondScanUID, ResourceGroup: testResourceGroupName1, - OpsType: "scan", - StatusTime: secondScanTime, - Status: model.OperationCompleted, + Type: "scan", + StatusTime: timestamppb.New(secondScanTime), + Status: pb.Operation_COMPLETED, }, } addOps(ctx, t, storage, secondScanOps) testObservationSecondScan := &pb.Observation{ - Uid: "observation3", - Resource: testResource, - Name: "testObservationSecondScan", - ScanUid: secondScanUID, - Timestamp: timestamppb.Now(), + Uid: "observation3", + ResourceRef: utils.GetResourceRef(testResource), + Name: "testObservationSecondScan", + ScanUid: utils.RefOrNull(secondScanUID), + Timestamp: timestamppb.Now(), + Source: pb.Observation_SOURCE_MODRON, + Severity: pb.Severity_SEVERITY_LOW, } _, err = storage.BatchCreateObservations(ctx, []*pb.Observation{testObservationSecondScan}) if err != nil { @@ -434,8 +456,170 @@ func TestStorageObservation(t *testing.T, storage model.Storage) { AreEqualObservations(t, wantObs, gotObs) } -func addOps(ctx context.Context, t *testing.T, storage model.Storage, ops []model.Operation) { - if err := storage.AddOperationLog(context.Background(), ops); err != nil { +func StorageListObservations2(t *testing.T, storage model.Storage) { + ctx := context.Background() + scanUUID := uuid.NewString() + collectionUUID := uuid.NewString() + startTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) + endTime := startTime.Add(time.Minute) + + addOps(ctx, t, storage, []*pb.Operation{ + { + Id: collectionUUID, + ResourceGroup: "projects/test-1", + // IMPORTANT: We use "collect" here because the collection of observations from SCC + // is part of the "collection" step - thus we need to make sure that the storage is aware of the fact + // that observations can either come from a "collection" or a "scan" (and they should be merged). + Type: "collection", + StatusTime: timestamppb.New(startTime), + Status: pb.Operation_STARTED, + Reason: "", + }, + { + Id: scanUUID, + ResourceGroup: "projects/test-1", + // IMPORTANT: We use "scan" here because we pretend that we've run a Modron scan that generated + // Modron observations, and these appear only after a "scan" operation. + Type: "scan", + StatusTime: timestamppb.New(startTime), + Status: pb.Operation_STARTED, + Reason: "", + }, + }) + flushOps(ctx, t, storage) + + // Create a resource + resourceUUID := uuid.NewString() + rsrc := &pb.Resource{ + Uid: resourceUUID, + Name: "custom-resource", + Type: &pb.Resource_ApiKey{ApiKey: &pb.APIKey{Scopes: []string{"this", "is", "an", "example"}}}, + } + _, err := storage.BatchCreateResources(ctx, []*pb.Resource{rsrc}) + if err != nil { + t.Fatalf("BatchCreateResources: %v", err) + } + + // Create an observation + scanTs := startTime.Add(10 * time.Second) //nolint:mnd + originalModronObs := &pb.Observation{ + Uid: uuid.NewString(), + ScanUid: utils.RefOrNull(scanUUID), + Timestamp: timestamppb.New(scanTs), + Name: "MY_CUSTOM_OBSERVATION", + ExpectedValue: structpb.NewStringValue("expected"), + ObservedValue: structpb.NewStringValue("observed"), + Remediation: &pb.Remediation{ + Description: "Desc", + Recommendation: "Recommendation", + }, + ResourceRef: &pb.ResourceRef{ + Uid: &resourceUUID, + GroupName: "projects/test-1", + ExternalId: utils.RefOrNull("//cloud.google.com/example"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + + Source: pb.Observation_SOURCE_MODRON, + Severity: pb.Severity_SEVERITY_INFO, + Impact: pb.Impact_IMPACT_MEDIUM, + RiskScore: pb.Severity_SEVERITY_INFO, + } + + unknownSeverityObs := &pb.Observation{ + Uid: uuid.NewString(), + ScanUid: utils.RefOrNull(scanUUID), + Timestamp: timestamppb.New(scanTs), + Name: "UNKNOWN_SEVERITY_OBSERVATIONS_WILL_NEVER_SHOW_UP", + ExpectedValue: structpb.NewStringValue("expected"), + ObservedValue: structpb.NewStringValue("observed"), + Remediation: &pb.Remediation{ + Description: "Desc", + Recommendation: "Recommendation", + }, + ResourceRef: &pb.ResourceRef{ + Uid: &resourceUUID, + GroupName: "projects/test-1", + ExternalId: utils.RefOrNull("//cloud.google.com/example"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + + Source: pb.Observation_SOURCE_MODRON, + Severity: pb.Severity_SEVERITY_UNKNOWN, + Impact: pb.Impact_IMPACT_HIGH, + RiskScore: pb.Severity_SEVERITY_UNKNOWN, + } + + sccObs := &pb.Observation{ + Uid: uuid.NewString(), + CollectionId: utils.RefOrNull(collectionUUID), + Timestamp: timestamppb.New(scanTs), + Name: "GCP_STORAGE_BUCKET_READABLE", + ExpectedValue: nil, + ObservedValue: nil, + Remediation: &pb.Remediation{ + Description: "A world readable GCP Storage bucket was discovered which may contain potentially sensitive data.", + Recommendation: "Investigate whether this bucket should be readable, and if not, adjust the permissions.", + }, + ResourceRef: &pb.ResourceRef{ + Uid: nil, + GroupName: "projects/test-1", + ExternalId: utils.RefOrNull("//storage.googleapis.com/test-dev-example-public"), + CloudPlatform: pb.CloudPlatform_GCP, + }, + ExternalId: utils.RefOrNull("//securitycenter.googleapis.com/projects/12345/sources/123/findings/42000000"), + Source: pb.Observation_SOURCE_SCC, + Category: pb.Observation_CATEGORY_MISCONFIGURATION, + Severity: pb.Severity_SEVERITY_LOW, + Impact: pb.Impact_IMPACT_HIGH, + RiskScore: pb.Severity_SEVERITY_MEDIUM, + } + + listObs := []*pb.Observation{sccObs, originalModronObs, unknownSeverityObs} + _, err = storage.BatchCreateObservations(ctx, listObs) + if err != nil { + t.Fatalf("BatchCreateObservations(ctx, %v) error: %v", listObs, err) + } + addOps(ctx, t, storage, []*pb.Operation{ + { + Id: collectionUUID, + ResourceGroup: "projects/test-1", + Type: "collection", + StatusTime: timestamppb.New(endTime), + Status: pb.Operation_COMPLETED, + Reason: "", + }, + { + Id: scanUUID, + ResourceGroup: "projects/test-1", + Type: "scan", + StatusTime: timestamppb.New(endTime), + Status: pb.Operation_COMPLETED, + Reason: "", + }, + }) + flushOps(ctx, t, storage) + + // Sorted by severity + want := []*pb.Observation{sccObs, originalModronObs} + got, err := storage.ListObservations(ctx, model.StorageFilter{}) + if err != nil { + t.Errorf("ListObservations(ctx, %v) error: %v", model.StorageFilter{}, err) + } + + if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" { + t.Errorf("unexpected diff (-want, +got): %v", diff) + } +} + +func flushOps(ctx context.Context, t *testing.T, storage model.Storage) { + if err := storage.FlushOpsLog(ctx); err != nil { + t.Fatalf("Flushops: %v", err) + } +} + +func addOps(ctx context.Context, t *testing.T, storage model.Storage, ops []*pb.Operation) { + if err := storage.AddOperationLog(ctx, ops); err != nil { t.Fatalf("AddOperation unexpected error: %v", err) } if err := storage.FlushOpsLog(ctx); err != nil { diff --git a/src/storage/utils/utils.go b/src/storage/utils/utils.go new file mode 100644 index 0000000..8b11b5b --- /dev/null +++ b/src/storage/utils/utils.go @@ -0,0 +1,19 @@ +package storageutils + +import ( + "fmt" + "os" + + "github.com/google/uuid" +) + +func GetSqliteMemoryDbPath() string { + debugDbPath := os.Getenv("DEBUG_DB_PATH") + if debugDbPath != "" { + return debugDbPath + } + // We use an uniqueID, so that two tests running in parallel do not conflict with each other + uniqueID := uuid.NewString() + // Do not use `:memory:` here! https://github.com/mattn/go-sqlite3/issues/204 + return fmt.Sprintf("file:%s?mode=memory&cache=shared", uniqueID) +} diff --git a/src/test/e2e_test.go b/src/test/e2e_test.go index 97c5f83..7399b1e 100644 --- a/src/test/e2e_test.go +++ b/src/test/e2e_test.go @@ -2,9 +2,14 @@ package e2e import ( "context" + "crypto/ed25519" + "crypto/rand" "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" "flag" "fmt" + "math/big" "net" "os" "reflect" @@ -24,7 +29,7 @@ import ( "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" - "github.com/nianticlabs/modron/src/pb" + pb "github.com/nianticlabs/modron/src/proto/generated" ) func init() { @@ -46,16 +51,16 @@ var projectListFile string func runFakeNotificationService(t *testing.T, port int64) { t.Helper() - lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) + lis, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) if err != nil { t.Fatalf("cannot listen on port %d: %v", port, err) } - srvCert, err := tls.LoadX509KeyPair("cert.pem", "key.pem") + tlsCert, err := getTLSCert() if err != nil { - panic(fmt.Sprintln("load certificate: ", err)) + panic(fmt.Sprintln("generate certificate: ", err)) } grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{srvCert}, + Certificates: []tls.Certificate{tlsCert}, ClientAuth: tls.NoClientCert, }))) svc := New() @@ -66,12 +71,53 @@ func runFakeNotificationService(t *testing.T, port int64) { } } +func getTLSCert() (tls.Certificate, error) { + public, private, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return tls.Certificate{}, err + } + x509Cert := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Country: []string{"US"}, + CommonName: "modron", + }, + } + cert, err := x509.CreateCertificate(rand.Reader, x509Cert, x509Cert, public, private) + if err != nil { + return tls.Certificate{}, err + } + return tls.Certificate{Certificate: [][]byte{cert}, PrivateKey: private}, nil +} + +// TODO: deterministically sort the observations to avoid flaky tests func testModronE2e(t *testing.T, addr string, resourceGroupNames []string, want map[string][]*structpb.Value) { flag.Parse() ctx := context.Background() go func() { runFakeNotificationService(t, extractNotificationServicePortFromEnvironment(t)) }() + + // Wait for backend to be available + var backendAvailable bool + maxTries := 10 + for i := 0; i < maxTries; i++ { + conn, err := net.Dial("tcp", addr) + if err != nil { + fmt.Printf("waiting for backend to be available: %v\n", err) + time.Sleep(time.Second) + continue + } + fmt.Printf("backend is available\n") + backendAvailable = true + conn.Close() + break + } + + if !backendAvailable { + t.Fatalf("backend is not available after %d tries", maxTries) + } + var opts []grpc.DialOption opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) @@ -94,7 +140,7 @@ func testModronE2e(t *testing.T, addr string, resourceGroupNames []string, want } } - allObs := []*pb.Observation{} + var allObs []*pb.Observation for _, el := range listObsResponse.ResourceGroupsObservations { for _, rules := range el.RulesObservations { allObs = append(allObs, rules.Observations...) @@ -103,30 +149,21 @@ func testModronE2e(t *testing.T, addr string, resourceGroupNames []string, want if len(allObs) < 1 { t.Fatalf("no observations returned") } + + // TODO: use a comparer instead of this got := map[string][]*structpb.Value{} for _, ob := range allObs { - if _, ok := got[ob.Resource.Name]; !ok { - got[ob.Resource.Name] = []*structpb.Value{} + if ob.ResourceRef.ExternalId == nil { + t.Errorf("observation %+v has no externalId", ob) + continue } - temp := got[ob.Resource.Name] - got[ob.Resource.Name] = append(temp, ob.ExpectedValue) - } - for k, v := range got { - if diff := cmp.Diff(want[k], v, protocmp.Transform()); diff != "" { - t.Errorf("Resource %v has unexpected observations (-want, +got): %v", k, diff) + if _, ok := got[*ob.ResourceRef.ExternalId]; !ok { + got[*ob.ResourceRef.ExternalId] = []*structpb.Value{} } + temp := got[*ob.ResourceRef.ExternalId] + got[*ob.ResourceRef.ExternalId] = append(temp, ob.ExpectedValue) } - // TODO extract this to its own test - manualObservation := &pb.Observation{ - Resource: allObs[0].GetResource(), - Name: "test-observation", - ObservedValue: structpb.NewStringValue("test observation"), - Remediation: &pb.Remediation{ - Description: "test observation", - Recommendation: "test observation, no recommendation", - }, - } cmpOpts := cmp.Options{ protocmp.Transform(), cmpopts.EquateApproxTime(time.Second), @@ -147,7 +184,26 @@ func testModronE2e(t *testing.T, addr string, resourceGroupNames []string, want return time.Unix(t["seconds"].(int64), 0).UTC() }), ), + cmpopts.SortSlices(func(a, b *structpb.Value) bool { + return a.GetStringValue() < b.GetStringValue() + }), + protocmp.IgnoreFields(&pb.Observation{}, "uid"), + } + if diff := cmp.Diff(want, got, cmpOpts); diff != "" { + t.Errorf("Resources have unexpected observations (-want, +got): %v", diff) } + + // TODO extract this to its own test + manualObservation := &pb.Observation{ + ResourceRef: allObs[0].GetResourceRef(), + Name: "test-observation", + ObservedValue: structpb.NewStringValue("test observation"), + Remediation: &pb.Remediation{ + Description: "test observation", + Recommendation: "test observation, no recommendation", + }, + } + manualObservation.Timestamp = timestamppb.Now() gotManualObs, err := client.CreateObservation(ctx, &pb.CreateObservationRequest{Observation: manualObservation}) if err != nil { @@ -158,7 +214,8 @@ func testModronE2e(t *testing.T, addr string, resourceGroupNames []string, want } } - manualObservation.Resource = &pb.Resource{Name: "non existing"} + nonExisting := "non existing" + manualObservation.ResourceRef = &pb.ResourceRef{ExternalId: &nonExisting} _, err = client.CreateObservation(ctx, &pb.CreateObservationRequest{Observation: manualObservation}) if err == nil { t.Errorf("CreateObservation(ctx, %+v) wanted error, got nil", manualObservation) @@ -232,21 +289,32 @@ func TestModronE2e(t *testing.T) { func TestModronE2eFake(t *testing.T) { want := map[string][]*structpb.Value{ - "account-1": {structpb.NewStringValue(""), structpb.NewStringValue("")}, - "bucket-public": {structpb.NewStringValue("PRIVATE")}, + "//cloudsql.googleapis.com/projects/project-id/instances/xyz": {nil}, + "api-key-unrestricted-0": {structpb.NewStringValue("restricted")}, + "api-key-unrestricted-1": {structpb.NewStringValue("restricted")}, + "api-key-with-overbroad-scope-1": { + structpb.NewStringValue(""), + structpb.NewStringValue(""), + }, + "backend-svc-external-no-modern": {structpb.NewStringValue("TLS 1.2")}, + "backend-svc-no-iap": {structpb.NewBoolValue(true)}, + "bucket-accessible-from-other-project": { + structpb.NewStringValue("prod"), + structpb.NewStringValue("modron-test"), + }, "bucket-public-allusers": {structpb.NewStringValue("PRIVATE")}, - "bucket-accessible-from-other-project": {structpb.NewStringValue("")}, - "api-key-unrestricted-0[]": {structpb.NewStringValue("restricted")}, - "api-key-unrestricted-1[]": {structpb.NewStringValue("restricted")}, - "api-key-with-overbroad-scope-1[]": {structpb.NewStringValue(""), structpb.NewStringValue("")}, - "backend-svc-2[0]": {structpb.NewNumberValue(float64(pb.Certificate_MANAGED))}, - "backend-svc-3[0]": {structpb.NewNumberValue(float64(pb.Certificate_MANAGED))}, - "backend-svc-5[0]": {structpb.NewStringValue("TLS 1.2")}, - "subnetwork-no-private-access-should-be-reported[0]": {structpb.NewStringValue("enabled")}, + "bucket-public": {structpb.NewStringValue("PRIVATE")}, "cloudsql-report-not-enforcing-tls": {structpb.NewBoolValue(true)}, "cloudsql-test-db-public-and-no-authorized-networks": {structpb.NewStringValue("AUTHORIZED_NETWORKS_SET")}, - "instance-1[0]": {structpb.NewStringValue("empty")}, - "projects/modron-test": {structpb.NewStringValue(""), structpb.NewStringValue("")}, + "instance-1": {structpb.NewStringValue("empty")}, + "projects/modron-test": { + structpb.NewStringValue("prod"), + structpb.NewStringValue("modron-test"), + structpb.NewStringValue("modron-test"), + structpb.NewStringValue("No basic roles"), + structpb.NewStringValue("No basic roles"), + }, + "subnetwork-no-private-access-should-be-reported": {structpb.NewStringValue("enabled")}, } testModronE2e(t, fakeServerAddr, []string{"projects/modron-test"}, want) } diff --git a/src/test/fake_notification_service.go b/src/test/fake_notification_service.go index a1425a6..803b3fc 100644 --- a/src/test/fake_notification_service.go +++ b/src/test/fake_notification_service.go @@ -1,6 +1,6 @@ package e2e -import "github.com/nianticlabs/modron/src/pb" +import pb "github.com/nianticlabs/modron/src/proto/generated" func New() pb.NotificationServiceServer { return newFakeServer() diff --git a/src/test/go.mod b/src/test/go.mod index a66c861..a65d275 100644 --- a/src/test/go.mod +++ b/src/test/go.mod @@ -1,20 +1,35 @@ module github.com/nianticlabs/modron/src/e2e_test -go 1.21 +go 1.23.2 -replace github.com/nianticlabs/modron/src/pb => ../proto/ +replace github.com/nianticlabs/modron/src/proto/generated => ../proto/generated require ( - github.com/google/go-cmp v0.5.9 - google.golang.org/grpc v1.57.0 - google.golang.org/protobuf v1.31.0 - github.com/nianticlabs/modron/src/pb v0.0.0-00010101000000-000000000000 + github.com/google/go-cmp v0.6.0 + google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.35.1 + github.com/nianticlabs/modron/src/proto/generated v0.0.0-00010101000000-000000000000 ) require ( - github.com/golang/protobuf v1.5.3 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/api v0.31.2 // indirect + k8s.io/apimachinery v0.31.2 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/src/test/go.sum b/src/test/go.sum new file mode 100644 index 0000000..db82501 --- /dev/null +++ b/src/test/go.sum @@ -0,0 +1,125 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= +k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= +k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= +k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= +k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/src/ui/.gitignore b/src/ui/.gitignore new file mode 100644 index 0000000..61c3bc7 --- /dev/null +++ b/src/ui/.gitignore @@ -0,0 +1 @@ +.yarn diff --git a/src/ui/.yarnrc.yml b/src/ui/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/src/ui/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/src/ui/Dockerfile b/src/ui/Dockerfile index 0a2217a..8496c40 100644 --- a/src/ui/Dockerfile +++ b/src/ui/Dockerfile @@ -1,27 +1,28 @@ -ARG GOVERSION=1.21 +ARG GOVERSION=1.23 +ARG NODE_VERSION=20 -FROM node:18.17.1-alpine AS ui_builder +FROM node:${NODE_VERSION}-alpine AS ui_builder WORKDIR /app -COPY ./client/ . +COPY ./src/ui/client/ . RUN npm install RUN npm run build -FROM golang:${GOVERSION} as server_builder -ENV GOPATH /go +FROM golang:${GOVERSION} AS server_builder +ENV GOPATH=/go WORKDIR /app -COPY go.* ./ +COPY ./src/ui/go.* ./ RUN go mod download -COPY . ./ +COPY ./src/ui/ ./ RUN CGO_ENABLED=0 go build -v -o modron-ui-server -FROM alpine:latest as ca-certificates_builder +FROM alpine:latest AS ca-certificates_builder RUN apk add --no-cache ca-certificates -FROM scratch +# FROM scratch WORKDIR /app -COPY --from=ca-certificates_builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +# COPY --from=ca-certificates_builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=ui_builder /app/dist/ . COPY --from=server_builder /app/modron-ui-server . USER 101:101 EXPOSE 8080 -ENTRYPOINT ["/app/modron-ui-server", "--logtostderr"] +ENTRYPOINT ["/app/modron-ui-server", "-logtostderr"] diff --git a/src/ui/README.md b/src/ui/README.md index 6261859..9244aea 100644 --- a/src/ui/README.md +++ b/src/ui/README.md @@ -2,15 +2,44 @@ User interface for the Modron service. ## Dependencies -- Docker 20.10 (and docker-compose) -- Node 16.16 -- Angular CLI 14.1 +- Docker +- Node.js LTS +- Angular CLI ## How to run ```bash -npm run genproto # Generate gRPC -npm run dev # Run UI with mock gRPC server and envoy proxy +npm run dev # Run UI with mock gRPC server and envoy proxy ``` -Then navigate to `localhost:4200`. \ No newline at end of file +Then navigate to `localhost:4200`. + + +## Developing on MacOS with [Lima](https://github.com/lima-vm/lima) + +### Frontend with `mock-grcp-server` + +When you're developing against the `mock-grpc-server` (`npm run dev`), this will be the setup: + +```mermaid +flowchart LR + subgraph Host + frontend["frontend"] + webpack_proxy["webpack-proxy"] + end + subgraph "Docker Host" + subgraph docker + envoy + mgs["mock-grpc-server"] + end + end + + frontend -- 127.0.0.1:4000 --> webpack_proxy + webpack_proxy -- 127.0.0.1:4201 --> envoy + envoy --> mgs +``` + +### Frontend with Modron as a backend + +1. Start the backend (either with `docker-compose.yml` or via `go run ./src`) and make sure it's listening on `:4201` +1. In this directory (`src/ui`), run `npm run dev:client` diff --git a/src/ui/client/.dockerignore b/src/ui/client/.dockerignore new file mode 100644 index 0000000..bbdb198 --- /dev/null +++ b/src/ui/client/.dockerignore @@ -0,0 +1,6 @@ +/cypress +!/cypress/e2e +!/cypress/components +!/cypress/tsconfig.json +/dist +/results diff --git a/src/ui/client/.eslintignore b/src/ui/client/.eslintignore new file mode 100644 index 0000000..e97b560 --- /dev/null +++ b/src/ui/client/.eslintignore @@ -0,0 +1 @@ +/src/proto/** diff --git a/src/ui/client/Dockerfile b/src/ui/client/Dockerfile index 57f780a..ad502a3 100644 --- a/src/ui/client/Dockerfile +++ b/src/ui/client/Dockerfile @@ -1,5 +1,5 @@ -FROM node:18.17.1-bullseye-slim -RUN apt-get update && apt-get install -y gnupg wget python +FROM node:20-bookworm-slim +RUN apt-get update && apt-get install -y gnupg wget RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' RUN apt-get update && apt-get install -y google-chrome-stable diff --git a/src/ui/client/Dockerfile.e2e b/src/ui/client/Dockerfile.e2e index fd7a2a4..1c7e041 100644 --- a/src/ui/client/Dockerfile.e2e +++ b/src/ui/client/Dockerfile.e2e @@ -1,7 +1,7 @@ -FROM cypress/base:18.16.1 +FROM cypress/base:20.9.0 WORKDIR /app -COPY package.json . -COPY package-lock.json . +COPY ./src/ui/client/package.json . +COPY ./src/ui/client/package-lock.json . ENV CI=1 RUN npm ci RUN npx cypress verify diff --git a/src/ui/client/angular.json b/src/ui/client/angular.json index 7ea7cf9..bd19f3c 100644 --- a/src/ui/client/angular.json +++ b/src/ui/client/angular.json @@ -88,13 +88,13 @@ "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { - "browserTarget": "ui:build:production" + "buildTarget": "ui:build:production" }, "development": { - "browserTarget": "ui:build:development" + "buildTarget": "ui:build:development" }, "developmentLocal": { - "browserTarget": "ui:build:developmentLocal" + "buildTarget": "ui:build:developmentLocal" } }, "defaultConfiguration": "developmentLocal" @@ -102,7 +102,7 @@ "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "ui:build" + "buildTarget": "ui:build" } }, "test": { diff --git a/src/ui/client/cypress/.gitignore b/src/ui/client/cypress/.gitignore new file mode 100644 index 0000000..733b5fd --- /dev/null +++ b/src/ui/client/cypress/.gitignore @@ -0,0 +1,2 @@ +/screenshots +/videos diff --git a/src/ui/client/cypress/e2e/spec.cy.ts b/src/ui/client/cypress/e2e/spec.cy.ts index 6110aa7..6f32edf 100644 --- a/src/ui/client/cypress/e2e/spec.cy.ts +++ b/src/ui/client/cypress/e2e/spec.cy.ts @@ -4,13 +4,31 @@ describe("ModronApp", () => { it("scans, refreshes observations", () => { const group = "modron-test" cy.visit("/") - cy.contains("0 observations").should("be.visible") - cy.contains(group).parents().find(".button").first().click() - cy.contains("Scanning").should("be.visible") + const projectCard = cy.get(".mat-mdc-card").contains(group).parents(".mat-mdc-card") + projectCard.contains("0 observations").should("be.visible") + projectCard.get("mat-progress-bar").should("not.exist") + + const scanButton = cy.get(".scan-all-rgs-button"); + scanButton.should("be.visible") + scanButton.contains("SCAN ALL") + scanButton.click() + projectCard.get("mat-progress-bar").should("be.visible") + cy.get(".scan-all-rgs-button").should("be.disabled") + cy.wait(2000) // Wait for the scan to run - cy.contains("16 observations", { timeout: SCAN_TIMEOUT_MS }).should("be.visible") - cy.contains("Scan").should("be.visible") - cy.contains(group).parents().find(".resourceGroup-info").click() + + projectCard.get(".findings-by-severity", { timeout: SCAN_TIMEOUT_MS }).should("be.visible") + projectCard.get("mat-progress-bar").should("not.exist") + cy.contains("SCAN").should("be.visible") + + // Iterate through the children + projectCard.get(".findings-by-severity > div").then((elements) => { + cy.wrap(elements.eq(0)).contains("5").should("be.visible") + cy.wrap(elements.eq(1)).contains("14").should("be.visible") + cy.wrap(elements.eq(2)).contains("1").should("be.visible") + }) + + projectCard.get(".mat-mdc-card").click() cy.contains("API_KEY_WITH_OVERBROAD_SCOPE").should("be.visible") cy.contains("CROSS_PROJECT_PERMISSIONS").should("be.visible") }) @@ -26,7 +44,7 @@ describe("ModronApp", () => { }) it("creates exceptions", () => { cy.visit("/modron/resourcegroup/projects-modron-test") - cy.get("div.notify-ctn").first().should("be.visible").click() + cy.get("app-notif-bell-button").first().should("be.visible").click() cy.get("textarea[formControlName=\"justification\"]").type("trust me") cy.get("input[formControlName=\"validUntilTime\"]").should(($dateTimePicker: any) => { const date = new Date($dateTimePicker.val()) @@ -35,7 +53,7 @@ describe("ModronApp", () => { }) cy.get("button[type=\"submit\"]").should("be.enabled").click() // Check that the exception is indeed created - cy.get(".notify-ctn>svg").first().should("be.visible").click() + cy.get("app-notif-bell-button").first().should("be.visible").click() cy.contains("trust me").should("be.visible") }) }) diff --git a/src/ui/client/package-lock.json b/src/ui/client/package-lock.json new file mode 100644 index 0000000..b8311df --- /dev/null +++ b/src/ui/client/package-lock.json @@ -0,0 +1,20469 @@ +{ + "name": "ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ui", + "version": "0.0.0", + "dependencies": { + "@angular/animations": "^18.2.1", + "@angular/cdk": "^18.2.1", + "@angular/common": "^18.2.1", + "@angular/compiler": "^18.2.1", + "@angular/core": "^18.2.1", + "@angular/forms": "^18.2.1", + "@angular/material": "^18.2.1", + "@angular/platform-browser": "^18.2.1", + "@angular/platform-browser-dynamic": "^18.2.1", + "@angular/router": "^18.2.1", + "@bufbuild/protobuf": "^2.0.0", + "@grpc/grpc-js": "^1.8.18", + "@material-symbols/font-400": "^0.23.0", + "@types/google-protobuf": "^3.15.6", + "chart.js": "^4.4.4", + "google-protobuf": "^3.21.2", + "grpc-web": "^1.5.0", + "moment": "^2.29.4", + "ng2-charts": "^6.0.1", + "ngx-cookie-service": "^18.0.0", + "ngx-markdown": "^18.0.0", + "rxjs": "~7.8.1", + "tslib": "^2.6.0", + "zone.js": "~0.14.10" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^18.2.1", + "@angular-eslint/builder": "18.4.0", + "@angular-eslint/eslint-plugin": "18.4.0", + "@angular-eslint/eslint-plugin-template": "18.4.0", + "@angular-eslint/schematics": "18.4.0", + "@angular-eslint/template-parser": "18.4.0", + "@angular/cli": "~18.2.1", + "@angular/compiler-cli": "^18.2.1", + "@cypress/schematic": "^2.5.0", + "@types/jasmine": "~4.3.5", + "@types/jest": "^29.5.12", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "cypress": "^13.14.2", + "eslint": "^8.57.0", + "jasmine-core": "~5.0.1", + "karma": "~6.4.2", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.1", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "karma-junit-reporter": "^2.0.1", + "npm": "^9.8.0", + "ts-protoc-gen": "^0.15.0", + "typescript": "~5.5.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1802.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.11.tgz", + "integrity": "sha512-p+XIc/j51aI83ExNdeZwvkm1F4wkuKMGUUoj0MVUUi5E6NoiMlXYm6uU8+HbRvPBzGy5+3KOiGp3Fks0UmDSAA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.2.11", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.11.tgz", + "integrity": "sha512-09Ln3NAdlMw/wMLgnwYU5VgWV5TPBEHolZUIvE9D8b6SFWBCowk3B3RWeAMgg7Peuf9SKwqQHBz2b1C7RTP/8g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1802.11", + "@angular-devkit/build-webpack": "0.1802.11", + "@angular-devkit/core": "18.2.11", + "@angular/build": "18.2.11", + "@babel/core": "7.25.2", + "@babel/generator": "7.25.0", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-transform-async-generator-functions": "7.25.0", + "@babel/plugin-transform-async-to-generator": "7.24.7", + "@babel/plugin-transform-runtime": "7.24.7", + "@babel/preset-env": "7.25.3", + "@babel/runtime": "7.25.0", + "@discoveryjs/json-ext": "0.6.1", + "@ngtools/webpack": "18.2.11", + "@vitejs/plugin-basic-ssl": "1.1.0", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.20", + "babel-loader": "9.1.3", + "browserslist": "^4.21.5", + "copy-webpack-plugin": "12.0.2", + "critters": "0.0.24", + "css-loader": "7.1.2", + "esbuild-wasm": "0.23.0", + "fast-glob": "3.3.2", + "http-proxy-middleware": "3.0.3", + "https-proxy-agent": "7.0.5", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "karma-source-map-support": "1.4.0", + "less": "4.2.0", + "less-loader": "12.2.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.3.1", + "magic-string": "0.30.11", + "mini-css-extract-plugin": "2.9.0", + "mrmime": "2.0.0", + "open": "10.1.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "postcss": "8.4.41", + "postcss-loader": "8.1.1", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.77.6", + "sass-loader": "16.0.0", + "semver": "7.6.3", + "source-map-loader": "5.0.0", + "source-map-support": "0.5.21", + "terser": "5.31.6", + "tree-kill": "1.2.2", + "tslib": "2.6.3", + "vite": "5.4.6", + "watchpack": "2.4.1", + "webpack": "5.94.0", + "webpack-dev-middleware": "7.4.2", + "webpack-dev-server": "5.0.4", + "webpack-merge": "6.0.1", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.23.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "@web/test-runner": "^0.18.0", + "browser-sync": "^3.0.2", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "karma": "^6.3.0", + "ng-packagr": "^18.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.6" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@web/test-runner": { + "optional": true + }, + "browser-sync": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1802.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.11.tgz", + "integrity": "sha512-G76rNsyn1iQk7qjyr+K4rnDzfalmEswmwXQorypSDGaHYzIDY1SZXMoP4225WMq5fJNBOJrk82FA0PSfnPE+zQ==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1802.11", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" + } + }, + "node_modules/@angular-devkit/core": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.11.tgz", + "integrity": "sha512-H9P1shRGigORWJHUY2BRa2YurT+DVminrhuaYHsbhXBRsPmgB2Dx/30YLTnC1s5XmR9QIRUCsg/d3kyT1wd5Zg==", + "dev": true, + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.11.tgz", + "integrity": "sha512-efRK3FotTFp4KD5u42jWfXpHUALXB9kJNsWiB4wEImKFH6CN+vjBspJQuLqk2oeBFh/7D2qRMc5P+2tZHM5hdw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.2.11", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.11", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-eslint/builder": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.4.0.tgz", + "integrity": "sha512-FOzGHX/nHSV1wSduSsabsx3aqC1nfde0opEpEDSOJhxExDxKCwoS1XPy1aERGyKip4ZVA6phC3dLtoBH3QMkVQ==", + "dev": true, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.0.tgz", + "integrity": "sha512-HlFHt2qgdd+jqyVIkCXmrjHauXo/XY3Rp0UNabk83ejGi/raM/6lEFI7iFWzHxLyiAKk4OgGI5W26giSQw991A==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.0.tgz", + "integrity": "sha512-Saz9lkWPN3da7ZKW17UsOSN7DeY+TPh+wz/6GCNZCh67Uw2wvMC9agb+4hgpZNXYCP5+u7erqzxQmBoWnS/A+A==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.4.0", + "@angular-eslint/utils": "18.4.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.0.tgz", + "integrity": "sha512-n3uZFCy76DnggPqjSVFV3gYD1ik7jCG28o2/HO4kobcMNKnwW8XAlFUagQ4TipNQh7fQiAefsEqvv2quMsYDVw==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.4.0", + "@angular-eslint/utils": "18.4.0", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@typescript-eslint/types": "^7.11.0 || ^8.0.0", + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.0.tgz", + "integrity": "sha512-ssqe+0YCfekbWIXNdCrHfoPK/bPZAWybs0Bn/b99dfd8h8uyXkERo9AzIOx4Uyj/08SkP9aPL/0uOOEHDsRGwQ==", + "dev": true, + "dependencies": { + "@angular-eslint/eslint-plugin": "18.4.0", + "@angular-eslint/eslint-plugin-template": "18.4.0", + "ignore": "5.3.2", + "semver": "7.6.3", + "strip-json-comments": "3.1.1" + }, + "peerDependencies": { + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.4.0.tgz", + "integrity": "sha512-VTep3Xd3IOaRIPL+JN/TV4/2DqUPbjtF3TNY15diD/llnrEhqFnmsvMihexbQyTqzOG+zU554oK44YfvAtHOrw==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.4.0", + "eslint-scope": "^8.0.2" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.0.tgz", + "integrity": "sha512-At1yS8GRviGBoaupiQwEOL4/IcZJCE/+2vpXdItMWPGB1HWetxlKAUZTMmIBX/r5Z7CoXxl+LbqpGhrhyzIQAg==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "18.4.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular/animations": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.10.tgz", + "integrity": "sha512-LT5+CocFZJ4t5jXsXLx5w/sBuWSxOEjmNTYga13usRcLOblrAB902pjUdFCHEZyrCUgm4MH8vov9fMS+Ks2GCw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "18.2.10" + } + }, + "node_modules/@angular/build": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.11.tgz", + "integrity": "sha512-AgirvSCmqUKiDE3C0rl3JA68OkOqQWDKUvjqRHXCkhxldLVOVoeIl87+jBYK/v9gcmk+K+ju+5wbGEfu1FjhiQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1802.11", + "@babel/core": "7.25.2", + "@babel/helper-annotate-as-pure": "7.24.7", + "@babel/helper-split-export-declaration": "7.24.7", + "@babel/plugin-syntax-import-attributes": "7.24.7", + "@inquirer/confirm": "3.1.22", + "@vitejs/plugin-basic-ssl": "1.1.0", + "browserslist": "^4.23.0", + "critters": "0.0.24", + "esbuild": "0.23.0", + "fast-glob": "3.3.2", + "https-proxy-agent": "7.0.5", + "listr2": "8.2.4", + "lmdb": "3.0.13", + "magic-string": "0.30.11", + "mrmime": "2.0.0", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.6.1", + "rollup": "4.22.4", + "sass": "1.77.6", + "semver": "7.6.3", + "vite": "5.4.6", + "watchpack": "2.4.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "less": "^4.2.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.6" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular/cdk": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.11.tgz", + "integrity": "sha512-FuvfhrSz2ch0gyOVHrkWq2C/I2PnOzKYSXlG/VEG+ize/WNrvlYy//5WVrTh/hv+HD9sdoWPr9ULXsfFfgbo7w==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cli": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.11.tgz", + "integrity": "sha512-0JI1xjOLRemBPjdT/yVlabxc3Zkjqa/lhvVxxVC1XhKoW7yGxIGwNrQ4pka4CcQtCuktO6KPMmTGIu8YgC3cpw==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1802.11", + "@angular-devkit/core": "18.2.11", + "@angular-devkit/schematics": "18.2.11", + "@inquirer/prompts": "5.3.8", + "@listr2/prompt-adapter-inquirer": "2.0.15", + "@schematics/angular": "18.2.11", + "@yarnpkg/lockfile": "1.1.0", + "ini": "4.1.3", + "jsonc-parser": "3.3.1", + "listr2": "8.2.4", + "npm-package-arg": "11.0.3", + "npm-pick-manifest": "9.1.0", + "pacote": "18.0.6", + "resolve": "1.22.8", + "semver": "7.6.3", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/common": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.10.tgz", + "integrity": "sha512-YzTCmuqLiOuT+Yv07vuKymDWiebOVZ8BuXakJiz4EM7FMoOw5gICHJ04jepZSjDNWpA16e7kJSdt5ucnmvCFDQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "18.2.10", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.10.tgz", + "integrity": "sha512-cu+Uq1nnyl00Glg0+2uvm+Xpaq5b4YvWpaLGGtit7uGETAJ4L/frlBVeaTRhEoaIAGBI+RRlyuFLae+etQDA0w==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "18.2.10" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.10.tgz", + "integrity": "sha512-CNFStKWMB89MFKAZZFUOhoQi+fHqRLgNOOrI73LjizXixvngEh3BDZJRtK9hbSGG+giujBrummEA60CWAw69MA==", + "dev": true, + "dependencies": { + "@babel/core": "7.25.2", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/compiler": "18.2.10", + "typescript": ">=5.4 <5.6" + } + }, + "node_modules/@angular/compiler-cli/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/compiler-cli/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/core": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.10.tgz", + "integrity": "sha512-EfxVz0pLaxnOppOYkdhnaUkk8HZT+uxaAGpJD3ppAa7YAWTE9xIGoNJmtS33cZNNOnvriMkdv7yn6pDtV4ct+Q==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.14.10" + } + }, + "node_modules/@angular/forms": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.10.tgz", + "integrity": "sha512-2VprGB+enJIeqfz2oALmP/G/UiFzpZW6PHgyZXhk/0J/UMsa26JoYxwDFvfdm/WGTrB+VaQEG7in5xwiFPAFtQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/common": "18.2.10", + "@angular/core": "18.2.10", + "@angular/platform-browser": "18.2.10", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/material": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.11.tgz", + "integrity": "sha512-VPfnpwmg6p5DsH1UMfOXjKA+qAbUx6nyinGWpx4+ntr/T1oEhRk5CnoOtVS0Xk0rnRSbEF6ayjDBH2YPR9ol3A==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "18.2.11", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.10.tgz", + "integrity": "sha512-zKyRKFr3AaEA4SE/DEeb5FWHJutT26avHZog6ZGDkMeMN12zMtSqjPuTSgmDXCWleoOkzbb+nhAQ+fK/EyGyPA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/animations": "18.2.10", + "@angular/common": "18.2.10", + "@angular/core": "18.2.10" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.10.tgz", + "integrity": "sha512-syKyOTgfQnMxfpDRP1khTSPZ5dsMgA8YQwNF6KsB3eZQl15CKSka7bzjMOUWeZ8M3WShOp1AzTf0MfwNeh0UBA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/common": "18.2.10", + "@angular/compiler": "18.2.10", + "@angular/core": "18.2.10", + "@angular/platform-browser": "18.2.10" + } + }, + "node_modules/@angular/router": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.10.tgz", + "integrity": "sha512-ZqJgOGOfvW0epsc7pIo7DffZqYHo3O9aUCVepZAhOxqtjF/sfhX2fy+A0xopTIiR0eM3LrT823V+2hjlBHj+CA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/common": "18.2.10", + "@angular/core": "18.2.10", + "@angular/platform-browser": "18.2.10", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz", + "integrity": "sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==", + "optional": true, + "dependencies": { + "package-manager-detector": "^0.2.0", + "tinyexec": "^0.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", + "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", + "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", + "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", + "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", + "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-simple-access": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", + "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.0.tgz", + "integrity": "sha512-o+UlMLt49RvtCASlOMW0AkHnabN9wR9rwCCherxO0yG4Npy34GkvrAqdXQvrhNs+jh+gkK8gB8Lf05qL/O7KWg==", + "optional": true + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.1.tgz", + "integrity": "sha512-gdWzq7eX017a1kZCU/bP/sbk4e0GZ6idjsXOcMrQwODCb/rx985fHJJ8+hCu79KpuG7PfZh7bo3BBjPH37JuZw==" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "optional": true, + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "optional": true, + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "optional": true + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "optional": true + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "optional": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", + "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.0", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.13.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/schematic": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.5.2.tgz", + "integrity": "sha512-H+V3ZP3KQVOs6b49N66jioXa+rkLzszVi+Bl3jiroVTURUNMOpSa4BOrt10Pn8F57TO0Bamhch2WOk/e9cq98w==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.0.0", + "rxjs": "~6.6.0" + }, + "peerDependencies": { + "@angular/cli": ">=14", + "@angular/core": ">=14" + } + }, + "node_modules/@cypress/schematic/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@cypress/schematic/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", + "integrity": "sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==", + "dev": true, + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.2.tgz", + "integrity": "sha512-bgxdZmgTrJZX50OjyVwz3+mNEnCTNkh3cIqGPWVNeW9jX6bn1ZkU80uPd+67/ZpIJIjRQ9qaHCjhavyoWYxumg==", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "optional": true + }, + "node_modules/@iconify/utils": { + "version": "2.1.33", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.33.tgz", + "integrity": "sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==", + "optional": true, + "dependencies": { + "@antfu/install-pkg": "^0.4.0", + "@antfu/utils": "^0.7.10", + "@iconify/types": "^2.0.0", + "debug": "^4.3.6", + "kolorist": "^1.8.0", + "local-pkg": "^0.5.0", + "mlly": "^1.7.1" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", + "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", + "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", + "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", + "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.7.tgz", + "integrity": "sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", + "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", + "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", + "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", + "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^2.4.7", + "@inquirer/confirm": "^3.1.22", + "@inquirer/editor": "^2.1.22", + "@inquirer/expand": "^2.1.22", + "@inquirer/input": "^2.2.9", + "@inquirer/number": "^1.0.10", + "@inquirer/password": "^2.1.22", + "@inquirer/rawlist": "^2.2.4", + "@inquirer/search": "^1.0.7", + "@inquirer/select": "^2.4.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", + "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", + "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", + "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", + "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "dev": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true + }, + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.15.tgz", + "integrity": "sha512-MZrGem/Ujjd4cPTLYDfCZK2iKKeiO/8OX13S6jqxldLs0Prf2aGqVlJ77nMBqMv7fzqgXEgjrNHLXcKR8l9lOg==", + "dev": true, + "dependencies": { + "@inquirer/type": "^1.5.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 6" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.13.tgz", + "integrity": "sha512-uiKPB0Fv6WEEOZjruu9a6wnW/8jrjzlZbxXscMB8kuCJ1k6kHpcBnuvaAWcqhbI7rqX5GKziwWEdD+wi2gNLfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.13.tgz", + "integrity": "sha512-bEVIIfK5mSQoG1R19qA+fJOvCB+0wVGGnXHT3smchBVahYBdlPn2OsZZKzlHWfb1E+PhLBmYfqB5zQXFP7hJig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.13.tgz", + "integrity": "sha512-Yml1KlMzOnXj/tnW7yX8U78iAzTk39aILYvCPbqeewAq1kSzl+w59k/fiVkTBfvDi/oW/5YRxL+Fq+Y1Fr1r2Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.13.tgz", + "integrity": "sha512-afbVrsMgZ9dUTNUchFpj5VkmJRxvht/u335jUJ7o23YTbNbnpmXif3VKQGCtnjSh+CZaqm6N3CPG8KO3zwyZ1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.13.tgz", + "integrity": "sha512-vOtxu0xC0SLdQ2WRXg8Qgd8T32ak4SPqk5zjItRszrJk2BdeXqfGxBJbP7o4aOvSPSmSSv46Lr1EP4HXU8v7Kg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.13.tgz", + "integrity": "sha512-UCrMJQY/gJnOl3XgbWRZZUvGGBuKy6i0YNSptgMzHBjs+QYDYR1Mt/RLTOPy4fzzves65O1EDmlL//OzEqoLlA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@material-symbols/font-400": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@material-symbols/font-400/-/font-400-0.23.0.tgz", + "integrity": "sha512-sAkBNo8npezd+GDOH/EO5TecwmKx6Ojv2SQ3RpINZTZGtJpNSygfzptlbJ4Ti0xOvET9bmpFVYK44RFBLy8CQg==" + }, + "node_modules/@mermaid-js/parser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz", + "integrity": "sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==", + "optional": true, + "dependencies": { + "langium": "3.0.0" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ngtools/webpack": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.11.tgz", + "integrity": "sha512-iTdUGJ5O7yMm1DyCzyoMDMxBJ68emUSSXPWbQzEEdcqmtifRebn+VAq4vHN8OmtGM1mtuKeLEsbiZP8ywrw7Ug==", + "dev": true, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "typescript": ">=5.4 <5.6", + "webpack": "^5.54.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", + "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", + "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "18.2.11", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.11.tgz", + "integrity": "sha512-jT54mc9+hPOwie9bji/g2krVuK1kkNh2PNFGwfgCg3Ofmt3hcyOBai1DKuot5uLTX4VCCbvfwiVR/hJniQl2SA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.2.11", + "@angular-devkit/schematics": "18.2.11", + "jsonc-parser": "3.3.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", + "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "optional": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "optional": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "optional": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "optional": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "optional": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "optional": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "optional": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "optional": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", + "optional": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "optional": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "optional": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "optional": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "optional": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "optional": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "optional": true + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "optional": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "optional": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "optional": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "optional": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "optional": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "optional": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "optional": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "optional": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", + "optional": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "optional": true + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "optional": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "optional": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "optional": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "optional": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "optional": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "optional": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "optional": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "optional": true + }, + "node_modules/@types/google-protobuf": { + "version": "3.15.12", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.12.tgz", + "integrity": "sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.6.tgz", + "integrity": "sha512-3N0FpQTeiWjm+Oo1WUYWguUS7E6JLceiGTriFrG8k5PU7zRLJCzLcWURU3wjMbZGS//a2/LgjsnO3QxIlwxt9g==", + "dev": true + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.8.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.5.tgz", + "integrity": "sha512-5iYk6AMPtsMbkZqCO1UGF9W5L38twq11S2pYWkybGHH2ogPUvXWNlQqJBzuEZWKj/WRH+QTeiv6ySWqJtvIEgA==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true + }, + "node_modules/@types/sizzle": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", + "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", + "dev": true + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true + }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", + "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", + "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.12.2", + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/typescript-estree": "8.12.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", + "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", + "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/visitor-keys": "8.12.2", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.12.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", + "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.12.2", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", + "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "dev": true, + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "devOptional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dev": true, + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001676", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz", + "integrity": "sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chart.js": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz", + "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "optional": true, + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "optional": true, + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "optional": true, + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "optional": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js-compat": { + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", + "dev": true, + "dependencies": { + "browserslist": "^4.24.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "optional": true, + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/critters": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", + "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", + "deprecated": "Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^5.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.2", + "htmlparser2": "^8.0.2", + "postcss": "^8.4.23", + "postcss-media-query-parser": "^0.2.3" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/cypress": { + "version": "13.15.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.1.tgz", + "integrity": "sha512-DwUFiKXo4lef9kA0M4iEhixFqoqp2hw8igr0lTqafRb9qtU3X0XGxKbkSYsUFdkrAkphc7MPDxoNPhk5pj9PVg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@cypress/request": "^3.0.4", + "@cypress/xvfb": "^1.2.4", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.7.1", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.1", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.1", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.5.3", + "supports-color": "^8.1.1", + "tmp": "~0.2.3", + "tree-kill": "1.2.2", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^16.0.0 || ^18.0.0 || >=20.0.0" + } + }, + "node_modules/cypress/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/cypress/node_modules/listr2/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cypress/node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cypress/node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cypress/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/cytoscape": { + "version": "3.30.3", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.3.tgz", + "integrity": "sha512-HncJ9gGJbVtw7YXtIs3+6YAFSSiKsom0amWc33Z7QbylbY2JGMrA0yz4EwrdTScZxnwclXeEZHzO5pxoy0ZE4g==", + "optional": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "optional": true, + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "optional": true, + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "optional": true, + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "optional": true + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "optional": true, + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "optional": true, + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "optional": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "optional": true, + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "optional": true, + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "optional": true, + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "optional": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "optional": true, + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "optional": true, + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "optional": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "optional": true, + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "optional": true, + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "optional": true, + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "optional": true, + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "optional": true + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "optional": true, + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "optional": true + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "optional": true, + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "optional": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "optional": true, + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "optional": true, + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "optional": true, + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "optional": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "optional": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz", + "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==", + "optional": true, + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "devOptional": true + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "devOptional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/default-gateway/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-gateway/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/default-gateway/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "optional": true, + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "optional": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", + "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==", + "optional": true + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ecc-jsbn/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.49", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.49.tgz", + "integrity": "sha512-ZXfs1Of8fDb6z7WEYZjXpgIRF6MEu8JdeGA0A40aZq6OQbS+eJpnnV49epZRna2DU/YsEjSQuGtQPPtvt6J65A==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/emoji-toolkit": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/emoji-toolkit/-/emoji-toolkit-9.0.1.tgz", + "integrity": "sha512-sMMNqKNLVHXJfIKoPbrRJwtYuysVNC9GlKetr72zE3SSVbHqoeDLWVrxP0uM0AE0qvdl3hbUk+tJhhwXZrDHaw==", + "optional": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.1.tgz", + "integrity": "sha512-QHuXVeZx9d+tIQAz/XztU0ZwZf2Agg9CcXcgE1rurqvdBeDBrpSwjl8/6XUqMg7tw2Y7uAdKb2sRv+bSEFqQ5A==", + "dev": true, + "dependencies": { + "punycode": "^1.4.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "devOptional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.23.0.tgz", + "integrity": "sha512-6jP8UmWy6R6TUUV8bMuC3ZyZ6lZKI56x0tkxyCIqWwRRJ/DgeQKneh/Oid5EoGoPFLrGNkz47ZEtWAYuiY/u9g==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "dependencies": { + "async": "^3.2.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "optional": true, + "dependencies": { + "delegate": "^3.1.2" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==" + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/grpc-web": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/grpc-web/-/grpc-web-1.5.0.tgz", + "integrity": "sha512-y1tS3BBIoiVSzKTDF3Hm7E8hV2n7YY7pO0Uo7depfWJqKzWE+SKr0jvHNIJsJJYILQlpYShpi/DRJJMbosgDMQ==" + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "optional": true + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", + "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.0.1.tgz", + "integrity": "sha512-D4bRej8CplwNtNGyTPD++cafJlZUphzZNV+MSAnbD3er4D0NjL4x9V+mu/SI+5129utnCBen23JwEuBZA9vlpQ==", + "dev": true + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-coverage/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma-coverage/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/karma-coverage/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", + "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", + "dev": true, + "peerDependencies": { + "jasmine-core": "^4.0.0 || ^5.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "node_modules/karma-jasmine/node_modules/jasmine-core": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", + "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", + "dev": true + }, + "node_modules/karma-junit-reporter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", + "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", + "dev": true, + "dependencies": { + "path-is-absolute": "^1.0.0", + "xmlbuilder": "12.0.0" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "node_modules/karma/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/karma/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/karma/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/katex": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", + "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "optional": true, + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==", + "optional": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "optional": true + }, + "node_modules/langium": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.0.0.tgz", + "integrity": "sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==", + "optional": true, + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/launch-editor": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "optional": true + }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true, + "engines": { + "node": "> 0.8" + } + }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", + "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/listr2": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lmdb": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.13.tgz", + "integrity": "sha512-UGe+BbaSUQtAMZobTb4nHvFMrmvuAQKSeaqAX2meTEQjfsbpl5sxdHD8T72OnwD4GU9uwNhYXIVe4QGs8N9Zyw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "msgpackr": "^1.10.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.4.1", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.0.13", + "@lmdb/lmdb-darwin-x64": "3.0.13", + "@lmdb/lmdb-linux-arm": "3.0.13", + "@lmdb/lmdb-linux-arm64": "3.0.13", + "@lmdb/lmdb-linux-x64": "3.0.13", + "@lmdb/lmdb-win32-x64": "3.0.13" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "optional": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.0.tgz", + "integrity": "sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==", + "dev": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mermaid": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.4.0.tgz", + "integrity": "sha512-mxCfEYvADJqOiHfGpJXLs4/fAjHz448rH0pfY5fAoxiz70rQiDSzUUy4dNET2T08i46IVpjohPd6WWbzmRHiPA==", + "optional": true, + "dependencies": { + "@braintree/sanitize-url": "^7.0.1", + "@iconify/utils": "^2.1.32", + "@mermaid-js/parser": "^0.3.0", + "@types/d3": "^7.4.3", + "@types/dompurify": "^3.0.5", + "cytoscape": "^3.29.2", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.11", + "dayjs": "^1.11.10", + "dompurify": "^3.0.11 <3.1.7", + "katex": "^0.16.9", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^13.0.2", + "roughjs": "^4.6.6", + "stylis": "^4.3.1", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.1" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz", + "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==", + "optional": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mermaid/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mlly": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.2.tgz", + "integrity": "sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==", + "optional": true, + "dependencies": { + "acorn": "^8.12.1", + "pathe": "^1.1.2", + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true + }, + "node_modules/msgpackr": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", + "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", + "dev": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/ng2-charts": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-6.0.1.tgz", + "integrity": "sha512-pO7evbvHqjiKB7zqE12tCKWQI9gmQ8DVOEaWBBLlxJabc4fLGk7o9t4jC4+Q9pJiQrTtQkugm0dIPQ4PFHUaWA==", + "dependencies": { + "lodash-es": "^4.17.15", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": ">=17.0.0", + "@angular/common": ">=17.0.0", + "@angular/core": ">=17.0.0", + "@angular/platform-browser": ">=17.0.0", + "chart.js": "^3.4.0 || ^4.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/ngx-cookie-service": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-18.0.0.tgz", + "integrity": "sha512-hkkUckzZTXXWtFgvVkT2hg6mwYMLXioXDZWBsVCOy9gYkADjsj0N5VViO7eo2izQ0VcMPd/Etog1trf/T4oZMQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "peerDependencies": { + "@angular/common": "^18.0.0-rc.0", + "@angular/core": "^18.0.0-rc.0" + } + }, + "node_modules/ngx-markdown": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-18.1.0.tgz", + "integrity": "sha512-n4HFSm5oqVMXFuD+WXIVkI6NyxD8Oubr4B3c9U1J7Ptr6t9DVnkNBax3yxWc+8Wli+FXTuGEnDXzB3sp7E9paA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "clipboard": "^2.0.11", + "emoji-toolkit": ">= 8.0.0 < 10.0.0", + "katex": "^0.16.0", + "mermaid": ">= 10.6.0 < 12.0.0", + "prismjs": "^1.28.0" + }, + "peerDependencies": { + "@angular/common": "^18.0.0", + "@angular/core": "^18.0.0", + "@angular/platform-browser": "^18.0.0", + "marked": ">= 9.0.0 < 13.0.0", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.14.0" + } + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/nice-napi/node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", + "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm": { + "version": "9.9.3", + "resolved": "https://registry.npmjs.org/npm/-/npm-9.9.3.tgz", + "integrity": "sha512-Z1l+rcQ5kYb17F3hHtO601arEpvdRYnCLtg8xo3AGtyj3IthwaraEOexI9903uANkifFbqHC8hT53KIrozWg8A==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/run-script", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "sigstore", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^6.5.0", + "@npmcli/config": "^6.4.0", + "@npmcli/fs": "^3.1.0", + "@npmcli/map-workspaces": "^3.0.4", + "@npmcli/package-json": "^4.0.1", + "@npmcli/promise-spawn": "^6.0.2", + "@npmcli/run-script": "^6.0.2", + "abbrev": "^2.0.0", + "archy": "~1.0.0", + "cacache": "^17.1.4", + "chalk": "^5.3.0", + "ci-info": "^4.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.3", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^6.1.1", + "ini": "^4.1.1", + "init-package-json": "^5.0.0", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^3.0.1", + "libnpmaccess": "^7.0.2", + "libnpmdiff": "^5.0.20", + "libnpmexec": "^6.0.4", + "libnpmfund": "^4.2.1", + "libnpmhook": "^9.0.3", + "libnpmorg": "^5.0.4", + "libnpmpack": "^5.0.20", + "libnpmpublish": "^7.5.1", + "libnpmsearch": "^6.0.2", + "libnpmteam": "^5.0.3", + "libnpmversion": "^4.0.2", + "make-fetch-happen": "^11.1.1", + "minimatch": "^9.0.3", + "minipass": "^7.0.4", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^9.4.1", + "nopt": "^7.2.0", + "normalize-package-data": "^5.0.0", + "npm-audit-report": "^5.0.0", + "npm-install-checks": "^6.3.0", + "npm-package-arg": "^10.1.0", + "npm-pick-manifest": "^8.0.2", + "npm-profile": "^7.0.1", + "npm-registry-fetch": "^14.0.5", + "npm-user-validate": "^2.0.0", + "npmlog": "^7.0.1", + "p-map": "^4.0.0", + "pacote": "^15.2.0", + "parse-conflict-json": "^3.0.1", + "proc-log": "^3.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^2.1.0", + "semver": "^7.6.0", + "sigstore": "^1.9.0", + "spdx-expression-parse": "^3.0.1", + "ssri": "^10.0.5", + "supports-color": "^9.4.0", + "tar": "^6.2.0", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^5.0.0", + "which": "^3.0.1", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", + "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "dev": true, + "dependencies": { + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/@gar/promisify": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "6.5.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.0", + "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^5.0.0", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^4.0.0", + "@npmcli/query": "^3.1.0", + "@npmcli/run-script": "^6.0.0", + "bin-links": "^4.0.1", + "cacache": "^17.0.4", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^6.1.1", + "json-parse-even-better-errors": "^3.0.0", + "json-stringify-nice": "^1.1.4", + "minimatch": "^9.0.0", + "nopt": "^7.0.0", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^10.1.0", + "npm-pick-manifest": "^8.0.1", + "npm-registry-fetch": "^14.0.3", + "npmlog": "^7.0.1", + "pacote": "^15.0.8", + "parse-conflict-json": "^3.0.0", + "proc-log": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.2", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.1", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "6.4.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "ci-info": "^4.0.0", + "ini": "^4.1.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^17.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^15.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/move-file": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^4.1.0", + "glob": "^10.2.2", + "hosted-git-info": "^6.1.1", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.2.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^1.1.0", + "@sigstore/protobuf-specs": "^0.2.0", + "make-fetch-happen": "^11.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.0", + "tuf-js": "^1.1.7" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tootallnate/once": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "1.0.0", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/npm/node_modules/agentkeepalive": { + "version": "4.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "17.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^7.0.3", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^4.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/delegates": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/gauge": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^4.0.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.3.10", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hasown": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "6.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/humanize-ms": { + "version": "1.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/infer-owner": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/ini": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^10.0.0", + "promzard": "^1.0.0", + "read": "^2.0.0", + "read-package-json": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^3.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.13.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "2.3.6", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "7.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^10.1.0", + "npm-registry-fetch": "^14.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "5.0.21", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^6.5.0", + "@npmcli/disparity-colors": "^3.0.0", + "@npmcli/installed-package-contents": "^2.0.2", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^9.0.0", + "npm-package-arg": "^10.1.0", + "pacote": "^15.0.8", + "tar": "^6.1.13" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "6.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^6.5.0", + "@npmcli/run-script": "^6.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^10.1.0", + "npmlog": "^7.0.1", + "pacote": "^15.0.8", + "proc-log": "^3.0.0", + "read": "^2.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "4.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^6.5.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "9.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^14.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "5.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^14.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "5.0.21", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^6.5.0", + "@npmcli/run-script": "^6.0.0", + "npm-package-arg": "^10.1.0", + "pacote": "^15.0.8" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "7.5.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^5.0.0", + "npm-package-arg": "^10.1.0", + "npm-registry-fetch": "^14.0.3", + "proc-log": "^3.0.0", + "semver": "^7.3.7", + "sigstore": "^1.4.0", + "ssri": "^10.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "6.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^14.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^14.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^4.0.1", + "@npmcli/run-script": "^6.0.0", + "json-parse-even-better-errors": "^3.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "7.18.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "11.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/make-fetch-happen/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "9.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/@npmcli/fs": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/abbrev": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/cacache": { + "version": "16.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/glob": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/cacache/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/make-fetch-happen": { + "version": "10.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minipass-fetch": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/nopt": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/node-gyp/node_modules/ssri": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/unique-filename": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/unique-slug": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "6.3.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "10.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "14.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^11.0.0", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npmlog": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^4.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^5.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/once": { + "version": "1.4.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "15.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^5.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^1.3.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/pacote/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.10.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~1.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/readable-stream": { + "version": "3.6.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.6.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "1.9.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^1.1.0", + "@sigstore/protobuf-specs": "^0.2.0", + "@sigstore/sign": "^1.0.0", + "@sigstore/tuf": "^1.0.3", + "make-fetch-happen": "^11.0.1" + }, + "bin": { + "sigstore": "bin/sigstore.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.17", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "10.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "1.1.7", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "1.0.4", + "debug": "^4.3.4", + "make-fetch-happen": "^11.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ordered-binary": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.2.tgz", + "integrity": "sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==", + "dev": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", + "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/package-manager-detector": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.2.tgz", + "integrity": "sha512-VgXbyrSNsml4eHWIvxxG/nTL4wgybMTXCV2Un/+yEc3aDKKU6nQBZjbeP3Pl3qm9Qg92X/1ng4ffvCeD/zwHgg==", + "optional": true + }, + "node_modules/pacote": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "devOptional": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "dependencies": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "optional": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "optional": true + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/piscina": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", + "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", + "dev": true, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-types": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", + "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", + "optional": true, + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.2", + "pathe": "^1.1.2" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "optional": true + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "optional": true, + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", + "dev": true + }, + "node_modules/regexpu-core": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", + "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.11.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.2.tgz", + "integrity": "sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==", + "dev": true, + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "optional": true + }, + "node_modules/rollup": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "optional": true, + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "optional": true + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "devOptional": true + }, + "node_modules/sass": { + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", + "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "optional": true + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==", + "optional": true + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dev": true, + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz", + "integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==", + "optional": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/terser": { + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "optional": true + }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "optional": true + }, + "node_modules/tldts": { + "version": "6.1.57", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.57.tgz", + "integrity": "sha512-Oy7yDXK8meJl8vPMOldzA+MtueAJ5BrH4l4HXwZuj2AtfoQbLjmTJmjNWPUcAo+E/ibHn7QlqMS0BOcXJFJyHQ==", + "dev": true, + "dependencies": { + "tldts-core": "^6.1.57" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.57", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.57.tgz", + "integrity": "sha512-lXnRhuQpx3zU9EONF9F7HfcRLvN1uRYUBIiKL+C/gehC/77XTU+Jye6ui86GA3rU6FjlJ0triD1Tkjt2F/2lEg==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "dev": true, + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", + "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "optional": true, + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ts-protoc-gen": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/ts-protoc-gen/-/ts-protoc-gen-0.15.0.tgz", + "integrity": "sha512-TycnzEyrdVDlATJ3bWFTtra3SCiEP0W0vySXReAuEygXCUr1j2uaVyL0DhzjwuUdQoW5oXPwk6oZWeA0955V+g==", + "dev": true, + "dependencies": { + "google-protobuf": "^3.15.5" + }, + "bin": { + "protoc-gen-ts": "bin/protoc-gen-ts" + } + }, + "node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==" + }, + "node_modules/tuf-js": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "dev": true, + "dependencies": { + "@tufjs/models": "2.0.1", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz", + "integrity": "sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "optional": true + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vite": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite/node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "optional": true, + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "optional": true, + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "optional": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "optional": true + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "optional": true + }, + "node_modules/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, + "node_modules/webpack": { + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlbuilder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", + "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zone.js": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", + "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==" + } + } +} diff --git a/src/ui/client/package.json b/src/ui/client/package.json index 05d83e4..9a5491e 100644 --- a/src/ui/client/package.json +++ b/src/ui/client/package.json @@ -16,41 +16,47 @@ }, "private": true, "dependencies": { - "@angular/animations": "^16.1.5", - "@angular/cdk": "^16.1.5", - "@angular/common": "^16.1.5", - "@angular/compiler": "^16.1.5", - "@angular/core": "^16.1.5", - "@angular/forms": "^16.1.5", - "@angular/material": "^16.1.5", - "@angular/platform-browser": "^16.1.5", - "@angular/platform-browser-dynamic": "^16.1.5", - "@angular/router": "^16.1.5", + "@angular/animations": "^18.2.1", + "@angular/cdk": "^18.2.1", + "@angular/common": "^18.2.1", + "@angular/compiler": "^18.2.1", + "@angular/core": "^18.2.1", + "@angular/forms": "^18.2.1", + "@angular/material": "^18.2.1", + "@angular/platform-browser": "^18.2.1", + "@angular/platform-browser-dynamic": "^18.2.1", + "@angular/router": "^18.2.1", + "@bufbuild/protobuf": "^2.0.0", "@grpc/grpc-js": "^1.8.18", - "@improbable-eng/grpc-web": "^0.15.0", + "@material-symbols/font-400": "^0.23.0", "@types/google-protobuf": "^3.15.6", + "chart.js": "^4.4.4", "google-protobuf": "^3.21.2", - "ngx-cookie-service": "^16.0.0", - "ngx-markdown": "^16.0.0", + "grpc-web": "^1.5.0", + "moment": "^2.29.4", + "ng2-charts": "^6.0.1", + "ngx-cookie-service": "^18.0.0", + "ngx-markdown": "^18.0.0", "rxjs": "~7.8.1", "tslib": "^2.6.0", - "zone.js": "~0.13.1" + "zone.js": "~0.14.10" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.1.4", - "@angular-eslint/builder": "16.1.0", - "@angular-eslint/eslint-plugin": "16.1.0", - "@angular-eslint/eslint-plugin-template": "16.1.0", - "@angular-eslint/schematics": "16.1.0", - "@angular-eslint/template-parser": "16.1.0", - "@angular/cli": "~16.1.4", - "@angular/compiler-cli": "^16.1.5", + "@angular-devkit/build-angular": "^18.2.1", + "@angular-eslint/builder": "18.4.0", + "@angular-eslint/eslint-plugin": "18.4.0", + "@angular-eslint/eslint-plugin-template": "18.4.0", + "@angular-eslint/schematics": "18.4.0", + "@angular-eslint/template-parser": "18.4.0", + "@angular/cli": "~18.2.1", + "@angular/compiler-cli": "^18.2.1", "@cypress/schematic": "^2.5.0", "@types/jasmine": "~4.3.5", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", - "cypress": "^12.17.1", - "eslint": "^8.45.0", + "@types/jest": "^29.5.12", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "cypress": "^13.14.2", + "eslint": "^8.57.0", "jasmine-core": "~5.0.1", "karma": "~6.4.2", "karma-chrome-launcher": "~3.2.0", @@ -60,6 +66,6 @@ "karma-junit-reporter": "^2.0.1", "npm": "^9.8.0", "ts-protoc-gen": "^0.15.0", - "typescript": "~5.1.6" + "typescript": "~5.5.4" } } diff --git a/src/ui/client/src/.gitignore b/src/ui/client/src/.gitignore new file mode 100644 index 0000000..32cf1ca --- /dev/null +++ b/src/ui/client/src/.gitignore @@ -0,0 +1,2 @@ +proto/ +!proto/.gitkeep diff --git a/src/ui/client/src/app/app-routing.module.ts b/src/ui/client/src/app/app-routing.module.ts index 5600659..0106fce 100644 --- a/src/ui/client/src/app/app-routing.module.ts +++ b/src/ui/client/src/app/app-routing.module.ts @@ -6,6 +6,7 @@ import { NotificationExceptionsComponent } from "./notification-exceptions/notif import { ResourceGroupDetailsComponent } from "./resource-group-details/resource-group-details.component" import { ResourceGroupsComponent } from "./resource-groups/resource-groups.component" import { StatsComponent } from "./stats/stats.component" +import {UIDemoComponent} from "./ui-demo/ui-demo.component"; const routes: Routes = [ { @@ -24,6 +25,10 @@ const routes: Routes = [ path: "exceptions/new/:notificationName", component: NotificationExceptionFormComponent, }, + { + path: "ui-demo", + component: UIDemoComponent, + } ], }, diff --git a/src/ui/client/src/app/app.module.ts b/src/ui/client/src/app/app.module.ts index 1c1d2cc..dd7e1ea 100644 --- a/src/ui/client/src/app/app.module.ts +++ b/src/ui/client/src/app/app.module.ts @@ -5,23 +5,29 @@ import { AuthenticationStore } from "./state/authentication.store" import { BrowserAnimationsModule } from "@angular/platform-browser/animations" import { BrowserModule } from "@angular/platform-browser" import { CookieService } from "ngx-cookie-service" -import { FilterKeyValuePipe } from "./filter.pipe" +import {FilterKeyValuePipe, ParseExternalIdPipe, ShortenDescriptionPipe, StructValueToStringPipe} from "./filter.pipe" import { FilterNamePipe, MapByTypePipe, MapByObservedValuesPipe, mapFlatRulesPipe } from "./resource-group-details/resource-group-details.pipe" import { FilterNoObservationsPipe, FilterObsPipe, reverseSortPipe } from "./filter.pipe" import { FormsModule, ReactiveFormsModule } from "@angular/forms" -import { HistogramHorizontalComponent } from "./histogram-horizontal/histogram-horizontal.component" +import { ObservationsStatsComponent } from "./observations-stats/observations-stats.component" import { MarkdownModule } from "ngx-markdown" import { MatButtonModule } from "@angular/material/button" import { MatCardModule } from "@angular/material/card" import { MatDatepickerModule } from "@angular/material/datepicker" import { MatDialogModule } from "@angular/material/dialog" +import { MatExpansionModule } from "@angular/material/expansion" import { MatFormFieldModule } from "@angular/material/form-field" import { MatIconModule } from "@angular/material/icon" import { MatInputModule } from "@angular/material/input" -import { MatNativeDateModule } from "@angular/material/core" +import { MatListModule } from "@angular/material/list" +import { MatMenuModule } from "@angular/material/menu"; +import {MatNativeDateModule, MatRippleModule} from "@angular/material/core" import { MatProgressBarModule } from "@angular/material/progress-bar" import { MatSnackBarModule } from "@angular/material/snack-bar" +import { MatSidenavModule } from "@angular/material/sidenav" import { MatTableModule } from "@angular/material/table" +import { MatToolbarModule } from "@angular/material/toolbar"; +import { MatTooltipModule } from "@angular/material/tooltip"; import { ModronAppComponent } from "./modron-app/modron-app.component" import { ModronService } from "./modron.service" import { ModronStore } from "./state/modron.store" @@ -32,40 +38,82 @@ import { NotificationExceptionsFilterPipe } from "./notification-exceptions/noti import { NotificationService } from "./notification.service" import { NotificationStore } from "./state/notification.store" import { ObservationDetailsComponent } from "./observation-details/observation-details.component" -import { ObservationsPipe, MapPerTypeName, ResourceGroupsPipe, InvalidProjectNb, ObsNbPipe } from "./resource-groups/resource-groups.pipe" +import { + ObservationsPipe, + MapPerTypeName, + ResourceGroupsPipe, + InvalidProjectNb, + ObsNbPipe, + MapByRiskScorePipe +} from "./resource-groups/resource-groups.pipe" import { ResourceGroupComponent } from "./resource-group/resource-group.component" import { ResourceGroupDetailsComponent } from "./resource-group-details/resource-group-details.component" import { ResourceGroupsComponent } from "./resource-groups/resource-groups.component" import { SearchObsComponent } from "./search-obs/search-obs.component" +import { SeverityIndicatorComponent } from "./severity-indicator/severity-indicator.component"; import { StatsComponent } from "./stats/stats.component" +import {SidenavComponent} from "./sidenav/sidenav.component"; +import {MatCheckboxModule} from "@angular/material/checkbox"; +import {NgOptimizedImage} from "@angular/common"; +import {MatBadgeModule} from "@angular/material/badge"; +import {FromNowPipe} from "./resource-group/resource-group.pipe"; +import {ImpactNamePipe, SeverityAmountPipe, SeverityNamePipe} from "./severity-indicator/severity-indicator.pipe"; +import {UIDemoComponent} from "./ui-demo/ui-demo.component"; +import {MatSortModule} from "@angular/material/sort"; +import {NotificationBellButtonComponent} from "./notif-bell-button/notif-bell-button.component"; +import {ObservationDetailsDialogComponent} from "./observation-details-dialog/observation-details-dialog.component"; +import { + ObservationDetailsDialogContentComponent +} from "./observation-details-dialog-content/observation-details-dialog-content.component"; +import {ImpactIndicatorComponent} from "./impact-indicator/impact-indicator.component"; +import {CategoryNamePipe} from "./observation-details-dialog-content/observation-details-dialog-content.filter"; +import {ObservationsTableComponent} from "./observations-table/observations-table.component"; +import {BaseChartDirective, provideCharts, withDefaultRegisterables} from "ng2-charts"; +import {MatGridList, MatGridTile} from "@angular/material/grid-list"; @NgModule({ declarations: [ AppComponent, + CategoryNamePipe, FilterKeyValuePipe, FilterNamePipe, FilterNoObservationsPipe, FilterObsPipe, - HistogramHorizontalComponent, + FromNowPipe, + ObservationsStatsComponent, + ImpactIndicatorComponent, + ImpactNamePipe, InvalidProjectNb, MapByObservedValuesPipe, MapByTypePipe, mapFlatRulesPipe, MapPerTypeName, + MapByRiskScorePipe, ModronAppComponent, + NotificationBellButtonComponent, NotificationExceptionFormComponent, NotificationExceptionsComponent, NotificationExceptionsFilterPipe, ObservationDetailsComponent, + ObservationDetailsDialogComponent, + ObservationDetailsDialogContentComponent, ObservationsPipe, + ObservationsTableComponent, ObsNbPipe, + ParseExternalIdPipe, ResourceGroupComponent, ResourceGroupDetailsComponent, ResourceGroupsComponent, ResourceGroupsPipe, reverseSortPipe, SearchObsComponent, + SeverityIndicatorComponent, + SeverityAmountPipe, + SeverityNamePipe, + ShortenDescriptionPipe, StatsComponent, + StructValueToStringPipe, + UIDemoComponent, ], imports: [ AppRoutingModule, @@ -73,18 +121,33 @@ import { StatsComponent } from "./stats/stats.component" BrowserModule, FormsModule, MarkdownModule.forRoot(), + MatBadgeModule, MatButtonModule, MatCardModule, + MatCheckboxModule, MatDatepickerModule, MatDialogModule, + MatExpansionModule, MatFormFieldModule, MatIconModule, MatInputModule, + MatListModule, + MatMenuModule, MatNativeDateModule, MatProgressBarModule, + MatSidenavModule, MatSnackBarModule, MatTableModule, + MatToolbarModule, + MatTooltipModule, + NgOptimizedImage, ReactiveFormsModule, + SidenavComponent, + MatSortModule, + MatRippleModule, + BaseChartDirective, + MatGridList, + MatGridTile, ], providers: [ AuthenticationService, @@ -94,6 +157,7 @@ import { StatsComponent } from "./stats/stats.component" ModronStore, NotificationService, NotificationStore, + provideCharts(withDefaultRegisterables()) ], bootstrap: [AppComponent], }) diff --git a/src/ui/client/src/app/filter.pipe.ts b/src/ui/client/src/app/filter.pipe.ts index da62975..4660197 100644 --- a/src/ui/client/src/app/filter.pipe.ts +++ b/src/ui/client/src/app/filter.pipe.ts @@ -1,7 +1,7 @@ import { KeyValue } from "@angular/common" import { Pipe, PipeTransform } from "@angular/core" import { Value } from "google-protobuf/google/protobuf/struct_pb" -import { Observation, Resource } from "src/proto/modron_pb" +import { Observation, ResourceRef } from "../proto/modron_pb" @Pipe({ name: "filterObs", @@ -26,12 +26,12 @@ export class FilterObsPipe implements PipeTransform { return items.filter((it) => { return ( - (it.getResource() as Resource) - .getName() + (it.getResourceRef() as ResourceRef) + .getExternalId() .toLocaleLowerCase() .includes(resource) && - (it.getResource() as Resource) - .getResourceGroupName().replace("projects/", "") + (it.getResourceRef() as ResourceRef) + .getGroupName().replace("projects/", "") .toLocaleLowerCase() .includes(group) && (it.getObservedValue() @@ -95,3 +95,55 @@ export class reverseSortPipe implements PipeTransform { return items.sort((a, b) => a.value.length - b.value.length).reverse() } } + +@Pipe({ + name: "shortenDescription", +}) +export class ShortenDescriptionPipe implements PipeTransform { + transform(value: string | undefined | null) { + if(value === undefined || value === null) { + return ""; + } + return value.split("\n")[0] + } +} + +@Pipe({ + name: "parseExternalId", +}) +export class ParseExternalIdPipe implements PipeTransform { + transform(value: string | undefined | null) { + if(value === undefined || value === null) { + return ""; + } + + const regex = /^\/\/container\.googleapis\.com\/projects\/[^\\/]+\/locations\/[^\\/]+\/clusters\/[^\\/]+\/k8s\/namespaces\/[^\\/]+\/apps\/((?:deployments|daemonsets)\/[^\\/]+)$/; + const matches = value.match(regex); + if (matches) { + return matches[1]; + } + + return value.split("/", -1).pop() || "" + } +} + +@Pipe({ + name: "structValueToString" +}) +export class StructValueToStringPipe implements PipeTransform { + transform(value: Value | null | undefined): string { + if (value === undefined || value === null) { + return "" + } + if(value.hasBoolValue()) { + return value.getBoolValue().toString() + } + if(value.hasStringValue()) { + return value.getStringValue() + } + if(value.hasNumberValue()) { + return value.getNumberValue().toString() + } + return value.toString() + } +} diff --git a/src/ui/client/src/app/impact-indicator/impact-indicator.component.html b/src/ui/client/src/app/impact-indicator/impact-indicator.component.html new file mode 100644 index 0000000..117d92a --- /dev/null +++ b/src/ui/client/src/app/impact-indicator/impact-indicator.component.html @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/client/src/app/impact-indicator/impact-indicator.component.scss b/src/ui/client/src/app/impact-indicator/impact-indicator.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/ui/client/src/app/impact-indicator/impact-indicator.component.ts b/src/ui/client/src/app/impact-indicator/impact-indicator.component.ts new file mode 100644 index 0000000..8537745 --- /dev/null +++ b/src/ui/client/src/app/impact-indicator/impact-indicator.component.ts @@ -0,0 +1,30 @@ +import {Component, Input} from "@angular/core"; +import {Impact, Severity} from "../../proto/modron_pb"; + +@Component( + { + selector: "app-impact-indicator", + templateUrl: "./impact-indicator.component.html", + styleUrls: ["./impact-indicator.component.scss"], + } +) +export class ImpactIndicatorComponent { + @Input() + public impact: Impact = Impact.IMPACT_UNKNOWN; + constructor() { + } + + // We reuse the severity indicator component to display the impact + public severity(): Severity { + switch (this.impact) { + case Impact.IMPACT_HIGH: + return Severity.SEVERITY_HIGH; + case Impact.IMPACT_MEDIUM: + return Severity.SEVERITY_MEDIUM; + case Impact.IMPACT_LOW: + return Severity.SEVERITY_LOW; + default: + return Severity.SEVERITY_UNKNOWN; + } + } +} diff --git a/src/ui/client/src/app/model/modron.model.ts b/src/ui/client/src/app/model/modron.model.ts index e485704..212aa0c 100644 --- a/src/ui/client/src/app/model/modron.model.ts +++ b/src/ui/client/src/app/model/modron.model.ts @@ -1,4 +1,7 @@ +import {RequestStatus, ScanType} from "../../proto/modron_pb"; + export type StatusInfo = { - state: number - resourceGroups: string[] + state: RequestStatus + resourceGroups: string[] + scanType: ScanType } diff --git a/src/ui/client/src/app/modron-app/modron-app.component.html b/src/ui/client/src/app/modron-app/modron-app.component.html index 70a1e70..3a037ae 100644 --- a/src/ui/client/src/app/modron-app/modron-app.component.html +++ b/src/ui/client/src/app/modron-app/modron-app.component.html @@ -1,40 +1,19 @@ -
-
-
-

{{ organization | uppercase }}

-

MODRON

-
-
- -
- - -
- +
+ +
+ + + Modron + + +
+
diff --git a/src/ui/client/src/app/modron-app/modron-app.component.scss b/src/ui/client/src/app/modron-app/modron-app.component.scss index d79ee23..506218d 100644 --- a/src/ui/client/src/app/modron-app/modron-app.component.scss +++ b/src/ui/client/src/app/modron-app/modron-app.component.scss @@ -1,86 +1,47 @@ -.grey { - color: rgb(220, 220, 220); -} - -svg:focus { - outline: none; -} +@use '../../colors.scss' as colors; -.modron-app-ctn { +.modron-app { + position: absolute; display: flex; - flex-direction: column; - height: 100vh; - - header { - align-items: center; + top: 0; + left: 0; + right: 0; + bottom: 0; + + .sidenav { + top: 0; + bottom: 0; + position: fixed; display: flex; - flex-direction: row; - justify-content: space-between; - padding: 15px 30px; + } - div { - display: flex; - flex-direction: row; - align-items: center; + .app-content { + position: relative; + margin-left: 68px; + width: calc(100% - 68px); - h1 { - margin: 1px 10px; - font-size: 60px; - } + .toolbar { + position: fixed; + display: flex; + height: 60px; + z-index: 300; - svg { - cursor: pointer; - height: 55px; + img.logo { + height: 60%; + margin-right: 4px; } - svg:hover { - fill: rgb(63, 63, 63); + .app-title { + font-weight: 400; + color: colors.$title; + text-transform: uppercase; } } - .logout { - margin: 0px 10px 0px 10px; - cursor: pointer; - } - } - - .modron-app-main { - display: flex; - flex-direction: row; - flex-grow: 1; - - svg { - cursor: pointer; - height: 55px; - } - - .nav-btn { - cursor: pointer; - height: 35px; - padding: 5px 0; - } - - nav { - display: flex; - flex-direction: column; - gap: 40px; - height: 100%; - margin: 10px; - padding: 0 30px; - width: 50px; - } - - .nav-btn>mat-icon { - align-items: center; - display: flex; - height: 55px; - justify-content: center; - font-size: 4em; - } - - .app { - width: 100%; - height: 100%; + .router-container { + margin: 16px; + margin-top: 60px; + overflow: hidden; } } } diff --git a/src/ui/client/src/app/modron-app/modron-app.component.ts b/src/ui/client/src/app/modron-app/modron-app.component.ts index a243794..fc20555 100644 --- a/src/ui/client/src/app/modron-app/modron-app.component.ts +++ b/src/ui/client/src/app/modron-app/modron-app.component.ts @@ -1,5 +1,6 @@ -import { Component } from "@angular/core" -import { environment } from "src/environments/environment" +import { Component, EventEmitter, Input, Output } from "@angular/core" +import { environment } from "../../environments/environment" +import { Router } from "@angular/router"; @Component({ selector: "app-modron-app", @@ -8,12 +9,26 @@ import { environment } from "src/environments/environment" }) export class ModronAppComponent { public organization: string + public href= ""; - constructor() { + @Input() isExpanded: boolean = false; + @Output() toggleMenu = new EventEmitter(); + + constructor(private router: Router) { this.organization = environment.organization } get production(): boolean { return environment.production } + + get currentUrl(): string { + return this.router.url; + } + + public navItems = [ + { link: "/modron/resourcegroups", name: "Resource Groups", icon: "folder" }, + { link: "/modron/stats", name: "Stats", icon: "bar_chart" }, + { link: "/modron/exceptions", name: "Exceptions", icon: "notifications_paused" }, + ]; } diff --git a/src/ui/client/src/app/modron.service.ts b/src/ui/client/src/app/modron.service.ts index da25e23..bd78ab2 100644 --- a/src/ui/client/src/app/modron.service.ts +++ b/src/ui/client/src/app/modron.service.ts @@ -1,10 +1,17 @@ -import { environment } from "src/environments/environment" -import { ModronServiceClient } from "src/proto/modron_pb_service" +import {environment} from "src/environments/environment" +import {ModronServiceClient} from "../proto/ModronServiceClientPb" -import { Injectable } from "@angular/core" -import { concat, EMPTY, from, mergeMap, Observable } from "rxjs" - -import * as pb from "src/proto/modron_pb" +import {Injectable} from "@angular/core" +import {concat, EMPTY, from, mergeMap, Observable} from "rxjs" +import { + CollectAndScanRequest, + CollectAndScanResponse, + GetStatusCollectAndScanRequest, + GetStatusCollectAndScanResponse, + ListObservationsRequest, + ListObservationsResponse, + Observation +} from "../proto/modron_pb"; @Injectable({ providedIn: "root", @@ -22,17 +29,17 @@ export class ModronService { listObservations( resourceGroups: string[] - ): Observable>> { + ): Observable>> { const fetchPage = ( pageToken: string | null - ): Observable => { - const req = new pb.ListObservationsRequest() + ): Observable => { + const req = new ListObservationsRequest() req.setResourceGroupNamesList(resourceGroups) req.setPageSize(ModronService.PAGE_SIZE) req.setPageToken(pageToken ?? "") return new Observable((sub) => { - this._client.listObservations(req, (err, res) => { + this._client.listObservations(req, {}, (err, res) => { if (err !== null) { return sub.error(`listObservations: ${err}`) } @@ -47,13 +54,13 @@ export class ModronService { } const fetchObs = ( pageToken: string | null = null - ): Observable>> => { + ): Observable>> => { return fetchPage(pageToken).pipe( mergeMap((res) => { // deepcode ignore CollectionUpdatedButNeverQueried: Used, false positive. - const obs = new Map>() + const obs = new Map>() res.getResourceGroupsObservationsList().forEach((v) => { - const map = new Map() + const map = new Map() v.getRulesObservationsList().forEach((r) => map.set(r.getRule(), r.getObservationsList()) ) @@ -71,12 +78,12 @@ export class ModronService { return fetchObs() } - collectAndScan(resourceGroups: string[]): Observable { - const fetchPage = (): Observable => { - const req = new pb.CollectAndScanRequest() + collectAndScan(resourceGroups: string[]): Observable { + const fetchPage = (): Observable => { + const req = new CollectAndScanRequest() req.setResourceGroupNamesList(resourceGroups.map( (rg) => { - if (!rg.startsWith("projects/")) { + if (rg.indexOf("/") === -1) { return `projects/${rg}` } return rg @@ -84,7 +91,7 @@ export class ModronService { )) return new Observable((sub) => { - this._client.collectAndScan(req, (err, res) => { + this._client.collectAndScan(req, {}, (err, res) => { if (err !== null) { return sub.error(`collectAndScan: ${err}`) } @@ -98,13 +105,31 @@ export class ModronService { return fetchPage() } - getCollectAndScanStatus(IDs: string): Observable { - const req = new pb.GetStatusCollectAndScanRequest() + collectAndScanAll(): Observable { + const fetchPage = (): Observable => { + const req = new CollectAndScanRequest() + return new Observable((sub) => { + this._client.collectAndScanAll(req, {}, (err, res) => { + if (err !== null) { + return sub.error(`collectAndScanAll: ${err}`) + } + if (res === null) { + return sub.error("collectAndScanAll: unexpected null response") + } + return sub.next(res) + }) + }) + } + return fetchPage() + } + + getCollectAndScanStatus(IDs: string): Observable { + const req = new GetStatusCollectAndScanRequest() req.setCollectId(IDs.split(ModronService.SEPARATOR)[0]) req.setScanId(IDs.split(ModronService.SEPARATOR)[1]) return new Observable((sub) => { - this._client.getStatusCollectAndScan(req, (err, res) => { + this._client.getStatusCollectAndScan(req, {}, (err, res) => { if (err !== null) { return sub.error(`getScanStatus: ${err}`) } diff --git a/src/ui/client/src/app/notif-bell-button/notif-bell-button.component.html b/src/ui/client/src/app/notif-bell-button/notif-bell-button.component.html new file mode 100644 index 0000000..0bdfe5f --- /dev/null +++ b/src/ui/client/src/app/notif-bell-button/notif-bell-button.component.html @@ -0,0 +1,36 @@ +
+
+ + edit_notifications + + + notifications_off + + +
diff --git a/src/ui/client/src/app/notif-bell-button/notif-bell-button.component.scss b/src/ui/client/src/app/notif-bell-button/notif-bell-button.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/ui/client/src/app/notif-bell-button/notif-bell-button.component.ts b/src/ui/client/src/app/notif-bell-button/notif-bell-button.component.ts new file mode 100644 index 0000000..c6e7141 --- /dev/null +++ b/src/ui/client/src/app/notif-bell-button/notif-bell-button.component.ts @@ -0,0 +1,71 @@ +import {Component, Input} from "@angular/core"; +import {Observation} from "../../proto/modron_pb"; +import {NotificationStore} from "../state/notification.store"; +import {NotificationExceptionsFilterPipe} from "../notification-exceptions/notification-exceptions.pipe"; +import {NotificationExceptionFormComponent} from "../notification-exception-form/notification-exception-form.component"; +import {NotificationException} from "../model/notification.model"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {MatDialog} from "@angular/material/dialog"; +import {Router} from "@angular/router"; + +@Component( + { + selector: "app-notif-bell-button", + templateUrl: "./notif-bell-button.component.html", + styleUrls: ["./notif-bell-button.component.scss"], + } +) +export class NotificationBellButtonComponent { + @Input() + public observation: Observation|undefined; + static readonly SNACKBAR_LINGER_DURATION_MS = 2500; + + constructor( + public notification: NotificationStore, + private _dialog: MatDialog, + private _snackBar: MatSnackBar, + private _router: Router, + ) { + } + + exceptionNameFromObservation(ob: Observation): string { + const resource = ob.getResourceRef() + return `${resource?.getGroupName().replace(new RegExp("/"), "_")}-${resource?.getExternalId()}-${ob.getName()}` + } + + notifyToggle(ob: Observation): void { + const expName = this.exceptionNameFromObservation(ob); + if ( + new NotificationExceptionsFilterPipe().transform( + this.notification.exceptions, + expName + ).length == 0 + ) { + const dialogRef = this._dialog.open(NotificationExceptionFormComponent, { + data: expName, + }); + dialogRef + .afterClosed() + .subscribe((ret: NotificationException | Error | boolean) => { + if(ret === false) { + return + } + if (ret instanceof NotificationException) { + this._snackBar.open( + "Notification exception created successfully", + "", + { + duration: NotificationBellButtonComponent.SNACKBAR_LINGER_DURATION_MS, + } + ); + } else { + this._snackBar.open("Creating notification exception failed", "", { + duration: NotificationBellButtonComponent.SNACKBAR_LINGER_DURATION_MS, + }); + } + }); + } else { + this._router.navigate(["modron", "exceptions", expName]); + } + } +} diff --git a/src/ui/client/src/app/notification-exception-form/notification-exception-form.component.html b/src/ui/client/src/app/notification-exception-form/notification-exception-form.component.html index d7b4feb..b2c6e51 100644 --- a/src/ui/client/src/app/notification-exception-form/notification-exception-form.component.html +++ b/src/ui/client/src/app/notification-exception-form/notification-exception-form.component.html @@ -60,12 +60,11 @@

New exception

- +
+ + diff --git a/src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.component.scss b/src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.component.scss new file mode 100644 index 0000000..15d0380 --- /dev/null +++ b/src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.component.scss @@ -0,0 +1,69 @@ +.dialog-content { + display: flex; + flex-direction: column; + width: 100%; + + h1, h2, h3, h4, h5, h6 { + margin-bottom: 0.5em; + } + + p { + margin-top: 0; + } +} + +.observation-details { + display: grid; + grid-template-columns: 1fr 2fr; + margin: 16px; + row-gap: 4px; + + p { + margin: 0; + } + + .hdr { + font-weight: bold; + } +} + +.risk-score { + display: grid; + grid-template-columns: 80px 1fr; + column-gap: 8px; + align-items: center; + width: 100%; + + .main-indicator { + justify-self: center; + } + + .severity-impact-container { + display: grid; + align-items: start; + row-gap: 18px; + grid-template-columns: 80px 1fr; + margin-top: 1em; + + .indicator { + justify-self: center; + } + + div.expl { + h1,h2,h3,h4,h5,h6 { + margin: 0 0 0.5em; + } + + code { + background-color: #eeeeee; + padding: 3px; + } + + } + } +} + +.short-risk-score-description { + margin-top: 1em; + margin-bottom: 1em; +} diff --git a/src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.component.ts b/src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.component.ts new file mode 100644 index 0000000..a3ae3d2 --- /dev/null +++ b/src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.component.ts @@ -0,0 +1,15 @@ +import {Component, Input} from "@angular/core"; +import {Impact, Observation} from "../../proto/modron_pb"; + +@Component({ + selector: "app-observation-details-dialog-content", + templateUrl: "./observation-details-dialog-content.component.html", + styleUrls: ["./observation-details-dialog-content.component.scss"], +}) +export class ObservationDetailsDialogContentComponent { + @Input() + observation!: Observation; + constructor() {} + + protected readonly Impact = Impact; +} diff --git a/src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.filter.ts b/src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.filter.ts new file mode 100644 index 0000000..f40caf0 --- /dev/null +++ b/src/ui/client/src/app/observation-details-dialog-content/observation-details-dialog-content.filter.ts @@ -0,0 +1,21 @@ +import {Pipe, PipeTransform} from "@angular/core"; +import {Observation} from "../../proto/modron_pb"; +import Category = Observation.Category; + +@Pipe({ + name: "categoryName" +}) +export class CategoryNamePipe implements PipeTransform { + transform(cat: Category): string { + switch(cat) { + case Category.CATEGORY_VULNERABILITY: + return "Vulnerability"; + case Category.CATEGORY_MISCONFIGURATION: + return "Misconfiguration"; + case Category.CATEGORY_TOXIC_COMBINATION: + return "Toxic Combination"; + default: + return "Unknown"; + } + } +} diff --git a/src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.html b/src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.html new file mode 100644 index 0000000..db47510 --- /dev/null +++ b/src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.html @@ -0,0 +1,5 @@ +
+ +
diff --git a/src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.scss b/src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.scss new file mode 100644 index 0000000..de724ad --- /dev/null +++ b/src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.scss @@ -0,0 +1,3 @@ +.observation-details-dialog { + padding: 20px; +} diff --git a/src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.ts b/src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.ts new file mode 100644 index 0000000..76c8c4b --- /dev/null +++ b/src/ui/client/src/app/observation-details-dialog/observation-details-dialog.component.ts @@ -0,0 +1,17 @@ +import {Component, Inject} from "@angular/core"; +import {MAT_DIALOG_DATA} from "@angular/material/dialog"; +import {Observation} from "../../proto/modron_pb"; + +@Component( + { + selector: "app-observation-details-dialog", + templateUrl: "./observation-details-dialog.component.html", + styleUrls: ["./observation-details-dialog.component.scss"], + } +) +export class ObservationDetailsDialogComponent { + constructor( + @Inject(MAT_DIALOG_DATA) public observation: Observation + ) { + } +} diff --git a/src/ui/client/src/app/observation-details/observation-details.component.html b/src/ui/client/src/app/observation-details/observation-details.component.html index 28e5400..67efac4 100644 --- a/src/ui/client/src/app/observation-details/observation-details.component.html +++ b/src/ui/client/src/app/observation-details/observation-details.component.html @@ -1,92 +1,128 @@
-
- v - > - Resource: {{this.parseName(ob.getResource()?.getName()) }} -
+ + + + + {{ + ob.getResourceRef()?.getExternalId() | parseExternalId + }} + + + {{ + ob.getRemediation()?.getDescription() | shortenDescription + }} + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Risk Score + + {{ getSeverity(ob.getRiskScore()).toUpperCase() }} + +
Impact + + {{ ob.getImpact() | impactName | uppercase }} + +
Severity + + {{ getSeverity(ob.getSeverity()).toUpperCase() }} + +
Finding Class{{ getCategoryName(ob.getCategory()) }}
Time of scan{{ ob.getTimestamp()?.toDate()?.toUTCString() }}
Expected{{ this.getExpectedValue(ob) }}
Observed{{ this.getObservedValue(ob) }}
-

-

- Resource Group: - {{ ob.getResource()?.getResourceGroupName() }} - {{ ob.getResource()?.getResourceGroupName() }} - {{ ob.getResource()?.getResourceGroupName() }} -

-

- Resource Time: - {{ ob.getResource()?.getTimestamp()?.toDate()?.toUTCString() }} -

-

-
-
-

Observation:

-

-

Time of scan: {{ ob.getTimestamp()?.toDate()?.toUTCString() }}

-

-
-
-
-
-

Expected:

-
-
- {{ this.getExpectedValue(ob) }} -
-
-

Observed:

-
-
- {{ this.getObservedValue(ob) }} -
-
-
-
-

Finding:

-
- {{ ob.getRemediation()?.getDescription() }}. +

Finding

+
+ {{ ob.getRemediation()?.getDescription() }}
-
+
-

Recommendation:

+

Recommendation

-
- {{ ob.getRemediation()?.getRecommendation() }}. +
+ {{ ob.getRemediation()?.getRecommendation() }}
-
+ -
- - - - - +
+
- - - - + | filterExceptions : this.exceptionNameFromObservation(ob) + ).length > 0; + then has_exceptions; + else has_no_exceptions + " + >
+ + notifications + + + + notifications_off + + +
diff --git a/src/ui/client/src/app/observation-details/observation-details.component.scss b/src/ui/client/src/app/observation-details/observation-details.component.scss index dba8efa..d3d218a 100644 --- a/src/ui/client/src/app/observation-details/observation-details.component.scss +++ b/src/ui/client/src/app/observation-details/observation-details.component.scss @@ -78,13 +78,13 @@ .remediation { background-color: #ffffff; align-self: stretch; - padding: 7px; } -.recommendation { - background-color: #ff832b67; +.recommendation-box { + border-radius: 8px; + background-color: #fff5c0; align-self: stretch; - padding: 7px; + padding: 4px 16px; } .inline { @@ -94,8 +94,26 @@ } .notify-ctn { - svg { - width: 40px; + display: flex; + .notifications-toggle { cursor: pointer; + transform: scale(1.25); + margin-top: 10px; + } +} + + +table.observation-properties { + td { + padding: 0px 8px; + } + + td:first-child { + font-weight: bold; + padding-left: 0; + } + + td > span.severity-value { + text-decoration: underline #333 dotted; } } diff --git a/src/ui/client/src/app/observation-details/observation-details.component.spec.ts b/src/ui/client/src/app/observation-details/observation-details.component.spec.ts index 05ac0a8..fb1dbaa 100644 --- a/src/ui/client/src/app/observation-details/observation-details.component.spec.ts +++ b/src/ui/client/src/app/observation-details/observation-details.component.spec.ts @@ -6,6 +6,8 @@ import { AuthenticationStore } from "../state/authentication.store"; import { NotificationStore } from "../state/notification.store"; import { ObservationDetailsComponent } from "./observation-details.component"; +import { ParseExternalIdPipe, ShortenDescriptionPipe } from "../filter.pipe"; +import {ImpactNamePipe, SeverityNamePipe} from "../severity-indicator/severity-indicator.pipe"; describe("ObservationDetailsComponent", () => { let component: ObservationDetailsComponent; @@ -17,6 +19,10 @@ describe("ObservationDetailsComponent", () => { declarations: [ ObservationDetailsComponent, NotificationExceptionsFilterPipe, + ImpactNamePipe, + ParseExternalIdPipe, + SeverityNamePipe, + ShortenDescriptionPipe, ], providers: [AuthenticationStore, NotificationStore], }).compileComponents(); diff --git a/src/ui/client/src/app/observation-details/observation-details.component.ts b/src/ui/client/src/app/observation-details/observation-details.component.ts index d9a7b8d..7e225c6 100644 --- a/src/ui/client/src/app/observation-details/observation-details.component.ts +++ b/src/ui/client/src/app/observation-details/observation-details.component.ts @@ -1,12 +1,12 @@ -import { ChangeDetectionStrategy, Component, Input } from "@angular/core" -import { MatDialog } from "@angular/material/dialog" -import { MatSnackBar } from "@angular/material/snack-bar" -import { Router } from "@angular/router" -import { Observation } from "src/proto/modron_pb" -import { NotificationException } from "../model/notification.model" -import { NotificationExceptionFormComponent } from "../notification-exception-form/notification-exception-form.component" -import { NotificationExceptionsFilterPipe } from "../notification-exceptions/notification-exceptions.pipe" -import { NotificationStore } from "../state/notification.store" +import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; +import { MatDialog } from "@angular/material/dialog"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { Router } from "@angular/router"; +import { Observation, Severity } from "../../proto/modron_pb"; +import { NotificationException } from "../model/notification.model"; +import { NotificationExceptionFormComponent } from "../notification-exception-form/notification-exception-form.component"; +import { NotificationExceptionsFilterPipe } from "../notification-exceptions/notification-exceptions.pipe"; +import { NotificationStore } from "../state/notification.store"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -15,15 +15,21 @@ import { NotificationStore } from "../state/notification.store" styleUrls: ["./observation-details.component.scss"], }) export class ObservationDetailsComponent { - private static readonly SNACKBAR_LINGER_DURATION_MS = 2500; + readonly Severity = Severity; + static readonly SNACKBAR_LINGER_DURATION_MS = 2500; - private readonly BASE_GCP_URL = "https://console.cloud.google.com" - readonly FOLDER_URL = `${this.BASE_GCP_URL}/welcome?folder=` - readonly ORGANIZATION_URL = `${this.BASE_GCP_URL}/welcome?organizationId=` - readonly PROJECT_URL = `${this.BASE_GCP_URL}/home/dashboard?project=` + private readonly BASE_GCP_URL = "https://console.cloud.google.com"; + readonly FOLDER_URL = `${this.BASE_GCP_URL}/welcome?folder=`; + readonly ORGANIZATION_URL = `${this.BASE_GCP_URL}/welcome?organizationId=`; + readonly PROJECT_URL = `${this.BASE_GCP_URL}/home/dashboard?project=`; @Input() ob: Observation = new Observation(); + @Input() + public expanded: boolean = true; + @Input() + public showActions: boolean = true; + public notifications: Map = new Map(); constructor( @@ -31,35 +37,100 @@ export class ObservationDetailsComponent { private _dialog: MatDialog, private _snackBar: MatSnackBar, private _router: Router - ) { } - + ) {} display: Map = new Map(); toggle(name: string) { if (this.display.has(name)) { - this.display.set(name, !(this.display.get(name) as boolean)) + this.display.set(name, !(this.display.get(name) as boolean)); } else { - this.display.set(name, true) + this.display.set(name, true); + } + } + + getColor(severity: number): string { + switch (severity) { + case Severity.SEVERITY_CRITICAL: + return "red"; + case Severity.SEVERITY_HIGH: + return "orange"; + case Severity.SEVERITY_MEDIUM: + return "yellow"; + case Severity.SEVERITY_LOW: + return "green"; + default: + return "black"; + } + } + + getSeverity(severity: number): string { + switch (severity) { + case Severity.SEVERITY_CRITICAL: + return "Critical"; + case Severity.SEVERITY_HIGH: + return "High"; + case Severity.SEVERITY_MEDIUM: + return "Medium"; + case Severity.SEVERITY_LOW: + return "Low"; + case Severity.SEVERITY_INFO: + return "Info"; + default: + return "Unknown"; + } + } + + getCategoryName(category: number): string { + switch (category) { + case Observation.Category.CATEGORY_VULNERABILITY: + return "Vulnerability"; + case Observation.Category.CATEGORY_MISCONFIGURATION: + return "Misconfiguration"; + case Observation.Category.CATEGORY_TOXIC_COMBINATION: + return "Toxic Combination"; + } + return "UNKNOWN"; + } + + getRgLink(observation: Observation): string { + const rgName = this.getRgName(observation); + if(rgName.startsWith("folders/")) { + return `${this.FOLDER_URL}${rgName.replace("folders/", "")}`; + } + if(rgName.startsWith("organizations/")) { + return `${this.ORGANIZATION_URL}${rgName.replace("organizations/", "")}`; + } + if(rgName.startsWith("projects/")) { + return `${this.PROJECT_URL}${rgName.replace("projects/", "")}`; + } + return ""; + } + + getRgName(observation: Observation): string { + const resource = observation.getResourceRef(); + if (resource === undefined) { + return ""; } + return resource.getGroupName(); } getObservedValue(ob: Observation): string | undefined { - return ob.getObservedValue()?.toString()?.replace(/,/g, "") + return ob.getObservedValue()?.toString()?.replace(/,/g, ""); } getExpectedValue(ob: Observation): string | undefined { - return ob.getExpectedValue()?.toString()?.replace(/,/g, "") + return ob.getExpectedValue()?.toString()?.replace(/,/g, ""); } parseName(ob: string | undefined): string | undefined { if (!(ob?.includes("[") && ob?.includes("]"))) { - return ob + return ob; } - return ob?.replace(/(\[.*\]$)/g, "") + return ob?.replace(/(\[.*]$)/g, ""); } - notifyToggle(ob: Observation): void { - const expName = this.exceptionNameFromObservation(ob) + async notifyToggle(ob: Observation): Promise { + const expName = this.exceptionNameFromObservation(ob); if ( new NotificationExceptionsFilterPipe().transform( this.notification.exceptions, @@ -68,16 +139,14 @@ export class ObservationDetailsComponent { ) { const dialogRef = this._dialog.open(NotificationExceptionFormComponent, { data: expName, - }) + }); dialogRef .afterClosed() - .subscribe((ret: NotificationException | Error) => { - const isNotificationException = ( - ret: NotificationException | Error - ): ret is NotificationException => { - return ret !== undefined + .subscribe((ret: NotificationException | Error | boolean) => { + if(ret === false) { + return } - if (isNotificationException(ret)) { + if (ret instanceof NotificationException) { this._snackBar.open( "Notification exception created successfully", "", @@ -85,20 +154,20 @@ export class ObservationDetailsComponent { duration: ObservationDetailsComponent.SNACKBAR_LINGER_DURATION_MS, } - ) + ); } else { this._snackBar.open("Creating notification exception failed", "", { duration: ObservationDetailsComponent.SNACKBAR_LINGER_DURATION_MS, - }) + }); } - }) + }); } else { - this._router.navigate(["modron", "exceptions", expName]) + await this._router.navigate(["modron", "exceptions", expName]); } } exceptionNameFromObservation(ob: Observation): string { - const resource = ob.getResource() - return `${resource?.getResourceGroupName().replace(new RegExp("/"), "_")}-${resource?.getName()}-${ob.getName()}` + const resource = ob.getResourceRef() + return `${resource?.getGroupName().replace(new RegExp("/"), "_")}-${resource?.getExternalId()}-${ob.getName()}` } } diff --git a/src/ui/client/src/app/observations-stats/observations-stats.component.html b/src/ui/client/src/app/observations-stats/observations-stats.component.html new file mode 100644 index 0000000..ea112a5 --- /dev/null +++ b/src/ui/client/src/app/observations-stats/observations-stats.component.html @@ -0,0 +1,5 @@ + diff --git a/src/ui/client/src/app/observations-stats/observations-stats.component.scss b/src/ui/client/src/app/observations-stats/observations-stats.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/ui/client/src/app/observations-stats/observations-stats.component.spec.ts b/src/ui/client/src/app/observations-stats/observations-stats.component.spec.ts new file mode 100644 index 0000000..3245491 --- /dev/null +++ b/src/ui/client/src/app/observations-stats/observations-stats.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { ObservationsStatsComponent } from "./observations-stats.component"; + +describe("HistogramHorizontalComponent", () => { + let component: ObservationsStatsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ObservationsStatsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ObservationsStatsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/ui/client/src/app/observations-stats/observations-stats.component.ts b/src/ui/client/src/app/observations-stats/observations-stats.component.ts new file mode 100644 index 0000000..d3efb42 --- /dev/null +++ b/src/ui/client/src/app/observations-stats/observations-stats.component.ts @@ -0,0 +1,44 @@ +import { + Component, + OnInit, + Input, + ChangeDetectionStrategy, +} from "@angular/core" +import {ChartData, ChartOptions} from "chart.js"; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: "app-observations-stats", + templateUrl: "./observations-stats.component.html", + styleUrls: ["./observations-stats.component.scss"], +}) +export class ObservationsStatsComponent implements OnInit { + @Input() data: Map = new Map(); + public options: ChartOptions = { + scales: { + + }, + indexAxis: "y", + plugins: { + legend: { + display: false, + }, + } + } + public chartData: ChartData = { + labels: [] as string[], + datasets: [ + { + label: "Observations", + data: [] as number[], + } + ] + }; + max = 1; + + ngOnInit(): void { + this.max = Math.max(...this.data.values()) + this.chartData.labels = Array.from(this.data.keys()); + this.chartData.datasets[0].data = Array.from(this.data.values()); + } +} diff --git a/src/ui/client/src/app/observations-table/observations-table.component.html b/src/ui/client/src/app/observations-table/observations-table.component.html new file mode 100644 index 0000000..85e7045 --- /dev/null +++ b/src/ui/client/src/app/observations-table/observations-table.component.html @@ -0,0 +1,104 @@ + + + Risk + + + + + + + Category + +
{{ r(row).getName() }}
+
+
+ + + Resource Group + + + {{ (r(row).getResourceRef()?.getGroupName() || '') }} + + + + + + Resource + {{ r(row).getResourceRef()?.getExternalId() | parseExternalId }} + + + + + Description + +

+
+
+ + + Observed + +

{{ r(row).getObservedValue() | structValueToString }}

+
+
+ + + Expected + + {{ r(row).getExpectedValue() | structValueToString }} + + + + + Actions + + + open_in_full + + + + + + + + +
diff --git a/src/ui/client/src/app/observations-table/observations-table.component.scss b/src/ui/client/src/app/observations-table/observations-table.component.scss new file mode 100644 index 0000000..6d545f8 --- /dev/null +++ b/src/ui/client/src/app/observations-table/observations-table.component.scss @@ -0,0 +1,49 @@ +.mat-column-riskScore { + max-width: 80px; +} + +.mat-column-category { + max-width: 350px; +} + +.mat-column-shortDesc { + min-width: 30%; +} + +.mat-column-shortDesc, .mat-column-category { + max-height: 1em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; + + div { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.mat-cell { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + > p { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + } +} + +.mat-column-actions { + display: grid; + grid-template-columns: repeat(2, 1fr); + justify-items: center; + max-width: 100px; + + > * { + cursor: pointer; + } +} diff --git a/src/ui/client/src/app/observations-table/observations-table.component.ts b/src/ui/client/src/app/observations-table/observations-table.component.ts new file mode 100644 index 0000000..d0c2f43 --- /dev/null +++ b/src/ui/client/src/app/observations-table/observations-table.component.ts @@ -0,0 +1,97 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, Component, Input, OnInit, + ViewChild +} from "@angular/core" +import { ModronService } from "../modron.service" +import { ModronStore } from "../state/modron.store" + +import * as pb from "src/proto/modron_pb" +import {Observation} from "src/proto/modron_pb"; +import {MatSort, Sort} from "@angular/material/sort"; +import {MatTableDataSource} from "@angular/material/table"; +import {MatDialog} from "@angular/material/dialog"; +import {ObservationDetailsDialogComponent} from "../observation-details-dialog/observation-details-dialog.component"; + +type ObsMap = Map +type RgObsMap = Map + +@Component({ + selector: "app-observations-table", + templateUrl: "./observations-table.component.html", + styleUrls: ["./observations-table.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ObservationsTableComponent implements OnInit,AfterViewInit { + public dataSource: MatTableDataSource = new MatTableDataSource(); + public sortedData: MatTableDataSource = new MatTableDataSource(); + + @Input() + public obs: pb.Observation[] = []; + constructor( + public store: ModronStore, + public modron: ModronService, + private dialog: MatDialog, + ){ + + } + + @Input() + public columns = ["riskScore", "category", "shortDesc", "resource", "actions"]; + public resourceGroupName = ""; + + + async ngOnInit(): Promise { + this.dataSource.data = this.obs + this.sortData({active: "riskScore", direction: "desc"}) + } + + @ViewChild(MatSort) sort: MatSort|undefined + + sortData(sort: Sort) { + if (!sort.active || sort.direction === "") { + this.sortedData.data = this.dataSource.data; + return; + } + + this.sortedData.data = this.dataSource.data.slice().sort((a, b) => { + let sortResult = 0; + switch(sort.active) { + case "riskScore": + sortResult = a.getRiskScore() - b.getRiskScore(); + break; + case "category": + sortResult = a.getName().localeCompare(b.getName()); + break; + } + return sort.direction === "asc" ? sortResult : -sortResult; + }); + } + + ngAfterViewInit(): void { + this.dataSource.sort = this.sort as MatSort + } + + getName(obs: RgObsMap): ObsMap | undefined { + return obs.get(this.resourceGroupName) + } + identity(index: number, item: pb.Observation): string { + return item.getUid() + } + + protected readonly JSON = JSON; + protected readonly Object = Object; + protected readonly Observation = Observation; + + r(row: unknown): pb.Observation { + return row as pb.Observation + } + + showObservationDetails(row: Observation) { + this.dialog.open(ObservationDetailsDialogComponent, { + data: row, + width: "50%", + hasBackdrop: true, + }) + } +} diff --git a/src/ui/client/src/app/resource-group-details/resource-group-details.component.html b/src/ui/client/src/app/resource-group-details/resource-group-details.component.html index 65b4eb2..c1fb2d0 100644 --- a/src/ui/client/src/app/resource-group-details/resource-group-details.component.html +++ b/src/ui/client/src/app/resource-group-details/resource-group-details.component.html @@ -1,46 +1,26 @@
-

+

{{ - this.resourceGroupName.replace('projects/', '') }} + href="{{this.PROJECT_URL + this.resourceGroupName.replace('projects/', '') }}">{{ + this.resourceGroupName.replace('projects/', '') + }} {{ - this.resourceGroupName }} + href="{{ this.FOLDER_URL + this.resourceGroupName.replace('folders/', '') }}">{{ + this.resourceGroupName + }} {{ - this.resourceGroupName }} - | -

-

observation details

+ href="{{ this.ORGANIZATION_URL + this.resourceGroupName.replace('organizations/', '') }}">{{ + this.resourceGroupName + }} +

+

Observation details

- - +
+
+ +
+ +
- diff --git a/src/ui/client/src/app/resource-group-details/resource-group-details.component.scss b/src/ui/client/src/app/resource-group-details/resource-group-details.component.scss index ae7877b..c7d8dba 100644 --- a/src/ui/client/src/app/resource-group-details/resource-group-details.component.scss +++ b/src/ui/client/src/app/resource-group-details/resource-group-details.component.scss @@ -40,16 +40,10 @@ } .app-resourcegroup-header { - display: flex; flex-direction: row; align-items: center; - gap: 10px; flex: 1; - background-color: rgb(239, 239, 239); - - h1 { - margin: 5px 40px; - } + margin-top: 24px; } } @@ -114,4 +108,76 @@ svg:hover { fill: rgb(64, 64, 64); } + + .mat-column-riskScore { + max-width: 80px; + } + + .mat-column-category { + max-width: 350px; + } + + .mat-column-shortDesc { + min-width: 30%; + } + + .mat-column-shortDesc, .mat-column-category { + max-height: 1em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; + + div { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .mat-column-actions { + display: grid; + grid-template-columns: repeat(2, 1fr); + justify-items: center; + max-width: 100px; + + > * { + cursor: pointer; + } + } +} + +.observation-card { + $margin: 20px; + margin: $margin 0 $margin 0; + padding: 10px; +} + +.observation-card-header { + margin-bottom: 1em; +} + +.observation-card-title { + display: flex; + flex-basis: content; + align-items: center; + + a.more-info { + display: block; + height: 24px; + margin-left: 8px; + } +} + +.observation-details { + display: flex; + flex-direction: column; + gap: 1em; +} + +.observation-subtitle { + &.has-observations { + color: #da1e28; + } } diff --git a/src/ui/client/src/app/resource-group-details/resource-group-details.component.ts b/src/ui/client/src/app/resource-group-details/resource-group-details.component.ts index 509a154..030fadd 100644 --- a/src/ui/client/src/app/resource-group-details/resource-group-details.component.ts +++ b/src/ui/client/src/app/resource-group-details/resource-group-details.component.ts @@ -1,11 +1,16 @@ -import { KeyValue, ViewportScroller } from "@angular/common" -import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core" +import { + ChangeDetectionStrategy, ChangeDetectorRef, + Component, OnDestroy, + OnInit, +} from "@angular/core" import { ActivatedRoute } from "@angular/router" import { ModronService } from "../modron.service" import { ModronStore } from "../state/modron.store" - import * as pb from "src/proto/modron_pb" -import { first } from "rxjs" +import {Subscription, tap} from "rxjs"; + +type ObsMap = Map +type RgObsMap = Map @Component({ selector: "app-resource-group-details", @@ -13,74 +18,62 @@ import { first } from "rxjs" styleUrls: ["./resource-group-details.component.scss"], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ResourceGroupDetailsComponent implements OnInit { +export class ResourceGroupDetailsComponent implements OnInit,OnDestroy { + public loading = true; + private subscription: Subscription | undefined; + public obs: pb.Observation[] = []; constructor( private route: ActivatedRoute, public store: ModronStore, public modron: ModronService, - private viewportScroller: ViewportScroller, - ) { } + private cdr: ChangeDetectorRef, + ){ + } public resourceGroupName = ""; private readonly BASE_GCP_URL = "https://console.cloud.google.com" readonly FOLDER_URL = `${this.BASE_GCP_URL}/welcome?folder=` readonly ORGANIZATION_URL = `${this.BASE_GCP_URL}/welcome?organizationId=` readonly PROJECT_URL = `${this.BASE_GCP_URL}/home/dashboard?project=` - public displayObsDetail: Map = new Map(); - ngOnInit(): void { + async ngOnInit(): Promise { this.resourceGroupName = (this.route.snapshot.paramMap.get("id") as string).replace(new RegExp("-"), "/") - } - - // Wait for https://github.com/angular/angular/issues/30139 to be fixed. - // The bug prevents us from scrolling to a fragment that is dynamically loaded. - ngAfterViewInit(): void { - this.store.observations$.subscribe(() => - this.route.fragment.pipe(first()).subscribe(fragment => { - console.log(fragment) - this.viewportScroller.scrollToAnchor(fragment!) + this.subscription = this.store.observations$.pipe( + tap((obs) => { + if(obs.size > 0) { + this.loading = false + this.cdr.markForCheck() + } + this.obs = this.getObservations(obs) }) - ) + ).subscribe() } - filterName( - obs: Map - ): Map { - const m = new Map() - m.set(this.resourceGroupName, obs.get(this.resourceGroupName as string)) - return m + ngOnDestroy(): void { + this.subscription?.unsubscribe() } - getName( - obs: Map> - ): Map { - return obs.get(this.resourceGroupName) as Map + + getName(obs: RgObsMap): ObsMap | undefined { + return obs.get(this.resourceGroupName) } - mapByType(obs: Map): Map { - const obsByType = new Map() - for (const ob of [...obs.values()].flat()) { - if (ob === undefined) { - continue - } - const type = ob.getName() - if (!obsByType.has(type)) { - obsByType.set(type, []) - } - obsByType.get(type)?.push(ob) + getObservations(obs?: RgObsMap): pb.Observation[] { + if(obs === undefined) { + return [] + } + const obsMap = this.getName(obs) + if(obsMap === undefined) { + return [] } - return obsByType - } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + return Array.from(obsMap).map(([_, v]) => v).flat() + } identity(index: number, item: pb.Observation): string { return item.getUid() } - - identityKV(index: number, item: KeyValue): string { - return item.key - } - getObservedValue(ob: pb.Observation): string | undefined { return ob.getObservedValue()?.toString()?.replace(/,/g, "") } @@ -89,15 +82,11 @@ export class ResourceGroupDetailsComponent implements OnInit { return ob.getExpectedValue()?.toString()?.replace(/,/g, "") } - toggle(id: string | undefined): void { - id = id as string - if (this.displayObsDetail.has(id)) { - this.displayObsDetail.set( - id, - !(this.displayObsDetail.get(id) as boolean) - ) - } else { - this.displayObsDetail.set(id, true) - } + r(row: unknown): pb.Observation { + return row as pb.Observation + } + + resourceRef(row: pb.Observation) { + return row.getResourceRef()?.getExternalId() } } diff --git a/src/ui/client/src/app/resource-group-details/resource-group-details.pipe.ts b/src/ui/client/src/app/resource-group-details/resource-group-details.pipe.ts index dacc5af..6596d97 100644 --- a/src/ui/client/src/app/resource-group-details/resource-group-details.pipe.ts +++ b/src/ui/client/src/app/resource-group-details/resource-group-details.pipe.ts @@ -1,6 +1,6 @@ import { Pipe, PipeTransform } from "@angular/core" -import { Value } from "google-protobuf/google/protobuf/struct_pb" import * as pb from "src/proto/modron_pb" +import {StructValueToStringPipe} from "../filter.pipe"; @Pipe({ name: "mapByType" }) export class MapByTypePipe implements PipeTransform { @@ -36,7 +36,7 @@ export class MapByObservedValuesPipe implements PipeTransform { const obsByType = new Map() obs.forEach((o) => { const obsValue = o.getObservedValue() - ? (o.getObservedValue() as Value).toString() + ? StructValueToStringPipe.prototype.transform(o.getObservedValue()) : "Observation count" if (!obsByType.has(obsValue)) { obsByType.set(obsValue, 0) diff --git a/src/ui/client/src/app/resource-group/resource-group.component.html b/src/ui/client/src/app/resource-group/resource-group.component.html index 43de7f9..2e79592 100644 --- a/src/ui/client/src/app/resource-group/resource-group.component.html +++ b/src/ui/client/src/app/resource-group/resource-group.component.html @@ -1,40 +1,58 @@ -
-
-

{{ this.name.replace('projects/', '') }}

- - - -
- -
-
-
- - - -

Scan

+ + + {{ this.provider }} + {{ this.name.replace('projects/', '') }} + + +
+
+
+
+ +
+
+ + +
+
0 observations
+
+
+
+
+ Last scanned: {{ this.lastScanDate! | fromNow }}
- -
- Scanning - + + +
+ Never scanned
-
+
+ + + -
-

{{ this.observationCount }} observations

-
-

{{ this.lastScanDate.slice(12) }}

-

{{ this.lastScanDate.slice(0, 12) }}

-
-
-
+ + + + + diff --git a/src/ui/client/src/app/resource-group/resource-group.component.scss b/src/ui/client/src/app/resource-group/resource-group.component.scss index 4880df7..d64ddae 100644 --- a/src/ui/client/src/app/resource-group/resource-group.component.scss +++ b/src/ui/client/src/app/resource-group/resource-group.component.scss @@ -1,3 +1,86 @@ +@use '../../colors.scss' as colors; + +.resource-group-card { + cursor: pointer; + overflow: hidden; + + .resource-group-card-header { + display: block; + } + + .resource-group-title { + height: 36px; + width: 100%; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + + .content { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + + .observations { + .findings-count, .no-findings { + height: 30px; + padding: 10px; + color: #FFF; + display: flex; + flex-direction: row; + align-items: center; + justify-items: center; + justify-content: center; + } + + .findings-by-severity { + display: flex; + row-gap: 4px; + column-gap: 4px; + justify-content: left; + height: 50px; + align-items: center; + } + + + .no-findings { + background-color: colors.$allGood; + } + + + .findings-count { + background-color: colors.$danger; + + .icon { + margin-right: 4px; + } + + .count { + margin-right: 4px; + } + + .desc { + + } + + } + } + + .last-scan { + color: colors.$secondaryText; + height: 20px; + font-size: 0.8em; + margin-top: 1em; + } + } + + .footer { + height: 4px; + } +} + + .resourceGroup-ctn { width: 300px; height: 220px; diff --git a/src/ui/client/src/app/resource-group/resource-group.component.ts b/src/ui/client/src/app/resource-group/resource-group.component.ts index c645366..a8bd977 100644 --- a/src/ui/client/src/app/resource-group/resource-group.component.ts +++ b/src/ui/client/src/app/resource-group/resource-group.component.ts @@ -3,6 +3,8 @@ import { map, Observable } from "rxjs" import { ModronStore } from "../state/modron.store" import { MatSnackBar } from "@angular/material/snack-bar" import * as pb from "src/proto/modron_pb" +import * as moment from "moment"; +import {Severity} from "src/proto/modron_pb"; @Component({ selector: "app-resource-group", @@ -16,14 +18,17 @@ export class ResourceGroupComponent { name = ""; @Input() - lastScanDate = ""; + lastScanDate: Date | null = null; @Input() - provider = ""; + provider = "GCP"; // TODO: Change when we support other providers @Input() observationCount = -1; + @Input() + observationBySeverity: [number, number][] = []; + constructor(public store: ModronStore, public snackBar: MatSnackBar) { } collectAndScan(resourceGroups: string[]): void { @@ -60,4 +65,10 @@ export class ResourceGroupComponent { }) ) } + + fromNow(date: string): string { + return moment(date).fromNow() + } + + protected readonly Severity = Severity; } diff --git a/src/ui/client/src/app/resource-group/resource-group.pipe.ts b/src/ui/client/src/app/resource-group/resource-group.pipe.ts new file mode 100644 index 0000000..9257f52 --- /dev/null +++ b/src/ui/client/src/app/resource-group/resource-group.pipe.ts @@ -0,0 +1,9 @@ +import {Pipe, PipeTransform} from "@angular/core"; +import * as moment from "moment"; + +@Pipe({name: "fromNow"}) +export class FromNowPipe implements PipeTransform { + transform(value: Date): string { + return moment(value).fromNow() + } +} diff --git a/src/ui/client/src/app/resource-groups/resource-groups.component.html b/src/ui/client/src/app/resource-groups/resource-groups.component.html index 96c8c56..e8973be 100644 --- a/src/ui/client/src/app/resource-groups/resource-groups.component.html +++ b/src/ui/client/src/app/resource-groups/resource-groups.component.html @@ -1,30 +1,28 @@
-

Resource groups |

-
-

Filter

- - -
-

- | - {{ - ( - obs - | mapFlatRules - | keyvalue - | filterKeyValue: searchText - | filterNoObservations: removeNoObs - ).length - }} - matching groups -

-
-
+

Resource Groups

+
+
+ + Filter + + + Only with observations
+ +

+ {{ + ( + obs + | mapFlatRules + | keyvalue + | filterKeyValue: searchText + | filterNoObservations: removeNoObs + ).length + }} + matching groups +

-
-
-
-
-

- {{ obsKvs | invalidProjectNb }} -

-

groups with dangerous observations

-
-

{{ obsKvs | obsNb }} total observations to solve

+ +
+
+
+ error +
{{ obsKvs | invalidProjectNb }}
+
groups with important observations
-
-
-
- - - -

Scan all

-
-
- -
- - - -

Scanning ...

-
-
+ +
+ warning +
{{ obsKvs | obsNb }}
+
total observations to solve
+
+
+ +
+ + + +
+
+
- + + +
diff --git a/src/ui/client/src/app/resource-groups/resource-groups.component.scss b/src/ui/client/src/app/resource-groups/resource-groups.component.scss index f1aa425..28942f7 100644 --- a/src/ui/client/src/app/resource-groups/resource-groups.component.scss +++ b/src/ui/client/src/app/resource-groups/resource-groups.component.scss @@ -1,9 +1,4 @@ -.inline { - display: flex; - flex-direction: row; - gap: 10px; - align-items: baseline; -} +@use '../../colors.scss' as colors; .inline-between { display: flex; @@ -17,9 +12,9 @@ width: 300px; height: 220px; background: linear-gradient( - to right, - rgb(211, 211, 211) 50%, - rgb(236, 236, 236) 50% + to right, + rgb(211, 211, 211) 50%, + rgb(236, 236, 236) 50% ); background-size: 200% 200%; animation: gradient 6s ease infinite; @@ -40,35 +35,86 @@ } .app-resourcegroup { - width: 98%; - // height: 100%; - + display: block; + height: 100%; + width: 100%; .app-resourcegroup-header { display: flex; flex-direction: row; align-items: center; gap: 10px; flex: 1; - background-color: rgb(239, 239, 239); + } + + h3 { + margin: 8px 0; + } + + h4 { + margin: 4px 0; + } + + .app-resourcegroup-filter { + display: flex; + flex-direction: row; + align-items: baseline; + gap: 10px; + } + + .matching-groups-count { + color: colors.$secondaryText; + margin-top: 2px; + } - .app-resourcegroup-header-filter { + .observations-top-bar { + display: grid; + width: 100%; + grid-template-columns: 1fr 1fr; + + .buttons { display: flex; - flex-direction: row; - gap: 10px; + justify-self: end; + } + } - input { - padding: 5px; - margin: 5px; - font-size: 20px; + .observations-result { + display: flex; + flex-direction: column; + row-gap: 4px; + margin: 12px 0px 12px 0px; + + .observation-type { + display: grid; + grid-template-columns: 40px 6em 300px; + justify-items: center; + align-items: center; + + &.warn { + .observation-icon { + color: colors.$warning; + } } - input:focus { - outline: none; + &.danger { + .observation-icon { + color: colors.$danger; + } } - } - h1 { - margin: 5px 40px; + .observation-icon { + align-self: center; + } + + .observation-count { + font-weight: bold; + font-size: 1.5em; + justify-self: right; + margin-right: 8px; + } + + .observation-description { + justify-self: start; + } } } @@ -86,6 +132,9 @@ } .buttons { + display: flex; + flex-direction: row; + .button { background-color: rgb(238, 238, 238); margin-left: 3px; @@ -108,6 +157,9 @@ margin-top: 10px; gap: 20px; display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + grid-template-columns: repeat(auto-fit, 300px); + overflow: auto; + padding: 10px; + max-height: calc(100vh - 356px); } } diff --git a/src/ui/client/src/app/resource-groups/resource-groups.component.spec.ts b/src/ui/client/src/app/resource-groups/resource-groups.component.spec.ts index c52f00d..1fb4223 100644 --- a/src/ui/client/src/app/resource-groups/resource-groups.component.spec.ts +++ b/src/ui/client/src/app/resource-groups/resource-groups.component.spec.ts @@ -4,7 +4,7 @@ import { FilterKeyValuePipe, FilterNoObservationsPipe } from "../filter.pipe"; import { mapFlatRulesPipe } from "../resource-group-details/resource-group-details.pipe"; import { ModronStore } from "../state/modron.store"; import { RouterTestingModule } from "@angular/router/testing"; -import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { provideHttpClientTesting } from "@angular/common/http/testing"; import { ResourceGroupsComponent } from "./resource-groups.component"; import { @@ -12,6 +12,7 @@ import { ObsNbPipe, ResourceGroupsPipe, } from "./resource-groups.pipe"; +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; describe("ResourceGroupsComponent", () => { let component: ResourceGroupsComponent; @@ -19,7 +20,7 @@ describe("ResourceGroupsComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ + declarations: [ ResourceGroupsComponent, ResourceGroupsPipe, FilterKeyValuePipe, @@ -27,14 +28,11 @@ describe("ResourceGroupsComponent", () => { InvalidProjectNb, ObsNbPipe, FilterNoObservationsPipe, - ], - imports: [ - MatSnackBarModule, - RouterTestingModule, - HttpClientTestingModule, - ], - providers: [ModronStore], - }).compileComponents(); + ], + imports: [MatSnackBarModule, + RouterTestingModule], + providers: [ModronStore, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] +}).compileComponents(); fixture = TestBed.createComponent(ResourceGroupsComponent); component = fixture.componentInstance; diff --git a/src/ui/client/src/app/resource-groups/resource-groups.component.ts b/src/ui/client/src/app/resource-groups/resource-groups.component.ts index c3c5196..747f272 100644 --- a/src/ui/client/src/app/resource-groups/resource-groups.component.ts +++ b/src/ui/client/src/app/resource-groups/resource-groups.component.ts @@ -36,8 +36,8 @@ export class ResourceGroupsComponent implements OnInit { return balance > 0 ? "#da1e28" : "#24a148" } - collectAndScan(resourceGroups: string[]): void { - this.store.collectAndScan$(resourceGroups).subscribe({ + collectAndScanAll(): void { + this.store.collectAndScanAll$().subscribe({ next: () => this.snackBar.open("Scanning all resource groups ...", "", { duration: ResourceGroupsComponent.SNACKBAR_LINGER_DURATION_MS, @@ -67,12 +67,11 @@ export class ResourceGroupsComponent implements OnInit { ) } - getDate(obs: any[]): string { - obs as Observation[] + getDate(obs: Observation[]): Date | null { if (obs.length > 0) { - return obs[0].getTimestamp()?.toDate().toUTCString().slice(4) + return obs[0].getTimestamp()?.toDate() || null; } - return "" + return null } public updateFilterUrlParam() { diff --git a/src/ui/client/src/app/resource-groups/resource-groups.pipe.ts b/src/ui/client/src/app/resource-groups/resource-groups.pipe.ts index e8a94fd..ff36ab7 100644 --- a/src/ui/client/src/app/resource-groups/resource-groups.pipe.ts +++ b/src/ui/client/src/app/resource-groups/resource-groups.pipe.ts @@ -24,6 +24,23 @@ export class MapPerTypeName implements PipeTransform { } } + +@Pipe({ name: "mapByRiskScore" }) +export class MapByRiskScorePipe implements PipeTransform { + transform(obs: pb.Observation[]): [number, number][] { + const sevMap = new Map(); + for(const o of obs) { + const amountSeverities = sevMap.get(o.getRiskScore()); + if(amountSeverities === undefined){ + sevMap.set(o.getRiskScore(), 1); + continue; + } + sevMap.set(o.getRiskScore(), amountSeverities+1); + } + return Array.from(sevMap.entries()).sort((a, b) => b[0] - a[0]) + } +} + @Pipe({ name: "observations" }) export class ObservationsPipe implements PipeTransform { transform(obs: Map): pb.Observation[] { diff --git a/src/ui/client/src/app/severity-indicator/severity-indicator.component.html b/src/ui/client/src/app/severity-indicator/severity-indicator.component.html new file mode 100644 index 0000000..3480f35 --- /dev/null +++ b/src/ui/client/src/app/severity-indicator/severity-indicator.component.html @@ -0,0 +1,22 @@ +
+ + {{ getIcon() }} + + + {{ count! | severityAmount }} + +
diff --git a/src/ui/client/src/app/severity-indicator/severity-indicator.component.scss b/src/ui/client/src/app/severity-indicator/severity-indicator.component.scss new file mode 100644 index 0000000..6f2feba --- /dev/null +++ b/src/ui/client/src/app/severity-indicator/severity-indicator.component.scss @@ -0,0 +1,47 @@ + +.severity-circle { + color: #FFF; + display: block; + width: 30px; + height: 30px; + line-height: 30px; + font-size: 16px; + overflow: hidden; + text-align: center; + border-radius: 30px; + margin-right: 6px; +} + +.severity-circle-unknown { + background-color: #9E9E9E; +} + +.severity-circle-info { + background-color: #2196F3; +} + +.severity-circle-low { + background-color: #AFB42B; +} + + +.severity-circle-medium { + background-color: #F9A825; +} + +.severity-circle-high { + background-color: #E65100; +} + +.severity-circle-critical { + background-color: #D50000; + + // Make this blink too! + animation: blinker 1s linear infinite; +} + +@keyframes blinker { + 50% { + background-color: #ff0000; + } +} diff --git a/src/ui/client/src/app/severity-indicator/severity-indicator.component.ts b/src/ui/client/src/app/severity-indicator/severity-indicator.component.ts new file mode 100644 index 0000000..62b9a4e --- /dev/null +++ b/src/ui/client/src/app/severity-indicator/severity-indicator.component.ts @@ -0,0 +1,36 @@ +import {Component, Input} from "@angular/core"; +import {Severity} from "../../proto/modron_pb"; + +@Component({ + selector: "app-severity-indicator", + templateUrl: "./severity-indicator.component.html", + styleUrls: ["./severity-indicator.component.scss"], +}) +export class SeverityIndicatorComponent { + @Input() + severity: Severity = Severity.SEVERITY_UNKNOWN; + + @Input() + count: number | undefined; + + getIcon(): string { + switch (this.severity) { + case Severity.SEVERITY_CRITICAL: + return "C"; + case Severity.SEVERITY_HIGH: + return "H"; + case Severity.SEVERITY_MEDIUM: + return "M"; + case Severity.SEVERITY_LOW: + return "L"; + case Severity.SEVERITY_INFO: + return "I"; + default: + return "?"; + } + } + + constructor() {} + + protected readonly Severity = Severity; +} diff --git a/src/ui/client/src/app/severity-indicator/severity-indicator.pipe.ts b/src/ui/client/src/app/severity-indicator/severity-indicator.pipe.ts new file mode 100644 index 0000000..6f41d7e --- /dev/null +++ b/src/ui/client/src/app/severity-indicator/severity-indicator.pipe.ts @@ -0,0 +1,48 @@ +import {Pipe, PipeTransform} from "@angular/core"; +import {Impact, Severity} from "../../proto/modron_pb"; + +@Pipe({name: "severityName"}) +export class SeverityNamePipe implements PipeTransform { + transform(severity: number): string { + switch(severity) { + case Severity.SEVERITY_CRITICAL: + return "Critical"; + case Severity.SEVERITY_HIGH: + return "High"; + case Severity.SEVERITY_MEDIUM: + return "Medium"; + case Severity.SEVERITY_LOW: + return "Low"; + case Severity.SEVERITY_INFO: + return "Info"; + default: + return "Unknown"; + } + } +} + +@Pipe({name: "impactName"}) +export class ImpactNamePipe implements PipeTransform { + transform(impact: number): string { + switch(impact) { + case Impact.IMPACT_HIGH: + return "High"; + case Impact.IMPACT_MEDIUM: + return "Medium"; + case Impact.IMPACT_LOW: + return "Low"; + default: + return "Unknown"; + } + } +} + +@Pipe({name: "severityAmount"}) +export class SeverityAmountPipe implements PipeTransform { + transform(count: number): string { + if(count > 99) { + return "99+"; + } + return count.toString(); + } +} diff --git a/src/ui/client/src/app/sidenav/sidenav.component.html b/src/ui/client/src/app/sidenav/sidenav.component.html new file mode 100644 index 0000000..ec8a9bf --- /dev/null +++ b/src/ui/client/src/app/sidenav/sidenav.component.html @@ -0,0 +1,29 @@ +
+
+ +
+ + + +
diff --git a/src/ui/client/src/app/sidenav/sidenav.component.scss b/src/ui/client/src/app/sidenav/sidenav.component.scss new file mode 100644 index 0000000..25414e1 --- /dev/null +++ b/src/ui/client/src/app/sidenav/sidenav.component.scss @@ -0,0 +1,64 @@ +@use '../../colors.scss' as colors; + +$navbarWidth: 68px; + +.sidenav { + display: flex; + background-color: colors.$sideNavBackground; + flex-direction: column; + width: $navbarWidth; + height: 100%; + + div.top-spacer { + height: 20px; + } + + .menu-icon-container { + align-self: center; + height: 24px; + margin-top: 12px; + margin-bottom: 12px; + } + + div.nav-items { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + gap: 24px; + margin-top: 20px; + + .nav-item { + cursor: pointer; + text-align: center; + width: $navbarWidth; + overflow: hidden; + + &.active { + .nav-item-icon { + background-color: colors.$sideNavButtonActiveBackground; + } + } + + .nav-item-icon { + font-size: 20px; + line-height: 20px; + height: 20px; + width: 30px; + padding: 4px; + border-radius: 40px; + transition: 250ms; + &:hover { + background-color: colors.$sideNavButtonActiveBackground; + } + } + + .nav-item-text { + width: $navbarWidth; + font-weight: bold; + font-size: 12px; + text-align: center; + } + } + } +} diff --git a/src/ui/client/src/app/sidenav/sidenav.component.spec.ts b/src/ui/client/src/app/sidenav/sidenav.component.spec.ts new file mode 100644 index 0000000..40bcb37 --- /dev/null +++ b/src/ui/client/src/app/sidenav/sidenav.component.spec.ts @@ -0,0 +1,47 @@ +import {ComponentFixture, TestBed} from "@angular/core/testing"; +import {SidenavComponent} from "./sidenav.component"; +import {Router} from "@angular/router"; +import {RouterTestingModule} from "@angular/router/testing"; +import {Component} from "@angular/core"; + +@Component({ + template: "" +}) +class DummyComponent { +} + +describe("SidenavComponent", () => { + let component: SidenavComponent; + let fixture: ComponentFixture; + let router: Router; + + beforeEach(async () => { + const testingModule = TestBed.configureTestingModule({ + imports: [SidenavComponent, RouterTestingModule.withRoutes( + [{path: "modron/resourcegroups", component: DummyComponent}] + )], + providers: [] + }) + await testingModule.compileComponents(); + + fixture = TestBed.createComponent(SidenavComponent); + component = fixture.componentInstance; + router = TestBed.inject(Router); + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should mark the icon as active when the route matches", async () => { + await router.navigateByUrl("/modron/resourcegroups"); + fixture.detectChanges(); + const {debugElement} = fixture; + const navItems = debugElement.nativeElement.querySelectorAll("div.nav-items div.nav-item") + expect(navItems.length).toBe(3); + expect(navItems[0].classList).toContain("active"); + expect(navItems[1].classList).not.toContain("active"); + expect(navItems[2].classList).not.toContain("active"); + }); +}); diff --git a/src/ui/client/src/app/sidenav/sidenav.component.ts b/src/ui/client/src/app/sidenav/sidenav.component.ts new file mode 100644 index 0000000..94b6707 --- /dev/null +++ b/src/ui/client/src/app/sidenav/sidenav.component.ts @@ -0,0 +1,23 @@ +import { Component } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import {MatIconModule} from "@angular/material/icon"; +import {MatListModule} from "@angular/material/list"; +import {Router, RouterLink} from "@angular/router"; +import {MatRippleModule} from "@angular/material/core"; + +@Component({ + selector: "app-sidenav", + standalone: true, + imports: [CommonModule, MatIconModule, MatListModule, RouterLink, MatRippleModule], + templateUrl: "./sidenav.component.html", + styleUrl: "./sidenav.component.scss" +}) +export class SidenavComponent { + constructor(public router: Router) {} + + public navItems = [ + { link: "/modron/resourcegroups", name: "Resource Groups", icon: "folder" }, + { link: "/modron/stats", name: "Stats", icon: "bar_chart" }, + { link: "/modron/exceptions", name: "Exceptions", icon: "notifications_paused" }, + ]; +} diff --git a/src/ui/client/src/app/state/modron.store.ts b/src/ui/client/src/app/state/modron.store.ts index 9d7cecc..0d31041 100644 --- a/src/ui/client/src/app/state/modron.store.ts +++ b/src/ui/client/src/app/state/modron.store.ts @@ -1,9 +1,10 @@ -import { Injectable } from "@angular/core" -import { BehaviorSubject, map, Observable } from "rxjs" -import { ModronService } from "../modron.service" -import { StatusInfo } from "../model/modron.model" +import {Injectable} from "@angular/core" +import {BehaviorSubject, map, Observable} from "rxjs" +import {ModronService} from "../modron.service" +import {StatusInfo} from "../model/modron.model" -import * as pb from "src/proto/modron_pb" +import * as pb from "../../proto/modron_pb" +import {RequestStatus, ScanType} from "../../proto/modron_pb" @Injectable() export class ModronStore { @@ -54,12 +55,35 @@ export class ModronStore { // A shallow copy here is enough const scanInfo = new Map(this.scanInfo) scanInfo.set(res.getCollectId() + ModronService.SEPARATOR + res.getScanId(), { - state: 2, + state: RequestStatus.RUNNING, resourceGroups: resourceGroups, + scanType: ScanType.SCAN_TYPE_PARTIAL }) this._runningScans.set(res.getCollectId() + ModronService.SEPARATOR + res.getScanId(), { - state: 2, + state: RequestStatus.RUNNING, resourceGroups: resourceGroups, + scanType: ScanType.SCAN_TYPE_PARTIAL + }) + this._scanIdsStatus.next(scanInfo) + return res + }) + ) + } + + collectAndScanAll$(): Observable { + this.checkScansStatus() + return this._service.collectAndScanAll().pipe( + map((res) => { + const scanInfo = new Map(this.scanInfo) + scanInfo.set(res.getCollectId() + ModronService.SEPARATOR + res.getScanId(), { + state: RequestStatus.RUNNING, + resourceGroups: [], + scanType: ScanType.SCAN_TYPE_FULL + }) + this._runningScans.set(res.getCollectId() + ModronService.SEPARATOR + res.getScanId(), { + state: RequestStatus.RUNNING, + resourceGroups: [], + scanType: ScanType.SCAN_TYPE_FULL }) this._scanIdsStatus.next(scanInfo) return res @@ -85,7 +109,7 @@ export class ModronStore { s = res.getScanStatus() } const scanInfo = new Map(this.scanInfo) - scanInfo.set(v, { state: s, resourceGroups: k.resourceGroups }) + scanInfo.set(v, { state: s, resourceGroups: k.resourceGroups, scanType: k.scanType }) if (s === pb.RequestStatus.DONE) { this._runningScans.delete(v) this.fetchObservations(k.resourceGroups).subscribe((obs) => { diff --git a/src/ui/client/src/app/stats/stats.component.html b/src/ui/client/src/app/stats/stats.component.html index 5e550ba..c84e415 100644 --- a/src/ui/client/src/app/stats/stats.component.html +++ b/src/ui/client/src/app/stats/stats.component.html @@ -1,118 +1,85 @@
-

Security Statistics |

-

general overview

+

Statistics

-
-
-
-

+
+ + + Compliant Projects + {{ - (obs | mapFlatRules).size - - (obs | mapFlatRules | keyvalue | invalidProjectNb) + (obs | mapFlatRules).size - + (obs | mapFlatRules | keyvalue | invalidProjectNb) }} -

-

compliant projects

-
- -
-

{{ obs | mapFlatRules | keyvalue | invalidProjectNb }}

-

projects with issues

-
- -
-

{{ (obs | mapFlatRules | observations).length }}

-

obser­vations

-
- -
-

{{ (obs | mapFlatRules | mapByType).size }}

-

rule types

-
-
- -
-
-
- - -
- -

- {{ obsType.key }}: {{ obsType.value.length }} total observations -

-
-
-
- - - - - - -
- -
- - - - - - - - -
- -
- - - - - -
+ + + + + + Projects with Issues + {{ obs | mapFlatRules | keyvalue | invalidProjectNb }} + + + + + Observations + {{ (obs | mapFlatRules | observations).length }} + + + + + Rule Types + {{ (obs | mapFlatRules | mapByType).size }} + + +
+
+ + + {{ obsType.key }} + {{ obsType.value.length }} observations + + +
+
+
+ + + + + List of the observations + + + + + + + +
- -
- -
- - -
-
+ +
-
-
diff --git a/src/ui/client/src/app/stats/stats.component.scss b/src/ui/client/src/app/stats/stats.component.scss index ca11492..73baccc 100644 --- a/src/ui/client/src/app/stats/stats.component.scss +++ b/src/ui/client/src/app/stats/stats.component.scss @@ -1,8 +1,3 @@ -.stats-ctn { - overflow: scroll; - max-height: 80vh; -} - .inline { display: flex; flex-direction: row; @@ -10,15 +5,12 @@ } .app-stats { - width: 98%; - .app-stats-header { display: flex; flex-direction: row; align-items: center; gap: 10px; flex: 1; - background-color: rgb(239, 239, 239); h1 { margin: 5px 40px; @@ -29,17 +21,18 @@ } } - h1 { - display: inline; - } + .main-stats { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + column-gap: 16px; - a { - text-decoration: none; - color: rgb(0, 0, 0); - } + .dashboard-card.positive-card { + background-color: rgb(159, 229, 180); + } - a:visited { - text-decoration: none; + .dashboard-card.negative-card { + background-color: rgb(255, 174, 174); + } } div a:hover::after { @@ -51,6 +44,29 @@ content: "\00A0\1F517"; } + .observations-list { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 16px; + + .observation-card { + .card-content { + margin-top: 12px; + } + } + + .observations-histogram { + max-height: 200px; + max-width: 600px; + padding: 10px; + } + + .export-csv-button { + margin-top: 16px; + } + } + .app-stats-main-stats { display: flex; flex-direction: row; diff --git a/src/ui/client/src/app/stats/stats.component.ts b/src/ui/client/src/app/stats/stats.component.ts index d9ba19d..a33cbc3 100644 --- a/src/ui/client/src/app/stats/stats.component.ts +++ b/src/ui/client/src/app/stats/stats.component.ts @@ -1,10 +1,11 @@ -import { KeyValue } from "@angular/common" -import { ChangeDetectionStrategy, Component } from "@angular/core" -import { ActivatedRoute } from "@angular/router" -import { ModronStore } from "../state/modron.store" -import { StatsService } from "../stats.service" +import {KeyValue} from "@angular/common" +import {ChangeDetectionStrategy, Component} from "@angular/core" +import {ActivatedRoute} from "@angular/router" +import {ModronStore} from "../state/modron.store" +import {StatsService} from "../stats.service" -import * as pb from "src/proto/modron_pb" +import * as pb from "../../proto/modron_pb" +import {StructValueToStringPipe} from "../filter.pipe"; @Component({ selector: "app-stats", @@ -13,21 +14,10 @@ import * as pb from "src/proto/modron_pb" changeDetection: ChangeDetectionStrategy.OnPush, }) export class StatsComponent { - constructor(public store: ModronStore, public stats: StatsService, private route: ActivatedRoute) { } - - displaySearchRules: Map = new Map(); - - toggleSearch(rule: string): void { - if (this.displaySearchRules.has(rule)) { - this.displaySearchRules.set( - rule, - !(this.displaySearchRules.get(rule) as boolean) - ) - } else { - this.displaySearchRules.set(rule, true) - } + constructor(public store: ModronStore, public stats: StatsService, private route: ActivatedRoute) { } + displaySearchRules: Map = new Map(); mapByType( obs: Map> ): Map { @@ -43,34 +33,31 @@ export class StatsComponent { return obsByType } - exportCsvMap(data: Map, filename: string) { - const csvData = Array.from( - data, - ([k, v]) => `${k.replace(/,/g, "")},${v}` - ).reduce((prev, curr) => `${prev}\n${curr}`) - this, this.exportCsv(csvData, filename) - } - exportCsvObs(obs: pb.Observation[], filename: string) { - const header = "resource-name,resource-group,observed-value,scan-date\n" - const data = obs.map( - (v) => - `${v.getResource()?.getName()},${v - .getResource() - ?.getResourceGroupName().replace("projects/", "")},${v.getObservedValue()},'${v - .getTimestamp() - ?.toDate() - .toUTCString()}'` - ) - this, + const rows: string[][] = [ + ["resource-name", "resource-group", "expected-value", "observed-value", "scan-date"] + ] + const obsRows: string[][] = obs.map( + (v) => { + return [ + v.getResourceRef()?.getExternalId(), + v.getResourceRef()?.getGroupName().replace("projects/", ""), + StructValueToStringPipe.prototype.transform(v.getExpectedValue()), + StructValueToStringPipe.prototype.transform(v.getObservedValue()), + v.getTimestamp()?.toDate().toUTCString() + ] as string[] + } + ) + rows.push(...obsRows) + obsRows.length = 0 this.exportCsv( - header + data.reduce((prev, curr) => `${prev}\n${curr}`), + rows.map((v)=>v.join(",")).join("\n"), filename ) } exportCsv(data: string, name: string): void { - const blob = new Blob([data], { type: "text/csv" }) + const blob = new Blob([data], {type: "text/csv;charset=utf-8"}) const url = window.URL.createObjectURL(blob) const filename = name + ".csv" diff --git a/src/ui/client/src/app/ui-demo/ui-demo.component.html b/src/ui/client/src/app/ui-demo/ui-demo.component.html new file mode 100644 index 0000000..769333f --- /dev/null +++ b/src/ui/client/src/app/ui-demo/ui-demo.component.html @@ -0,0 +1,48 @@ +
+

UI Demo

+

Severities

+

Empty

+
+ +
+ +

With number

+
+ +
+ +

Card

+
+ + +
+ +

Single Observation (old)

+ + +

Observation Dialog

+
+ +
+ +
diff --git a/src/ui/client/src/app/ui-demo/ui-demo.component.scss b/src/ui/client/src/app/ui-demo/ui-demo.component.scss new file mode 100644 index 0000000..451a2fc --- /dev/null +++ b/src/ui/client/src/app/ui-demo/ui-demo.component.scss @@ -0,0 +1,21 @@ +.severity-container { + display: grid; + grid-template-columns: repeat(8, 50px); + row-gap: 10px; +} + +.rg-container { + display: grid; + padding: 10px; + grid-template-columns: repeat(4, 300px); + column-gap: 10px; + row-gap: 10px; +} + +.observation-dialog { + width: 800px; + border: 1px solid #333; + padding: 10px; + border-radius: 5px; + margin: 16px; +} diff --git a/src/ui/client/src/app/ui-demo/ui-demo.component.ts b/src/ui/client/src/app/ui-demo/ui-demo.component.ts new file mode 100644 index 0000000..5b742cc --- /dev/null +++ b/src/ui/client/src/app/ui-demo/ui-demo.component.ts @@ -0,0 +1,32 @@ +import {ChangeDetectionStrategy, Component} from "@angular/core"; +import {Impact, Observation, Remediation, Severity} from "../../proto/modron_pb"; +import Category = Observation.Category; + +@Component({ + selector: "app-ui-demo", + templateUrl: "./ui-demo.component.html", + styleUrls: ["./ui-demo.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UIDemoComponent { + protected readonly Severity = Severity; + protected readonly Object = Object; + protected readonly severityValues: Severity[] = Object.values(Severity).reverse() as Severity[]; + protected readonly Date = Date; + date: Date | null = new Date(); + + public demoObservation = new Observation(); + constructor() { + this.demoObservation.setName("EXAMPLE_DEMO_OBSERVATION"); + this.demoObservation.setRiskScore(Severity.SEVERITY_CRITICAL); + this.demoObservation.setSeverity(Severity.SEVERITY_HIGH); + this.demoObservation.setImpact(Impact.IMPACT_HIGH); + this.demoObservation.setCategory(Category.CATEGORY_MISCONFIGURATION) + this.demoObservation.setImpactReason("environment=production") + const remediation = new Remediation(); + remediation.setDescription("Example description"); + remediation.setRecommendation("Example recommendation"); + this.demoObservation.setRemediation(remediation); + } + +} diff --git a/src/ui/client/src/assets/modron-white.svg b/src/ui/client/src/assets/modron-white.svg new file mode 100644 index 0000000..0ced35d --- /dev/null +++ b/src/ui/client/src/assets/modron-white.svg @@ -0,0 +1,42 @@ + + + + diff --git a/src/ui/client/src/assets/modron.svg b/src/ui/client/src/assets/modron.svg new file mode 100644 index 0000000..17410ca --- /dev/null +++ b/src/ui/client/src/assets/modron.svg @@ -0,0 +1,42 @@ + + + + diff --git a/src/ui/client/src/colors.scss b/src/ui/client/src/colors.scss new file mode 100644 index 0000000..f390a11 --- /dev/null +++ b/src/ui/client/src/colors.scss @@ -0,0 +1,23 @@ +@use '@angular/material' as mat; + +$my-primary: mat.m2-define-palette(mat.$m2-indigo-palette, 500); +$my-accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400); + +$my-theme: mat.m2-define-light-theme(( + color: ( + primary: $my-primary, + accent: $my-accent, + ), + density: 0, +)); + +$sideNavBackground: mat.m2-get-color-from-palette($my-primary, default, 0.1); +$sideNavButtonActiveBackground: mat.m2-get-color-from-palette($my-primary, default, 0.2); + +$title: #FFF; +$secondaryText: #555; + +$danger: #da1e28; +$warning: #f5a623; +$allGood: #24a148; + diff --git a/src/ui/client/src/main.ts b/src/ui/client/src/main.ts index 677f51d..c41b6f5 100644 --- a/src/ui/client/src/main.ts +++ b/src/ui/client/src/main.ts @@ -9,4 +9,4 @@ if (environment.production) { } platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.error(err)); \ No newline at end of file + .catch(err => console.error(err)); diff --git a/src/ui/client/src/styles.scss b/src/ui/client/src/styles.scss index 5026e82..35d9235 100644 --- a/src/ui/client/src/styles.scss +++ b/src/ui/client/src/styles.scss @@ -1,10 +1,14 @@ +@use '@angular/material' as mat; +@import '@material-symbols/font-400'; +@import 'colors.scss'; + /* You can add global styles to this file, and also import other style files */ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans&display=swap'); -@import '@angular/material/prebuilt-themes/deeppurple-amber.css'; + +@include mat.all-component-themes($my-theme); body { font-family: 'IBM Plex Sans', sans-serif; - overflow: hidden; margin: 0; height: 100% } @@ -24,3 +28,19 @@ input { border: none; background-color: rgb(249, 249, 249); } + +/* + Styles for dynamic elements +*/ +.mat-column-shortDesc > p{ + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +.markdown-content { + p { + margin-top: 0; + margin-bottom: 0; + } +} diff --git a/src/ui/client/tsconfig.app.json b/src/ui/client/tsconfig.app.json index 9b0cbd2..7c7374a 100644 --- a/src/ui/client/tsconfig.app.json +++ b/src/ui/client/tsconfig.app.json @@ -11,5 +11,6 @@ ], "include": [ "src/**/*.d.ts", + "src/proto/**/*.ts" ] } diff --git a/src/ui/docker-compose.yml b/src/ui/docker-compose.yml new file mode 100644 index 0000000..d5fbd30 --- /dev/null +++ b/src/ui/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3' +services: + mock-grpc-server: + build: + context: mock-grpc-server + networks: + - envoy-net + envoy: + image: envoyproxy/envoy:v1.29-latest + volumes: + - ./mock-grpc-server/envoy.yaml:/etc/envoy/envoy.yaml:ro + ports: + - "4201:4201" + networks: + - envoy-net +networks: + envoy-net: diff --git a/src/ui/go.mod b/src/ui/go.mod index c34317f..628cb5a 100644 --- a/src/ui/go.mod +++ b/src/ui/go.mod @@ -1,27 +1,35 @@ module server -go 1.21 +go 1.23.2 require ( - github.com/golang/glog v1.1.1 - google.golang.org/api v0.131.0 + github.com/golang/glog v1.2.2 + google.golang.org/api v0.203.0 ) require ( - cloud.google.com/go/compute v1.21.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/auth v0.10.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect + cloud.google.com/go/compute/metadata v0.5.2 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 // indirect - google.golang.org/grpc v1.56.2 // indirect - google.golang.org/protobuf v1.31.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/src/ui/go.sum b/src/ui/go.sum new file mode 100644 index 0000000..75b455c --- /dev/null +++ b/src/ui/go.sum @@ -0,0 +1,202 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= +cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= +cloud.google.com/go/auth v0.10.0 h1:tWlkvFAh+wwTOzXIjrwM64karR1iTBZ/GRr0S/DULYo= +cloud.google.com/go/auth v0.10.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= +cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= +github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.185.0 h1:ENEKk1k4jW8SmmaT6RE+ZasxmxezCrD5Vw4npvr+pAU= +google.golang.org/api v0.185.0/go.mod h1:HNfvIkJGlgrIlrbYkAm9W9IdkmKZjOTVh33YltygGbg= +google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= +google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 h1:CUiCqkPw1nNrNQzCCG4WA65m0nAmQiwXHpub3dNyruU= +google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/src/ui/mock-grpc-server/.dockerignore b/src/ui/mock-grpc-server/.dockerignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/src/ui/mock-grpc-server/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/src/ui/mock-grpc-server/Dockerfile b/src/ui/mock-grpc-server/Dockerfile new file mode 100644 index 0000000..339b95f --- /dev/null +++ b/src/ui/mock-grpc-server/Dockerfile @@ -0,0 +1,6 @@ +FROM node:lts +COPY . /app +WORKDIR /app +RUN npm install +ENV NODE_OPTIONS='--loader ts-node/esm' +ENTRYPOINT ["node", "server.ts"] diff --git a/src/ui/mock-grpc-server/copy-proto.sh b/src/ui/mock-grpc-server/copy-proto.sh new file mode 100755 index 0000000..9d735d0 --- /dev/null +++ b/src/ui/mock-grpc-server/copy-proto.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +# Unfortunately Docker doesn't support symlinks, so we need to copy the proto files + +# Make sure we're running in the script directory +pushd "$(dirname "$0")" +cp ../../proto/*.proto proto/ +popd + diff --git a/src/ui/mock-grpc-server/envoy.yaml b/src/ui/mock-grpc-server/envoy.yaml index a9cd615..5d1caf2 100644 --- a/src/ui/mock-grpc-server/envoy.yaml +++ b/src/ui/mock-grpc-server/envoy.yaml @@ -62,7 +62,7 @@ static_resources: - endpoint: address: socket_address: - address: 10.246.6.2 # you may need to replace this with your container/machine IP. + address: mock-grpc-server port_value: 4202 admin: diff --git a/src/ui/mock-grpc-server/package-lock.json b/src/ui/mock-grpc-server/package-lock.json new file mode 100644 index 0000000..bf9b507 --- /dev/null +++ b/src/ui/mock-grpc-server/package-lock.json @@ -0,0 +1,915 @@ +{ + "name": "modron-mock-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "modron-mock-server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@alenon/grpc-mock-server": "^3.0.21", + "@grpc/grpc-js": "^1.8", + "@improbable-eng/grpc-web": "^0.15.0", + "@types/google-protobuf": "^3.15.6", + "google-protobuf": "^3.21.2", + "nodemon": "^3.0.1", + "ts-node": "^10.9.1", + "wait-for-sigint": "^0.1.0" + }, + "devDependencies": { + "ts-protoc-gen": "^0.15.0" + } + }, + "node_modules/@alenon/grpc-mock-server": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/@alenon/grpc-mock-server/-/grpc-mock-server-3.0.21.tgz", + "integrity": "sha512-ZSEPk7BwRm18sGsu4665outrCDBkpPV3sDT/kAS6IdJdsXvwcVcY5EBlnRGGiBW8mkXgFHrO+nJsccmBqD0TvA==", + "dependencies": { + "@grpc/grpc-js": "^1.8.8", + "@types/debug": "^4.1.8", + "@types/google-protobuf": "^3.7.4", + "@types/node": "^20.2.4", + "debug": "^4.3.1", + "google-protobuf": "^3.14.0", + "protobufjs": "^7.2.1" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.8.22", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.22.tgz", + "integrity": "sha512-oAjDdN7fzbUi+4hZjKG96MR6KTEubAeMpQEb+77qy+3r0Ua5xTFuie6JOLr4ZZgl5g+W5/uRTS2M1V8mVAFPuA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz", + "integrity": "sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@improbable-eng/grpc-web": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.15.0.tgz", + "integrity": "sha512-ERft9/0/8CmYalqOVnJnpdDry28q+j+nAlFFARdjyxXDJ+Mhgv9+F600QC8BR9ygOfrXRlAk6CvST2j+JCpQPg==", + "dependencies": { + "browser-headers": "^0.4.1" + }, + "peerDependencies": { + "google-protobuf": "^3.14.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, + "node_modules/@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/google-protobuf": { + "version": "3.15.6", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.6.tgz", + "integrity": "sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, + "node_modules/@types/node": { + "version": "20.4.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", + "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-headers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz", + "integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==" + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.2.tgz", + "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nodemon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", + "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/protobufjs": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-protoc-gen": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/ts-protoc-gen/-/ts-protoc-gen-0.15.0.tgz", + "integrity": "sha512-TycnzEyrdVDlATJ3bWFTtra3SCiEP0W0vySXReAuEygXCUr1j2uaVyL0DhzjwuUdQoW5oXPwk6oZWeA0955V+g==", + "dev": true, + "dependencies": { + "google-protobuf": "^3.15.5" + }, + "bin": { + "protoc-gen-ts": "bin/protoc-gen-ts" + } + }, + "node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, + "node_modules/wait-for-sigint": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wait-for-sigint/-/wait-for-sigint-0.1.0.tgz", + "integrity": "sha512-vrHZMa7tTI/9zD3HR6nSZ6242PpUVw5Tn4Mij8PJObahR1zW2mQZ7mzUjRp+iGViD7a39gysct8/1cEl7LE2ig==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/src/ui/mock-grpc-server/package.json b/src/ui/mock-grpc-server/package.json index 49fa780..ea116a8 100644 --- a/src/ui/mock-grpc-server/package.json +++ b/src/ui/mock-grpc-server/package.json @@ -15,7 +15,7 @@ "wait-for-sigint": "^0.1.0", "nodemon": "^3.0.1", "ts-node": "^10.9.1", - "@grpc/grpc-js": "^1.8.18", + "@grpc/grpc-js": "^1.8", "@improbable-eng/grpc-web": "^0.15.0", "@types/google-protobuf": "^3.15.6", "google-protobuf": "^3.21.2" diff --git a/src/ui/mock-grpc-server/proto/.gitkeep b/src/ui/mock-grpc-server/proto/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/ui/mock-grpc-server/server.ts b/src/ui/mock-grpc-server/server.ts index 6e8a655..960e944 100644 --- a/src/ui/mock-grpc-server/server.ts +++ b/src/ui/mock-grpc-server/server.ts @@ -9,8 +9,8 @@ import * as proto_loader from '@grpc/proto-loader' const __dirname = dirname(fileURLToPath(import.meta.url)) class ModronMockGrpcServer { - private static readonly MODRON_PROTO_PATH: string = __dirname + '/../../proto/modron.proto' - private static readonly NOTIFICATION_PROTO_PATH: string = __dirname + '/../../proto/notification.proto' + private static readonly MODRON_PROTO_PATH: string = 'proto/modron.proto' + private static readonly NOTIFICATION_PROTO_PATH: string = 'proto/notification.proto' private static readonly PKG_NAME: string = '' private static readonly MODRON_SERVICE_NAME: string = 'ModronService' private static readonly NOTIFICATION_SERVICE_NAME: string = 'NotificationService' @@ -76,6 +76,41 @@ class ModronMockGrpcServer { await this.initMockServer() } + private generateObservations(ruleNr: number, projectName: string, amountObs?: number) { + let observations = []; + if(amountObs == undefined){ + return; + } + for(let i = 0; i < ruleNr; i++){ + observations.push(new this.mpb.Observation.constructor({ + name: `obs-${ruleNr}-rsrc-${i}`, + timestamp: { + seconds: new Date().getTime() / 1000, + nanos: 456, + }, + uid: "5cedca54-a6e0-4de5-8df5-facc533f5903--" + ruleNr, + remediation: this.getRemediation(), + resource: new this.mpb.Resource.constructor({ + name: `resource-${i}` + "[observation" + ruleNr + "]", + resourceGroupName: "project" + projectName, + timestamp: { + seconds: new Date().getTime() / 1000, + nanos: 456, + }, + }), + })); + } + + return observations + } + + private getRemediation() { + return new this.mpb.Remediation.constructor({ + description: "The project \"projects/example\" gives the principal [\"some-account@example.iam.gserviceaccount.com\"](https://example.com) vast permissions through the role `compute.loadBalancerAdmin`. This principal is defined in another project which means that anybody with rights in that project can use it to control the resources in this one.", + recommendation: "Replace the principal [\"some-account@example.iam.gserviceaccount.com\"](https://example.com) controlling the project \"projects/example\" with a principal created in the project \"//cloudresourcemanager.googleapis.com/folders/12345678\" that grants it the smallest set of permissions needed to operate.", + }) + } + private async initMockServer() { const modron_impls = { GetStatusCollectAndScan: (call: any, cb: any) => { @@ -144,29 +179,10 @@ class ModronMockGrpcServer { resourceGroupName: e[2], rulesObservations: [0, 1, 2, 3, 4, 5, 6].map(ruleNb => new this.mpb.RuleObservationPair.constructor({ rule: "observation" + ruleNb, - observations: (e[1] as Array).filter(ele => ele === ruleNb).map(e1 => new this.mpb.Observation.constructor({ - name: "observation" + e1, - timestamp: { - seconds: 123, - nanos: 456, - }, - uid: "5cedca54-a6e0-4de5-8df5-facc533f5903--" + e1, - remediation: new this.mpb.Remediation.constructor({ - description: "some description [title](https://www.example.com)", - recommendation: "do something [title](https://www.example.com)", - }), - resource: new this.mpb.Resource.constructor({ - name: "project" + e[0] + "[observation" + e1 + "]", - resourceGroupName: "project" + e[0], - timestamp: { - seconds: 1273, - nanos: 456, - }, - }), - })), - })) + observations: this.generateObservations(ruleNb, e[2] as string, e[1][ruleNb]), + })), + nextPageToken: '', })), - nextPageToken: '', })) } else { cb(null, new this.mpb.ListObservationsResponse.constructor({ @@ -177,26 +193,7 @@ class ModronMockGrpcServer { resourceGroupName: e[2], rulesObservations: [0, 1, 2, 3, 4, 5, 6].map(ruleNb => new this.mpb.RuleObservationPair.constructor({ rule: "observation" + ruleNb, - observations: (e[1] as Array).filter(ele => ele === ruleNb).map(e1 => new this.mpb.Observation.constructor({ - name: "observation" + e1, - timestamp: { - seconds: new Date().getTime() / 1000, - nanos: 456, - }, - uid: "5cedca54-a6e0-4de5-8df5-facc533f5903--" + e1, - remediation: new this.mpb.Remediation.constructor({ - description: "some description [title](https://www.example.com)", - recommendation: "do something [title](https://www.example.com)", - }), - resource: new this.mpb.Resource.constructor({ - name: "project" + e[0] + "[observation" + e1 + "]", - resourceGroupName: "project" + e[0], - timestamp: { - seconds: new Date().getTime() / 1000, - nanos: 456, - }, - }), - })), + observations: this.generateObservations(ruleNb, e[2] as string, e[1][ruleNb]), })) })), nextPageToken: '', @@ -255,14 +252,12 @@ await server.run() let sigterm = () => { process.stdin.resume() - var p = new Promise(function (resolve, reject) { + return new Promise(function (resolve, reject) { process.on('SIGTERM', function () { process.stdin.pause() resolve() }) - }) - - return p + }); } await sigterm() diff --git a/src/ui/package-lock.json b/src/ui/package-lock.json new file mode 100644 index 0000000..ef4713c --- /dev/null +++ b/src/ui/package-lock.json @@ -0,0 +1,321 @@ +{ + "name": "ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ui", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "concurrently": "^8.2.2" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/src/ui/package.json b/src/ui/package.json index d116669..7d23058 100644 --- a/src/ui/package.json +++ b/src/ui/package.json @@ -3,25 +3,12 @@ "version": "0.0.0", "scripts": { "postinstall": "(cd client && npm install); (cd mock-grpc-server && npm install)", - "dev": "npm run dev:mock-grpc-server-envoy && concurrently --kill-others \"npm run dev:client\" \"npm run dev:mock-grpc-server\"", - "dev:client": "cd client/ && ng serve --verbose --proxy-config ../proxy.conf.json", + "dev": "concurrently --kill-others \"npm run dev:client\" \"docker-compose up -d\"", + "dev:client": "cd client/ && npm run ng -- serve --verbose --proxy-config ../proxy.conf.json", "dev:mock-grpc-server-envoy": "cd mock-grpc-server/ && docker run --rm -p4201:4201 -p9901:9901 -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml:ro -t envoyproxy/envoy:v1.24-latest", - "dev:mock-grpc-server": "npm run --prefix mock-grpc-server/ dev", - "genproto": "npm run --prefix client/ genproto && npm run --prefix mock-grpc-server/ genproto" - }, - "devDependencies": { - "@angular-eslint/eslint-plugin": "^16.1.0", - "@angular-eslint/eslint-plugin-template": "^16.1.0", - "@angular-eslint/template-parser": "^16.1.0", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", - "concurrently": "^8.2.0", - "eslint": "^8.45.0" + "dev:mock-grpc-server": "npm run --prefix mock-grpc-server/ dev" }, "dependencies": { - "@alenon/grpc-mock-server": "^3.0.21", - "@angular/material": "^16.1.5", - "google-protobuf": "^3.21.2", - "ts-node": "^10.9.1" + "concurrently": "^8.2.2" } } diff --git a/src/ui/server.go b/src/ui/server.go index fbcc3db..24b5375 100644 --- a/src/ui/server.go +++ b/src/ui/server.go @@ -40,6 +40,7 @@ func main() { } mux := http.NewServeMux() mux.HandleFunc("/", handle) + glog.Infof("Listening on port %d", port) if err := http.ListenAndServe(fmt.Sprintf(":%d", port), mux); err != nil { glog.Errorf("listenAndServe: %v", err) os.Exit(2) diff --git a/src/utils/gcp.go b/src/utils/gcp.go new file mode 100644 index 0000000..d859a92 --- /dev/null +++ b/src/utils/gcp.go @@ -0,0 +1,101 @@ +package utils + +import ( + "fmt" + "strings" + + "github.com/nianticlabs/modron/src/constants" + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +func IsGCPServiceAccountProject(project string) bool { + _, ok := constants.GCPServiceAgentsProjects[project] + return ok +} + +func GetGCPProjectFromSAEmail(saEmail string) string { + if strings.HasSuffix(saEmail, appspotServiceAccountSuffix) { + return strings.TrimSuffix(saEmail, appspotServiceAccountSuffix) + } + + if strings.HasSuffix(saEmail, iamGServiceAccountSuffix) { + noSuffix := strings.TrimSuffix(saEmail, iamGServiceAccountSuffix) + split := strings.Split(noSuffix, "@") + if len(split) != 2 { //nolint:mnd + log.Errorf("failed to split service account email: %s", saEmail) + return "" + } + return split[1] + } + + if strings.HasSuffix(saEmail, developerGserviceAccountSuffix) { + // Can't handle this (we need a project ID, here we only have a project number) + return "" + } + + log.Warnf("unknown service account email format: %s", saEmail) + return "" +} + +type ResourceLink struct { + Name string + URL string + Type string +} + +// TODO: Make sure all the observations use this function +func LinkGCPResource(resource *pb.Resource) ResourceLink { + switch resource.Type.(type) { + case *pb.Resource_Bucket: + return ResourceLink{ + Name: resource.Name, + URL: "https://console.cloud.google.com/storage/browser/" + resource.Name, + Type: "bucket", + } + case *pb.Resource_Database: + return ResourceLink{ + Name: resource.Name, + URL: fmt.Sprintf( + "https://console.cloud.google.com/spanner/instances/%s/details/databases", + resource.Name, + ), + Type: "database", + } + + case *pb.Resource_ServiceAccount: + saEmail := strings.TrimPrefix(resource.Name, constants.GCPServiceAccountPrefix) + return ResourceLink{ + Name: saEmail, + URL: fmt.Sprintf( + "https://console.cloud.google.com/iam-admin/serviceaccounts/details/%s?project=%s", + saEmail, + strings.TrimPrefix(resource.ResourceGroupName, constants.GCPProjectsNamePrefix), + ), + Type: "service account", + } + case *pb.Resource_ResourceGroup: + gcpType := "" + switch { + case strings.HasPrefix(resource.Name, constants.GCPProjectsNamePrefix): + gcpType = "project" + case strings.HasPrefix(resource.Name, constants.GCPFolderIDPrefix): + gcpType = "folder" + case strings.HasPrefix(resource.Name, constants.GCPOrgIDPrefix): + gcpType = "organization" + default: + log.Warnf("LinkGCPResource: unknown resource type: %s", resource.Name) + } + return ResourceLink{ + Name: strings.TrimPrefix(resource.Name, constants.GCPProjectsNamePrefix), + URL: fmt.Sprintf("https://console.cloud.google.com/welcome?project=%s", + strings.TrimPrefix(resource.Name, constants.GCPProjectsNamePrefix), + ), + Type: gcpType, + } + default: + log.Warnf("LinkGCPResource: unknown resource type: %T", resource.Type) + } + return ResourceLink{ + Name: resource.Name, + } +} diff --git a/src/utils/gke.go b/src/utils/gke.go new file mode 100644 index 0000000..7aa2d15 --- /dev/null +++ b/src/utils/gke.go @@ -0,0 +1,33 @@ +package utils + +import ( + "fmt" + "strings" +) + +func GetGKEReference(resourceLink string) (projectID string, location string, clusterName string, namespace string) { + // resourceLink is formatted as follows: + // //container.googleapis.com/projects/project-id/zones/us-central1-b/clusters/gke-cluster-name/k8s/namespaces/kubernetes-ns-name + split := strings.Split(resourceLink, "/") + if len(split) != 12 { //nolint:mnd + return "", "", "", "" + } + projectID = split[4] + location = split[6] + clusterName = split[8] + namespace = split[11] + return +} + +func GetGkePodLink(name string, parent string) string { + // name is like "my-pod-name" + // parent is "//container.googleapis.com/projects/project-id/zones/us-central1-b/clusters/gke-cluster-name/k8s/namespaces/kubernetes-ns-name" + projectID, location, clusterName, namespace := GetGKEReference(parent) + return fmt.Sprintf("https://console.cloud.google.com/kubernetes/pod/%s/%s/%s/%s/details?project=%s", + location, + clusterName, + namespace, + name, + projectID, + ) +} diff --git a/src/utils/gke_test.go b/src/utils/gke_test.go new file mode 100644 index 0000000..6da3d91 --- /dev/null +++ b/src/utils/gke_test.go @@ -0,0 +1,31 @@ +package utils + +import "testing" + +func TestGetGKEReference(t *testing.T) { + tc := [][]string{ + { + "//container.googleapis.com/projects/project-id/zones/us-central1-b/clusters/gke-cluster-name/k8s/namespaces/kubernetes-ns-name", + "project-id", + "us-central1-b", + "gke-cluster-name", + "kubernetes-ns-name", + }, + } + + for _, tt := range tc { + projectID, location, clusterName, namespace := GetGKEReference(tt[0]) + if projectID != tt[1] { + t.Errorf("expected projectID %s, got %s", tt[1], projectID) + } + if location != tt[2] { + t.Errorf("expected location %s, got %s", tt[2], location) + } + if clusterName != tt[3] { + t.Errorf("expected clusterName %s, got %s", tt[3], clusterName) + } + if namespace != tt[4] { + t.Errorf("expected namespace %s, got %s", tt[4], namespace) + } + } +} diff --git a/src/utils/groups.go b/src/utils/groups.go new file mode 100644 index 0000000..f0d72ad --- /dev/null +++ b/src/utils/groups.go @@ -0,0 +1,15 @@ +package utils + +import ( + "golang.org/x/exp/maps" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +func GroupsFromResources(resources []*pb.Resource) (allGroups []string) { + resourceGroups := map[string]struct{}{} + for _, r := range resources { + resourceGroups[r.ResourceGroupName] = struct{}{} + } + return maps.Keys(resourceGroups) +} diff --git a/src/utils/hierarchy.go b/src/utils/hierarchy.go new file mode 100644 index 0000000..86c01d6 --- /dev/null +++ b/src/utils/hierarchy.go @@ -0,0 +1,72 @@ +package utils + +import ( + "fmt" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +func ComputeRgHierarchy(resources []*pb.Resource) (map[string]*pb.RecursiveResource, error) { + resourceMap := make(map[string]*pb.RecursiveResource) + for _, r := range resources { + if r.GetResourceGroup() == nil { + continue + } + recRes, err := ToRecursiveResource(r) + if err != nil { + return nil, err + } + resourceMap[r.Name] = recRes + } + + for _, r := range resources { + if r.Parent == "" { + var err error + resourceMap[""], err = ToRecursiveResource(r) + if err != nil { + return nil, err + } + continue + } + parent, ok := resourceMap[r.Parent] + if !ok { + log.Warnf("parent %q not found", r.Parent) + if r.ResourceGroupName == "" { + log.Errorf("resource %q has no parent and no resource group", r.Name) + continue + } + if r.ResourceGroupName == r.Name { + log.Errorf("resource %q is its own parent", r.Name) + continue + } + parent, ok = resourceMap[r.ResourceGroupName] + if !ok { + log.Errorf("resource group %q not found, is %q orphan?", r.ResourceGroupName, r.Name) + continue + } + } + recRes, err := ToRecursiveResource(r) + if err != nil { + return nil, fmt.Errorf("toRecursiveResource: %w", err) + } + parent.Children = append(parent.Children, recRes) + resourceMap[r.Parent] = parent + } + return resourceMap, nil +} + +func ToRecursiveResource(r *pb.Resource) (*pb.RecursiveResource, error) { + t, err := TypeFromResource(r) + if err != nil { + return nil, fmt.Errorf("typeFromResourceAsString: %w", err) + } + return &pb.RecursiveResource{ + Uuid: r.Uid, + Name: r.Name, + DisplayName: r.DisplayName, + Parent: r.Parent, + Type: t, + Labels: r.Labels, + Tags: r.Tags, + }, nil +} diff --git a/src/utils/keys.go b/src/utils/keys.go new file mode 100644 index 0000000..345cad2 --- /dev/null +++ b/src/utils/keys.go @@ -0,0 +1,44 @@ +package utils + +import ( + "strings" + + "github.com/sirupsen/logrus" + + "github.com/nianticlabs/modron/src/constants" +) + +var log = logrus.StandardLogger().WithField(constants.LogKeyPkg, "utils") + +const keyParts = 6 + +// GetKeyID converts a key reference (projects/my-project/serviceAccounts/sa-1/keys/abc) to a key ID (abc). +func GetKeyID(keyRef string) string { + if !strings.HasPrefix(keyRef, constants.GCPProjectsNamePrefix) { + log.Errorf("keyRef %s does not start with %s", keyRef, constants.GCPProjectsNamePrefix) + return keyRef + } + + split := strings.Split(keyRef, "/") + if len(split) < keyParts { + log.Errorf("keyRef %s has less than 6 parts", keyRef) + return keyRef + } + + return split[5] +} + +func GetServiceAccountNameFromKeyRef(keyRef string) string { + if !strings.HasPrefix(keyRef, constants.GCPProjectsNamePrefix) { + log.Errorf("keyRef %s does not start with %s", keyRef, constants.GCPProjectsNamePrefix) + return keyRef + } + + split := strings.Split(keyRef, "/") + if len(split) < keyParts { + log.Errorf("keyRef %s has less than 6 parts", keyRef) + return keyRef + } + + return split[3] +} diff --git a/src/utils/keys_test.go b/src/utils/keys_test.go new file mode 100644 index 0000000..99050c8 --- /dev/null +++ b/src/utils/keys_test.go @@ -0,0 +1,69 @@ +package utils_test + +import ( + "testing" + + "github.com/nianticlabs/modron/src/utils" +) + +func TestGetKeyID(t *testing.T) { + tests := []struct { + name string + keyRef string + expected string + }{ + { + name: "valid key reference", + keyRef: "projects/my-project/serviceAccounts/sa-1/keys/abc", + expected: "abc", + }, + { + name: "invalid key reference", + keyRef: "invalid-key-reference", + expected: "invalid-key-reference", + }, + { + name: "key reference with less than 6 parts", + keyRef: "projects/my-project/serviceAccounts/sa-1/keys", + expected: "projects/my-project/serviceAccounts/sa-1/keys", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := utils.GetKeyID(tt.keyRef); got != tt.expected { + t.Errorf("GetKeyID() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestGetServiceAccountNameFromKeyRef(t *testing.T) { + tests := []struct { + name string + keyRef string + expected string + }{ + { + name: "valid key reference", + keyRef: "projects/my-project/serviceAccounts/sa-1/keys/abc", + expected: "sa-1", + }, + { + name: "invalid key reference", + keyRef: "invalid-key-reference", + expected: "invalid-key-reference", + }, + { + name: "key reference with less than 6 parts", + keyRef: "projects/my-project/serviceAccounts/sa-1/keys", + expected: "projects/my-project/serviceAccounts/sa-1/keys", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := utils.GetServiceAccountNameFromKeyRef(tt.keyRef); got != tt.expected { + t.Errorf("GetServiceAccountNameFromKeyRef() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/src/utils/name.go b/src/utils/name.go new file mode 100644 index 0000000..060fd99 --- /dev/null +++ b/src/utils/name.go @@ -0,0 +1,26 @@ +package utils + +import "strings" + +const ( + containerAPI = "//container.googleapis.com/" + iamAPI = "//iam.googleapis.com/" + computeAPI = "//compute.googleapis.com/" +) + +// GetHumanReadableName returns the human-readable name of the resource - we currently use an allow-list of APIs +// to avoid interpreting the resource name incorrectly. +func GetHumanReadableName(resourceLink string) string { + for _, p := range []string{containerAPI, iamAPI, computeAPI} { + if strings.HasPrefix(resourceLink, p) { + r := strings.TrimPrefix(resourceLink, p) + split := strings.Split(r, "/") + return split[len(split)-1] + } + } + return resourceLink +} + +func StripProjectsPrefix(prefixedProject string) string { + return strings.TrimPrefix(prefixedProject, "projects/") +} diff --git a/src/utils/name_test.go b/src/utils/name_test.go new file mode 100644 index 0000000..1b0ff88 --- /dev/null +++ b/src/utils/name_test.go @@ -0,0 +1,62 @@ +package utils_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/nianticlabs/modron/src/utils" +) + +func TestGetHumanReadableName(t *testing.T) { + tc := [][]string{ + { + "//container.googleapis.com/projects/xyz/locations/us-central1/clusters/cluster-name/k8s/namespaces/kube-system/pods/my-pod-1", + "my-pod-1", + }, + { + "//container.googleapis.com/projects/xyz/locations/us-central1/clusters/cluster-name/k8s/namespaces/kube-system", + "kube-system", + }, + { + "//container.googleapis.com/projects/xyz/locations/us-central1/clusters/cluster-name", + "cluster-name", + }, + { + "//container.googleapis.com/projects/xyz/locations/us-central1", + "us-central1", + }, + { + "//container.googleapis.com/projects/xyz", + "xyz", + }, + { + "//iam.googleapis.com/projects/example-project/serviceAccounts/my-service-account@example-project.iam.gserviceaccount.com", + "my-service-account@example-project.iam.gserviceaccount.com", + }, + { + "//iam.googleapis.com/projects/example-project/serviceAccounts/3984989392373/keys/b8ceb3f5d69d4e46acc9e74bf224d4e9", + "b8ceb3f5d69d4e46acc9e74bf224d4e9", + }, + { + "//compute.googleapis.com/projects/example-project/zones/us-central1-f/instances/my-instance-4897322-03032024-cxx1-test-a0b0c0", + "my-instance-4897322-03032024-cxx1-test-a0b0c0", + }, + { + "//container.googleapis.com/projects/example-1/zones/us-central1-b/clusters/security-runners/k8s/namespaces/twistlock", + "twistlock", + }, + { + "//container.googleapis.com/projects/example-2/zones/us-central1-b/clusters/security-runners", + "security-runners", + }, + } + + for _, c := range tc { + want := c[1] + got := utils.GetHumanReadableName(c[0]) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("GetHumanReadableName(%q) mismatch (-want +got):\n%s", c[0], diff) + } + } +} diff --git a/src/utils/protobuf.go b/src/utils/protobuf.go new file mode 100644 index 0000000..4b0f80e --- /dev/null +++ b/src/utils/protobuf.go @@ -0,0 +1,40 @@ +package utils + +import ( + "fmt" + + "google.golang.org/protobuf/proto" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +func TypeFromResource(rsrc *pb.Resource) (ty string, err error) { + if rsrc == nil { + return "", fmt.Errorf("resource must not be nil") + } + reflectMsg := rsrc.ProtoReflect() + if reflectMsg == nil { + return "", fmt.Errorf("ProtoReflect() returned nil") + } + typeField := reflectMsg.Descriptor().Oneofs().ByName("type") + if typeField == nil { + return "", fmt.Errorf("cannot find field \"type\"") + } + field := reflectMsg.WhichOneof(typeField) + if field == nil { + return "", fmt.Errorf("cannot find field in oneof") + } + fieldMessage := reflectMsg.Get(field).Message() + if fieldMessage == nil { + return "", fmt.Errorf("field message is nil") + } + ty = string(fieldMessage.Descriptor().FullName()) + return +} + +func ProtoAcceptsTypes(types []proto.Message) (res []string) { + for _, t := range types { + res = append(res, string(t.ProtoReflect().Descriptor().FullName())) + } + return +} diff --git a/src/utils/protobuf_test.go b/src/utils/protobuf_test.go new file mode 100644 index 0000000..5c4c5b7 --- /dev/null +++ b/src/utils/protobuf_test.go @@ -0,0 +1,104 @@ +package utils + +import ( + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/proto" + + pb "github.com/nianticlabs/modron/src/proto/generated" +) + +func TestTypeFromResource(t *testing.T) { + tests := []struct { + res *pb.Resource + want string + }{ + { + res: &pb.Resource{Type: &pb.Resource_VmInstance{}}, + want: "VmInstance", + }, + { + res: &pb.Resource{Type: &pb.Resource_ApiKey{}}, + want: "APIKey", + }, + { + res: &pb.Resource{Type: &pb.Resource_ServiceAccount{}}, + want: "ServiceAccount", + }, + { + res: &pb.Resource{Type: &pb.Resource_KubernetesCluster{}}, + want: "KubernetesCluster", + }, + } + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + got, err := TypeFromResource(tt.res) + if err != nil { + t.Errorf("TypeFromResource() error = %v", err) + } + if got != tt.want { + t.Errorf("TypeFromResource() gotTy = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTypeFromResourceFail(t *testing.T) { + tests := []struct { + res *pb.Resource + wantErr string + }{ + { + res: &pb.Resource{}, + wantErr: "cannot find field in oneof", + }, + { + res: nil, + wantErr: "resource must not be nil", + }, + } + for _, tt := range tests { + t.Run(tt.wantErr, func(t *testing.T) { + _, err := TypeFromResource(tt.res) + if diff := cmp.Diff(tt.wantErr, err.Error()); diff != "" { + t.Errorf("TypeFromResource() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestToAcceptedTypes(t *testing.T) { + type args struct { + types []proto.Message + } + tests := []struct { + name string + args args + wantRes []string + }{ + { + name: "Three elements", + args: args{ + types: []proto.Message{ + &pb.APIKey{}, + &pb.ServiceAccount{}, + &pb.Bucket{}, + }, + }, + wantRes: []string{ + "APIKey", + "ServiceAccount", + "Bucket", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotRes := ProtoAcceptsTypes(tt.args.types); !reflect.DeepEqual(gotRes, tt.wantRes) { + t.Errorf("ProtoAcceptsTypes() = %v, want %v", gotRes, tt.wantRes) + } + }) + } +} diff --git a/src/utils/ref.go b/src/utils/ref.go new file mode 100644 index 0000000..7af0e03 --- /dev/null +++ b/src/utils/ref.go @@ -0,0 +1,9 @@ +package utils + +func RefOrNull(s string) *string { + if s == "" { + return nil + } + + return &s +} diff --git a/src/utils/resource_ref.go b/src/utils/resource_ref.go new file mode 100644 index 0000000..4c6322f --- /dev/null +++ b/src/utils/resource_ref.go @@ -0,0 +1,15 @@ +package utils + +import pb "github.com/nianticlabs/modron/src/proto/generated" + +func GetResourceRef(rsrc *pb.Resource) *pb.ResourceRef { + if rsrc == nil { + return nil + } + return &pb.ResourceRef{ + Uid: &rsrc.Uid, + GroupName: rsrc.ResourceGroupName, + ExternalId: RefOrNull(rsrc.Name), + CloudPlatform: pb.CloudPlatform_GCP, // TODO: Change when we have more cloud platforms + } +} diff --git a/src/utils/rule.go b/src/utils/rule.go new file mode 100644 index 0000000..d92d437 --- /dev/null +++ b/src/utils/rule.go @@ -0,0 +1,17 @@ +package utils + +import ( + "context" + "encoding/json" + + "github.com/nianticlabs/modron/src/model" +) + +func GetRuleConfig[T any](ctx context.Context, e model.Engine, name string, c *T) error { + v, err := e.GetRuleConfig(ctx, name) + if err != nil { + log.Errorf("no config found for rule %q: %v", name, err) + return err + } + return json.Unmarshal(v, c) +} diff --git a/src/utils/service_account.go b/src/utils/service_account.go new file mode 100644 index 0000000..bc6291c --- /dev/null +++ b/src/utils/service_account.go @@ -0,0 +1,7 @@ +package utils + +const ( + appspotServiceAccountSuffix = "@appspot.gserviceaccount.com" + iamGServiceAccountSuffix = ".iam.gserviceaccount.com" + developerGserviceAccountSuffix = "@developer.gserviceaccount.com" +) diff --git a/src/utils/service_account_test.go b/src/utils/service_account_test.go new file mode 100644 index 0000000..3cfac6b --- /dev/null +++ b/src/utils/service_account_test.go @@ -0,0 +1,35 @@ +package utils_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/nianticlabs/modron/src/utils" +) + +func TestGetProjectFromSAEmail(t *testing.T) { + tc := []struct { + saEmail string + expected string + }{ + { + "gitlab-sa@example-project.iam.gserviceaccount.com", + "example-project", + }, + { + "example-project@appspot.gserviceaccount.com", + "example-project", + }, + { + "123456789012-compute@developer.gserviceaccount.com", + "", + }, + } + + for _, tt := range tc { + if diff := cmp.Diff(tt.expected, utils.GetGCPProjectFromSAEmail(tt.saEmail)); diff != "" { + t.Errorf("unexpected result (-want +got):\n%s", diff) + } + } +} diff --git a/src/validation.go b/src/validation.go new file mode 100644 index 0000000..c7ca1da --- /dev/null +++ b/src/validation.go @@ -0,0 +1,119 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/nianticlabs/modron/src/collector" + "github.com/nianticlabs/modron/src/storage" +) + +func validateArgs() error { + var errArr []error + errArr = append(errArr, validateStorage()...) + errArr = append(errArr, validateProductionArgs()...) + errArr = append(errArr, validateGCPArgs()...) + errArr = append(errArr, validateRiskArgs()) + if err := errors.Join(errArr...); err != nil { + return err + } + warnArgs() + return nil +} + +func validateRiskArgs() error { + var impactMap map[string]string + if err := json.Unmarshal([]byte(args.ImpactMap), &impactMap); err != nil { + return fmt.Errorf("unable to decode impact map: %w", err) + } + + return nil +} + +func validateStorage() (errors []error) { + switch storage.Type(strings.ToLower(args.Storage)) { + case storage.Memory: + break + case storage.SQL: + errors = append(validateSQL(), errors...) + default: + errors = append(errors, fmt.Errorf("invalid storage type: %s", args.Storage)) + } + return +} + +func validateSQL() (errors []error) { + switch strings.ToLower(args.SQLBackendDriver) { + case "postgres": + break + default: + errors = append(errors, fmt.Errorf("invalid SQL backend driver: %s", args.SQLBackendDriver)) + return + } + + if args.SQLConnectionString == "" { + errors = append(errors, fmt.Errorf("SQL connection string is required")) + } + + if args.DbBatchSize < 1 { + errors = append(errors, fmt.Errorf("DB batch size must be greater than 0")) + } + + if args.DbMaxConnections < 1 { + errors = append(errors, fmt.Errorf("DB max connections must be greater than 0")) + } + + if args.DbMaxIdleConnections < 1 { + errors = append(errors, fmt.Errorf("DB max idle connections must be greater than 0")) + } + return +} + +func validateProductionArgs() (errors []error) { + if strings.EqualFold(args.Environment, "production") { + if args.NotificationService == "" { + errors = append(errors, fmt.Errorf("notification service is required in production")) + } + + if args.Collector == collector.Fake { + errors = append(errors, fmt.Errorf("fake collector cannot be used in production")) + } + + if args.SkipIAP { + errors = append(errors, fmt.Errorf("IAP cannot be skipped in production")) + } + + if args.PersistentCache { + errors = append(errors, fmt.Errorf("persistent cache cannot be used in production")) + } + } + return +} + +func validateGCPArgs() (errors []error) { + if args.OrgID == "" { + errors = append(errors, fmt.Errorf("organization ID is required")) + } + + if args.OrgSuffix == "" { + errors = append(errors, fmt.Errorf("organization suffix is required")) + } + return +} + +func warnArgs() { + if args.Collector == collector.Fake { + log.Warnf("Using fake collector") + } + if args.SkipIAP { + log.Warnf("Skipping IAP, if you see this in production, reach out to security.") + } + if args.NotificationService == "" { + log.Warnf("Notification service address is empty, logging instead") + } + if len(args.AdditionalAdminRoles) == 0 { + log.Warnf("No additional admin roles specified") + } +} diff --git a/terraform/dev/main.tf.example b/terraform/dev/main.tf.example index e618eec..724803c 100644 --- a/terraform/dev/main.tf.example +++ b/terraform/dev/main.tf.example @@ -3,10 +3,10 @@ module "modron" { source = "../modron" - domain = "hosted.at.example.com" + domain = "modron-dev.example.com" env = "dev" org_id = "GCP_ORGID" - project = "GCP_PROJECT_NAME-dev" + project = "my-modron-dev" zone = "GCP_ZONE" modron_admins = [ @@ -18,4 +18,8 @@ module "modron" { project_admins = [ "group:modron-project-admins@example.com" ] + docker_registry = "mirror.gcr.io" + notification_system = "https://notification-system.example.com" + notification_system_client_id = "client-id" + org_suffix = "@example.com" } diff --git a/terraform/modron/artifact_registry.tf b/terraform/modron/artifact_registry.tf new file mode 100644 index 0000000..8e290b8 --- /dev/null +++ b/terraform/modron/artifact_registry.tf @@ -0,0 +1,23 @@ +resource "google_artifact_registry_repository" "registry" { + location = local.region + repository_id = "modron" + description = "Modron Docker images" + format = "DOCKER" +} + +# writer is not enough: GitLab needs to be able to delete tags +# otherwise the pipeline will fail with IAM_PERMISSION_DENIED when trying to replace the :dev / :prod tags +data "google_iam_policy" "modron_repository_editor_policy" { + binding { + role = "organizations/0123456789/roles/ArtifactRegistryDockerEditor" + members = [ + "serviceAccount:${google_service_account.deployer_SA.email}" + ] + } +} + +resource "google_artifact_registry_repository_iam_policy" "modron_repository_write_policy" { + location = google_artifact_registry_repository.registry.location + repository = google_artifact_registry_repository.registry.name + policy_data = data.google_iam_policy.modron_repository_editor_policy.policy_data +} diff --git a/terraform/modron/cloud_run.tf b/terraform/modron/cloud_run.tf index 8729e0a..38a4d8e 100644 --- a/terraform/modron/cloud_run.tf +++ b/terraform/modron/cloud_run.tf @@ -1,132 +1,194 @@ -resource "google_cloud_run_service" "grpc_web" { +resource "google_cloud_run_v2_service" "grpc_web" { name = "modron-grpc-web-${var.env}" - location = substr(var.zone, 0, length(var.zone) - 2) + location = local.region - # We need this to avoid naming collision with CI/CD deployments. - autogenerate_revision_name = true template { - spec { - service_account_name = google_service_account.modron_runner.email - timeout_seconds = 1800 - containers { - image = "gcr.io/${var.project}/modron:${var.env}" - ports { - container_port = 8080 - name = "http1" - } - resources { - limits = { - cpu = "4000m" - memory = "4Gi" - } - } - env { - name = "ADMIN_GROUPS" - value = join(",", [for g in var.modron_admins : split(":", g)[1]]) - } - env { - name = "DB_MAX_CONNECTIONS" - # Max is 100 for Cloud SQL, but we may need some connections for other purposes. - value = 90 - } - env { - name = "ENVIRONMENT" - value = "PRODUCTION" - } - env { - name = "GCP_PROJECT_ID" - value = var.project - } - env { - name = "GLOG_logtostderr" - value = 1 - } - env { - name = "GLOG_v" - value = var.env == "dev" ? 10 : 1 - } - env { - name = "NOTIFICATION_INTERVAL_DURATION" - value = "720h" // 30d - } - env { - name = "NOTIFICATION_SERVICE" - value = "https://nagatha.example.com:443" - } - env { - name = "OBSERVATION_TABLE_ID" - value = "observations" - } - env { - name = "OPERATION_TABLE_ID" - value = "operations" - } - env { - name = "ORG_ID" - value = var.org_id - } - env { - name = "ORG_SUFFIX" - value = var.org_suffix - } - env { - name = "RESOURCE_TABLE_ID" - value = "resources" + scaling { + max_instance_count = 1 + min_instance_count = 1 + } + + service_account = google_service_account.modron_runner.email + timeout = "300s" + containers { + name = "modron" + depends_on = ["collector"] + image = "${local.region}-docker.pkg.dev/${var.project}/modron/modron:${var.env}" + ports { + container_port = 8080 + name = "http1" + } + startup_probe { + http_get { + path = "/healthz" + port = 8080 } - env { - name = "SQL_BACKEND_DRIVER" - value = "postgres" + initial_delay_seconds = 10 + period_seconds = 3 + failure_threshold = 5 * 20 # 5 minutes (20 times 3s intervals = 1 minute) + } + resources { + cpu_idle = false + limits = { + cpu = "4000m" + memory = "4Gi" } - env { - name = "SQL_CONNECT_STRING" - value_from { - secret_key_ref { - key = "latest" - name = split("/", resource.google_secret_manager_secret.sql_connect_string_config.name)[3] - } + } + env { + name = "ADDITIONAL_ADMIN_ROLES" + value = join(",", var.additional_admin_roles) + } + env { + name = "ADMIN_GROUPS" + value = join(",", [for g in var.modron_admins : split(":", g)[1]]) + } + env { + name = "ALLOWED_SCC_CATEGORIES" + value = join(",", var.allowed_scc_categories) + } + env { + name = "DB_MAX_CONNECTIONS" + # Max is 100 for Cloud SQL, but we may need some connections for other purposes. + value = 90 + } + env { + name = "ENVIRONMENT" + value = "production" + } + env { + name = "IMPACT_MAP" + value = jsonencode(var.impact_map) + } + env { + name = "LABEL_TO_EMAIL_REGEXP" + value = var.label_to_email_regexp + } + env { + name = "LABEL_TO_EMAIL_SUBSTITUTION" + value = var.label_to_email_substitution + } + env { + name = "LISTEN_ADDR" + value = "0.0.0.0" + } + env { + name = "LOG_LEVEL" + value = var.env == "dev" ? "debug" : "warning" + } + env { + name = "NOTIFICATION_INTERVAL_DURATION" + value = "720h" // 30d + } + env { + name = "NOTIFICATION_SERVICE" + value = var.notification_system + } + env { + name = "NOTIFICATION_SERVICE_CLIENT_ID" + value = var.notification_system_client_id + } + env { + name = "ORG_ID" + value = var.org_id + } + env { + name = "ORG_SUFFIX" + value = var.org_suffix + } + env { + name = "RULE_CONFIGS" + value = var.rule_configs + } + env { + name = "SELF_URL" + value = "https://${var.domain}" + } + env { + name = "SQL_BACKEND_DRIVER" + value = "postgres" + } + env { + name = "SQL_CONNECT_STRING" + value_source { + secret_key_ref { + secret = split("/", resource.google_secret_manager_secret.sql_connect_string_config.name)[3] + version = "latest" } } - env { - name = "STORAGE" - value = "SQL" + } + env { + name = "STORAGE" + value = "sql" + } + env { + name = "TAG_CUSTOMER_DATA" + value = "${var.org_id}/customer_data" + } + env { + name = "TAG_EMPLOYEE_DATA" + value = "${var.org_id}/employee_data" + } + env { + name = "TAG_ENVIRONMENT" + value = "${var.org_id}/environment" + } + + volume_mounts { + mount_path = "/cloudsql" + name = "cloudsql" + } + } + + containers { + name = "collector" + image = "${var.docker_registry}/otel/opentelemetry-collector-contrib:0.111.0" + startup_probe { + http_get { + path = "/" + port = 13133 } } + + volume_mounts { + mount_path = "/etc/otelcol-contrib" + name = "otel-config" + } + } + + vpc_access { + connector = google_vpc_access_connector.connector.id + egress = "PRIVATE_RANGES_ONLY" } - metadata { - annotations = { - "autoscaling.knative.dev/maxScale" = "1" - "autoscaling.knative.dev/minScale" = "1" - "client.knative.dev/user-image" = "gcr.io/${var.project}/modron:${var.env}" - "run.googleapis.com/cloudsql-instances" = google_sql_database_instance.instance.connection_name - "run.googleapis.com/cpu-throttling" = "false" - "run.googleapis.com/vpc-access-connector" = google_vpc_access_connector.connector.id - "run.googleapis.com/vpc-access-egress" = "private-ranges-only" - } - labels = { - "run.googleapis.com/startupProbeType" = "Default" + + volumes { + name = "cloudsql" + cloud_sql_instance { + instances = [ + google_sql_database_instance.instance.connection_name + ] } } - } - metadata { - annotations = { - "client.knative.dev/user-image" = "gcr.io/${var.project}/modron:${var.env}" - "run.googleapis.com/ingress" = "internal-and-cloud-load-balancing" + volumes { + name = "otel-config" + gcs { + bucket = google_storage_bucket.otel_config.name + } } } + + ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" + traffic { - percent = 100 - latest_revision = true + percent = 100 + type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST" } lifecycle { ignore_changes = [ - metadata[0].annotations["run.googleapis.com/operation-id"], - metadata[0].annotations["run.googleapis.com/client-name"], - metadata[0].annotations["run.googleapis.com/client-version"], - template[0].metadata[0].annotations["run.googleapis.com/operation-id"], - template[0].metadata[0].annotations["run.googleapis.com/client-name"], - template[0].metadata[0].annotations["run.googleapis.com/client-version"], + annotations["run.googleapis.com/operation-id"], + annotations["run.googleapis.com/client-name"], + annotations["run.googleapis.com/client-version"], ] } depends_on = [ @@ -134,65 +196,45 @@ resource "google_cloud_run_service" "grpc_web" { ] } -resource "google_cloud_run_service" "ui" { +resource "google_cloud_run_v2_service" "ui" { name = "modron-ui" - location = substr(var.zone, 0, length(var.zone) - 2) - - # We need this to avoid naming collision with CI/CD deployments. - autogenerate_revision_name = true + location = local.region template { - spec { - service_account_name = google_service_account.modron_runner.email - timeout_seconds = 300 - containers { - image = "gcr.io/${var.project}/modron-ui:${var.env}" - ports { - container_port = 8080 - name = "http1" - } - resources { - limits = { - cpu = "4000m" - memory = "4Gi" - } - } - env { - name = "DIST_PATH" - value = "./ui" - } - } + service_account = google_service_account.modron_runner.email + timeout = "300s" + scaling { + max_instance_count = 1 + min_instance_count = 1 } - metadata { - annotations = { - "autoscaling.knative.dev/maxScale" = "1" - "autoscaling.knative.dev/minScale" = "1" - "client.knative.dev/user-image" = "gcr.io/${var.project}/modron-ui:${var.env}" + containers { + image = "${local.region}-docker.pkg.dev/${var.project}/modron/modron-ui:${var.env}" + ports { + container_port = 8080 + name = "http1" } - labels = { - "run.googleapis.com/startupProbeType" = "Default" + resources { + limits = { + cpu = "4000m" + memory = "4Gi" + } + } + env { + name = "DIST_PATH" + value = "./ui" } } } - - metadata { - annotations = { - "client.knative.dev/user-image" = "gcr.io/${var.project}/modron-ui:${var.env}" - "run.googleapis.com/ingress" = "internal-and-cloud-load-balancing" - } - } + ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" traffic { - percent = 100 - latest_revision = true + percent = 100 + type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST" } lifecycle { ignore_changes = [ - metadata[0].annotations["run.googleapis.com/operation-id"], - metadata[0].annotations["run.googleapis.com/client-name"], - metadata[0].annotations["run.googleapis.com/client-version"], - template[0].metadata[0].annotations["run.googleapis.com/operation-id"], - template[0].metadata[0].annotations["run.googleapis.com/client-name"], - template[0].metadata[0].annotations["run.googleapis.com/client-version"], + annotations["run.googleapis.com/operation-id"], + annotations["run.googleapis.com/client-name"], + annotations["run.googleapis.com/client-version"], ] } depends_on = [ @@ -200,11 +242,6 @@ resource "google_cloud_run_service" "ui" { ] } -resource "google_project_iam_member" "runner_log_writer" { - project = var.project - role = "roles/logging.logWriter" - member = "serviceAccount:${google_service_account.modron_runner.email}" -} data "google_iam_policy" "cloud_run_invokers" { binding { @@ -214,13 +251,13 @@ data "google_iam_policy" "cloud_run_invokers" { } resource "google_cloud_run_service_iam_policy" "cloud_run_ui_invokers" { - service = google_cloud_run_service.ui.name - location = google_cloud_run_service.ui.location + service = google_cloud_run_v2_service.ui.name + location = google_cloud_run_v2_service.ui.location policy_data = data.google_iam_policy.cloud_run_invokers.policy_data } resource "google_cloud_run_service_iam_policy" "cloud_run_backend_invokers" { - service = google_cloud_run_service.grpc_web.name - location = google_cloud_run_service.grpc_web.location + service = google_cloud_run_v2_service.grpc_web.name + location = google_cloud_run_v2_service.grpc_web.location policy_data = data.google_iam_policy.cloud_run_invokers.policy_data } diff --git a/terraform/modron/cloud_sql.tf b/terraform/modron/cloud_sql.tf new file mode 100644 index 0000000..ef0a8b2 --- /dev/null +++ b/terraform/modron/cloud_sql.tf @@ -0,0 +1,128 @@ +resource "google_compute_global_address" "private_ip_address" { + name = "modron-${var.env}-db-address" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.cloud_run_network.id +} + +resource "google_service_networking_connection" "private_vpc_connection" { + network = google_compute_network.cloud_run_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_address.name] +} + +resource "random_id" "db_name_suffix" { + byte_length = 4 +} + +resource "google_sql_database_instance" "instance" { + name = "modron-${var.env}-${random_id.db_name_suffix.hex}" + database_version = "POSTGRES_14" + + depends_on = [google_service_networking_connection.private_vpc_connection] + + settings { + tier = "db-custom-8-30720" + availability_type = "ZONAL" + ip_configuration { + # Set this to true if you need to connect to the database via the Cloud SQL Proxy. + ipv4_enabled = false + private_network = google_compute_network.cloud_run_network.id + } + maintenance_window { + day = 7 + hour = 1 + } + database_flags { + name = "cloudsql.iam_authentication" + value = "on" + } + database_flags { + name = "max_connections" + // 100 is the maximum we can do from cloud run. + value = "100" + } + database_flags { + name = "log_temp_files" + value = "0" + } + backup_configuration { + enabled = true + location = "us" + } + insights_config { + query_insights_enabled = true + query_plans_per_minute = 5 + query_string_length = var.env == "dev" ? 4000 : 1024 + record_application_tags = false + record_client_address = false + } + } + + deletion_protection = "true" +} + +resource "google_sql_database" "modron_database" { + name = "modron${var.env}" + instance = google_sql_database_instance.instance.name +} + +resource "google_sql_user" "iam_user" { + name = "modron${var.env}runner" + instance = google_sql_database_instance.instance.name + # TODO: Move to cloud IAM (soon) + # https://github.com/GoogleCloudPlatform/cloud-sql-proxy#-enable_iam_login + type = "BUILT_IN" + password = random_password.sql_user_password.result +} + +resource "random_password" "sql_user_password" { + length = 16 + special = true + min_lower = 2 + min_numeric = 2 + min_special = 2 + min_upper = 2 + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +resource "google_compute_instance" "jump_host_sql" { + name = "jump-host-sql" + machine_type = "e2-standard-2" + zone = var.zone + + boot_disk { + initialize_params { + image = "ubuntu-os-cloud/ubuntu-2204-lts" + } + } + + network_interface { + network = google_compute_network.cloud_run_network.id + } + + metadata_startup_script = "sudo apt -y install postgresql-client-14 && gcloud -q components install cloud_sql_proxy" + + service_account { + # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles. + email = google_service_account.jump_host_runner.email + scopes = ["cloud-platform"] + } + + shielded_instance_config { + enable_secure_boot = true + } +} + +data "google_iam_policy" "jump_host_accessors" { + binding { + role = "roles/compute.instanceAdmin.v1" + members = var.modron_admins + } +} + +resource "google_compute_instance_iam_policy" "jump_host_policy" { + instance_name = google_compute_instance.jump_host_sql.id + policy_data = data.google_iam_policy.jump_host_accessors.policy_data +} diff --git a/terraform/modron/gitlab.tf b/terraform/modron/gitlab.tf new file mode 100644 index 0000000..5e5c616 --- /dev/null +++ b/terraform/modron/gitlab.tf @@ -0,0 +1,42 @@ +resource "google_service_account" "deployer_SA" { + account_id = "gitlab-deployer" + description = "Used by Gitlab to deploy on Cloud Run." + display_name = "gitlab-deployer" +} + +data "google_iam_policy" "gitlab_deployer" { + binding { + role = "roles/iam.serviceAccountTokenCreator" + members = [ + "serviceAccount:${var.gitlab_impersonator_service_account}", + ] + } + count = var.gitlab_impersonator_service_account != "" ? 1 : 0 +} + +resource "google_service_account_iam_policy" "gitlab_deployer_iam_policy" { + policy_data = data.google_iam_policy.gitlab_deployer[0].policy_data + service_account_id = google_service_account.deployer_SA.name + count = var.gitlab_impersonator_service_account != "" ? 1 : 0 +} + +resource "google_project_iam_member" "gitlab_cloud_build" { + project = var.project + role = "roles/cloudbuild.builds.editor" + member = "serviceAccount:${google_service_account.deployer_SA.email}" +} + +resource "google_project_iam_member" "gitlab_run_developer" { + project = var.project + role = "roles/run.developer" + member = "serviceAccount:${google_service_account.deployer_SA.email}" +} + +# This is required to build, according to Google it is compatible with the concept of least privilege -_- +# https://cloud.google.com/build/docs/securing-builds/store-manage-build-logs#viewing_build_logs +# TODO: Update to a custom log bucket and remove this permission. +resource "google_project_iam_member" "gitlab_cloud_build_storage" { + project = var.project + role = "roles/viewer" + member = "serviceAccount:${google_service_account.deployer_SA.email}" +} diff --git a/terraform/modron/load_balancer.tf b/terraform/modron/load_balancer.tf index e39b82c..67b875e 100644 --- a/terraform/modron/load_balancer.tf +++ b/terraform/modron/load_balancer.tf @@ -23,6 +23,7 @@ resource "google_compute_backend_service" "modron_grpc_web" { group = google_compute_region_network_endpoint_group.grpc_web_neg.self_link } iap { + enabled = true oauth2_client_id = google_iap_client.project_client.client_id oauth2_client_secret = google_iap_client.project_client.secret } @@ -44,6 +45,7 @@ resource "google_compute_backend_service" "modron_ui" { group = google_compute_region_network_endpoint_group.ui_neg.self_link } iap { + enabled = true oauth2_client_id = google_iap_client.project_client.client_id oauth2_client_secret = google_iap_client.project_client.secret } diff --git a/terraform/modron/main.tf b/terraform/modron/main.tf index 5761ba7..f20319e 100644 --- a/terraform/modron/main.tf +++ b/terraform/modron/main.tf @@ -1,10 +1,14 @@ provider "google" { project = var.project - region = substr(var.zone, 0, length(var.zone) - 2) + region = local.region access_token = data.google_service_account_access_token.sa.access_token zone = var.zone } +locals { + region = substr(var.zone, 0, length(var.zone) - 2) +} + resource "google_compute_ssl_policy" "modern_TLS_policy" { min_tls_version = "TLS_1_2" name = "modern-ssl-policy" diff --git a/terraform/modron/network.tf b/terraform/modron/network.tf index 1689e68..e7c0974 100644 --- a/terraform/modron/network.tf +++ b/terraform/modron/network.tf @@ -11,18 +11,18 @@ resource "google_compute_network" "cloud_run_network" { resource "google_compute_region_network_endpoint_group" "grpc_web_neg" { name = "modron-grpc-web-${var.env}-endpoint" network_endpoint_type = "SERVERLESS" - region = substr(var.zone, 0, length(var.zone) - 2) + region = local.region cloud_run { - service = google_cloud_run_service.grpc_web.name + service = google_cloud_run_v2_service.grpc_web.name } } resource "google_compute_region_network_endpoint_group" "ui_neg" { name = "modron-ui-${var.env}-endpoint" network_endpoint_type = "SERVERLESS" - region = substr(var.zone, 0, length(var.zone) - 2) + region = local.region cloud_run { - service = google_cloud_run_service.ui.name + service = google_cloud_run_v2_service.ui.name } } @@ -36,7 +36,7 @@ resource "google_vpc_access_connector" "connector" { # This is required to install packages on the SQL jump host resource "google_compute_router" "router" { name = "sql-jump-host" - region = substr(var.zone, 0, length(var.zone) - 2) + region = local.region network = google_compute_network.cloud_run_network.id bgp { diff --git a/terraform/modron/otel/README.md b/terraform/modron/otel/README.md new file mode 100644 index 0000000..90002f0 --- /dev/null +++ b/terraform/modron/otel/README.md @@ -0,0 +1,3 @@ +# otel-collector + +Configuration adapted from [GoogleCloudRun/opentelemetry-cloud-run](https://github.com/GoogleCloudPlatform/opentelemetry-cloud-run) diff --git a/terraform/modron/otel/config.yaml b/terraform/modron/otel/config.yaml new file mode 100644 index 0000000..53f7fd7 --- /dev/null +++ b/terraform/modron/otel/config.yaml @@ -0,0 +1,55 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + +processors: + batch: + # batch metrics before sending to reduce API usage + send_batch_max_size: 200 + send_batch_size: 200 + timeout: 5s + + memory_limiter: + # drop metrics if memory usage gets too high + check_interval: 1s + limit_percentage: 65 + spike_limit_percentage: 20 + + resourcedetection: + detectors: [env, gcp] + timeout: 2s + override: false + +exporters: + googlecloud: + log: + default_log_name: "otel-collector" + otlphttp: + endpoint: "http://10.43.0.2:80" + tls: + insecure: true + googlemanagedprometheus: + +extensions: + health_check: + endpoint: "0.0.0.0:13133" + +service: + extensions: [health_check] + pipelines: + traces: + receivers: [otlp] + processors: [resourcedetection] + exporters: [googlecloud,otlphttp] + logs: + receivers: [otlp] + processors: [resourcedetection] + exporters: [googlecloud] + metrics: + receivers: [otlp] + processors: [resourcedetection] + exporters: [googlemanagedprometheus] diff --git a/terraform/modron/project.tf b/terraform/modron/project.tf new file mode 100644 index 0000000..91a0ebb --- /dev/null +++ b/terraform/modron/project.tf @@ -0,0 +1,21 @@ +resource "google_project_iam_binding" "cloud_sql_admins" { + project = var.project + role = "roles/cloudsql.admin" + members = var.project_admins +} + +resource "google_project_iam_binding" "cloud_trace_agent" { + project = var.project + role = "roles/cloudtrace.agent" + members = [ + "serviceAccount:${google_service_account.modron_runner.email}", + ] +} + +resource "google_project_iam_binding" "monitoring_writer" { + project = var.project + role = "roles/monitoring.metricWriter" + members = [ + "serviceAccount:${google_service_account.modron_runner.email}", + ] +} \ No newline at end of file diff --git a/terraform/modron/secret.tf b/terraform/modron/secret.tf index 3394dfb..b354a3a 100644 --- a/terraform/modron/secret.tf +++ b/terraform/modron/secret.tf @@ -4,7 +4,7 @@ resource "google_secret_manager_secret" "sql_connect_string_config" { replication { user_managed { replicas { - location = substr(var.zone, 0, length(var.zone) - 2) + location = local.region } } } diff --git a/terraform/modron/service_account.tf b/terraform/modron/service_account.tf index c204476..c995b45 100644 --- a/terraform/modron/service_account.tf +++ b/terraform/modron/service_account.tf @@ -4,11 +4,49 @@ resource "google_service_account" "modron_runner" { display_name = "modron-${var.env}-runner" } +locals { + service_account_sa_users = [for v in compact([ + google_service_account.deployer_SA.email, + var.gitlab_impersonator_service_account, + ]) : "serviceAccount:${v}"] +} + +resource "google_service_account_iam_binding" "modron_runner_user" { + service_account_id = google_service_account.modron_runner.name + role = "roles/iam.serviceAccountUser" + members = concat(local.service_account_sa_users, var.project_admins) +} + +resource "google_project_iam_member" "runner_log_writer" { + project = var.project + role = "roles/logging.logWriter" + member = "serviceAccount:${google_service_account.modron_runner.email}" +} + +resource "google_project_iam_member" "project_monitoring" { + project = var.project + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.modron_runner.email}" +} + +resource "google_project_iam_member" "sql_client_iam" { + project = var.project + role = "roles/cloudsql.client" + member = "serviceAccount:${google_service_account.modron_runner.email}" +} +############ + resource "google_service_account" "jump_host_runner" { account_id = "modron-${var.env}-sql-jumphost" display_name = "modron-${var.env}-sql-jumphost" } +resource "google_service_account_iam_binding" "jump_host_runner_user" { + service_account_id = google_service_account.jump_host_runner.name + role = "roles/iam.serviceAccountUser" + members = var.project_admins +} + resource "google_project_iam_member" "jump_host_log_writer" { project = var.project role = "roles/logging.logWriter" diff --git a/terraform/modron/tracing.tf b/terraform/modron/tracing.tf new file mode 100644 index 0000000..786ce2d --- /dev/null +++ b/terraform/modron/tracing.tf @@ -0,0 +1,35 @@ +resource "google_storage_bucket" "otel_config" { + name = "${var.project}-otel-config" + location = local.region + uniform_bucket_level_access = true +} + +data "google_iam_policy" "otel_config" { + binding { + role = "roles/storage.objectViewer" + members = [ + "serviceAccount:${google_service_account.modron_runner.email}", + ] + } + + binding { + role = "roles/storage.admin" + members = concat( + var.project_admins, + [ + "serviceAccount:${data.google_service_account.terraform_sa.email}", + ] + ) + } +} + +resource "google_storage_bucket_iam_policy" "otel_config" { + bucket = google_storage_bucket.otel_config.name + policy_data = data.google_iam_policy.otel_config.policy_data +} + +resource "google_storage_bucket_object" "otel_config" { + bucket = google_storage_bucket.otel_config.name + name = "config.yaml" + content = file("${path.module}/otel/config.yaml") +} \ No newline at end of file diff --git a/terraform/modron/variables.tf b/terraform/modron/variables.tf index d3d371c..00dc900 100644 --- a/terraform/modron/variables.tf +++ b/terraform/modron/variables.tf @@ -34,12 +34,6 @@ variable "env" { description = "Environment type of the database." } -variable "dataset_id" { - description = "(optional) Name of the dataset to be created. Will default to modron" - type = string - default = "modron" -} - variable "project_admins" { description = "People that can impersonate the terraform account and manage the project." type = list(string) @@ -58,3 +52,81 @@ variable "modron_users" { description = "List of group or users that will have access to the modron UI. The content will still be showed depending on the users' access inside the organisation." type = list(string) } + +variable "notification_system" { + description = "Notification system to use for modron." + type = string + validation { + condition = length(var.notification_system) > 0 + error_message = "The notification_system URL is required" + } +} + +variable "notification_system_client_id" { + description = "Notification system client id." + type = string + validation { + condition = length(var.notification_system_client_id) > 0 + error_message = "The notification_system_client_id is required" + } +} + +variable "gitlab_impersonator_service_account" { + description = "The service account email that will impersonate the GitLab service account" + type = string + default = "" + validation { + error_message = "This must be a valid GCP service account email." + condition = can(regex(".*@.*\\.iam\\.gserviceaccount\\.com", var.gitlab_impersonator_service_account)) || length(var.gitlab_impersonator_service_account) == 0 + } +} + +variable "docker_registry" { + description = "Docker registry to use for the public images" + validation { + error_message = "The docker registry must be a valid URL" + condition = length(var.docker_registry) > 0 + } +} + +variable "impact_map" { + type = map(string) + description = "A map of environments to impact (e.g: prod -> IMPACT_HIGH) that will be used for calculating the risk score" + default = { + "prod" = "IMPACT_HIGH" + "staging" = "IMPACT_MEDIUM" + "dev" = "IMPACT_LOW" + } +} + +variable "additional_admin_roles" { + type = list(string) + default = [] + description = "A list of additional roles that are considered admin in GCP, for example ['organizations/11111/roles/MyOrgOwner']" +} + +variable "label_to_email_regexp" { + description = "Regexp to be used to convert the label contents of contact1,contact2 to an email" + type = string + default = "(.*)_(.*?)_(.*?)$" +} + +variable "label_to_email_substitution" { + description = "Substitution to be used to convert the label contents of contact1,contact2 to an email" + type = string + default = "$1@$2.$3" +} + +variable "allowed_scc_categories" { + description = "List of allowed Security Command Center categories to create observations.\nThese categories are the Finding.category (https://cloud.google.com/security-command-center/docs/reference/rest/v1/organizations.sources.findings#Finding), often refered to as \"API equivalent\" in the GCP console.\nFor example, if you want to allow the category \"GKE_RUNTIME_OS_VULNERABILITY\" you should add it to this list.\n\nA list with some possible findings can be found on https://cloud.google.com/chronicle/docs/ingestion/default-parsers/collect-security-command-center-findings." + type = list(string) + default = [] +} + +variable "rule_configs" { + description = "A JSON map of the rules to their configuration" + validation { + error_message = "The rule_configs must be a valid JSON map" + condition = can(jsondecode(var.rule_configs)) + } +} diff --git a/terraform/prod/main.tf.example b/terraform/prod/main.tf.example index 771679b..8dc1047 100644 --- a/terraform/prod/main.tf.example +++ b/terraform/prod/main.tf.example @@ -3,10 +3,10 @@ module "modron" { source = "../modron" - domain = "hosted.at.example.com" + domain = "modron-prod.example.com" env = "prod" org_id = "GCP_ORGID" - project = "GCP_PROJECT_NAME" + project = "my-modron-prod" zone = "GCP_ZONE" modron_admins = [ @@ -18,4 +18,8 @@ module "modron" { project_admins = [ "group:modron-project-admins@example.com" ] + docker_registry = "mirror.gcr.io" + notification_system = "https://notification-system.example.com" + notification_system_client_id = "client-id" + org_suffix = "@example.com" } diff --git a/utils/gcp_service_agents/.gitignore b/utils/gcp_service_agents/.gitignore new file mode 100644 index 0000000..94a2dd1 --- /dev/null +++ b/utils/gcp_service_agents/.gitignore @@ -0,0 +1 @@ +*.json \ No newline at end of file diff --git a/utils/gcp_service_agents/README.md b/utils/gcp_service_agents/README.md new file mode 100644 index 0000000..3278828 --- /dev/null +++ b/utils/gcp_service_agents/README.md @@ -0,0 +1,15 @@ +# gcp_service_agents + +GCP publishes a list of "Service Agents" on their [documentation pages](https://cloud.google.com/iam/docs/service-agents). +Unfortunately this list is not in a machine readable format. This little helper scrapes that page and provides a list +of project IDs (e.g: `service-PROJECT_NUMBER@gcp-sa-aiplatform-cc.iam.gserviceaccount.com` -> `gcp-sa-aiplatform-cc`) +that Google provides, and thus that are considered "secure" to be used in IAM policies. + +## Usage + +```bash +go run ./ -o out.json +jq -r '.projects[] | "\"" + . + "\"" + ": {},"' out.json | clipcopy +``` + +Then paste the content of your clipboard into the `constants/gcp_sa_projects.go` file. \ No newline at end of file diff --git a/utils/gcp_service_agents/go.mod b/utils/gcp_service_agents/go.mod new file mode 100644 index 0000000..458965b --- /dev/null +++ b/utils/gcp_service_agents/go.mod @@ -0,0 +1,17 @@ +module github.com/nianticlabs/modron/utils/gcp_service_agents + +go 1.23.2 + +require ( + github.com/alexflint/go-arg v1.5.1 + github.com/sirupsen/logrus v1.9.3 + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 +) + +require ( + github.com/PuerkitoBio/goquery v1.9.0 // indirect + github.com/alexflint/go-scalar v1.2.0 // indirect + github.com/andybalholm/cascadia v1.3.2 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect +) diff --git a/utils/gcp_service_agents/go.sum b/utils/gcp_service_agents/go.sum new file mode 100644 index 0000000..bb3eef3 --- /dev/null +++ b/utils/gcp_service_agents/go.sum @@ -0,0 +1,72 @@ +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/PuerkitoBio/goquery v1.9.0 h1:zgjKkdpRY9T97Q5DCtcXwfqkcylSFIVCocZmn2huTp8= +github.com/PuerkitoBio/goquery v1.9.0/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY= +github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= +github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= +github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= +github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= +github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= +github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/utils/gcp_service_agents/main.go b/utils/gcp_service_agents/main.go new file mode 100644 index 0000000..ec573f2 --- /dev/null +++ b/utils/gcp_service_agents/main.go @@ -0,0 +1,166 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "regexp" + "sort" + "strings" + + "github.com/PuerkitoBio/goquery" + "github.com/alexflint/go-arg" + "github.com/sirupsen/logrus" + "golang.org/x/exp/maps" +) + +const ( + serviceAgentsUrl = "https://cloud.google.com/iam/docs/service-agents" + firebaseServiceAccountsUrl = "https://firebase.google.com/support/guides/service-accounts" +) + +var ( + log = logrus.StandardLogger() + undocumentedProjects = map[string]struct{}{ + "appsheet-prod-service-accounts": {}, + "cloud-ml.google.com": {}, + "cloud-cdn-fill": {}, + "gae-api-prod.google.com": {}, + } +) + +var args struct { + OutputFile string `arg:"-o,required" help:"Output file to write the JSON to"` +} + +func main() { + arg.MustParse(&args) + + f, err := os.Create(args.OutputFile) + if err != nil { + log.Fatalf("failed to create output file: %v", err) + } + defer f.Close() + + if err := generateServiceAgents(f); err != nil { + log.Fatalf("failed to generate service agents: %v", err) + } +} + +type ServiceAgentProjects struct { + Projects []string `json:"projects"` +} + +func generateServiceAgents(f io.Writer) error { + projects := make(map[string]struct{}) + if err := scrapeServiceAgentsPage(projects); err != nil { + return err + } + + if err := scrapeFirebaseServiceAccountsPage(projects); err != nil { + return err + } + + for _, undocumentedProjects := range maps.Keys(undocumentedProjects) { + projects[undocumentedProjects] = struct{}{} + } + + projectIDs := maps.Keys(projects) + sort.Strings(projectIDs) + toWrite := ServiceAgentProjects{Projects: projectIDs} + if err := json.NewEncoder(f).Encode(toWrite); err != nil { + return fmt.Errorf("failed to encode JSON: %v", err) + } + return nil +} + +func scrapeFirebaseServiceAccountsPage(projects map[string]struct{}) error { + doc, err := getGoQueryDocument(firebaseServiceAccountsUrl) + if err != nil { + return err + } + doc.Find("table tbody tr").Each(func(i int, s *goquery.Selection) { + svcAccount := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) + svcAccountProject := getServiceAccountProject(svcAccount) + if len(svcAccountProject) == 0 { + log.Warnf("Service account project not found for %s", strings.ReplaceAll(svcAccount, "\n", " ")) + return + } + for _, saProjectID := range svcAccountProject { + projects[saProjectID] = struct{}{} + } + }) + return nil +} + +func getGoQueryDocument(pageURL string) (*goquery.Document, error) { + page, err := scrapePage(pageURL) + if err != nil { + return nil, fmt.Errorf("failed to scrape page: %v", err) + } + doc, err := goquery.NewDocumentFromReader(bytes.NewBufferString(page)) + if err != nil { + return nil, fmt.Errorf("failed to parse HTML: %v", err) + } + return doc, nil +} + +func scrapeServiceAgentsPage(projects map[string]struct{}) error { + doc, err := getGoQueryDocument(serviceAgentsUrl) + if err != nil { + return err + } + doc.Find("#service-agents tbody tr").Each(func(i int, s *goquery.Selection) { + serviceAgent := strings.TrimSpace(s.Find("td:nth-child(1)").Text()) + svcAccountProject := getServiceAccountProject(serviceAgent) + if len(svcAccountProject) == 0 { + log.Warnf("Service agent project not found for %s", strings.ReplaceAll(serviceAgent, "\n", " ")) + return + } + log.Infof("Service agent: %s", svcAccountProject) + for _, saProjectID := range svcAccountProject { + projects[saProjectID] = struct{}{} + } + }) + return nil +} + +var saAccountProjectRegex = regexp.MustCompile("\\S+@([A-z0-9-]+)\\.iam\\.gserviceaccount\\.com") + +func getServiceAccountProject(agent string) []string { + projectsMap := make(map[string]struct{}) + matches := saAccountProjectRegex.FindAllStringSubmatch(agent, -1) + for _, match := range matches { + if len(match) != 2 { + log.Warnf("failed to extract project from service agent: %s", agent) + } else { + if match[1] == "project-id" || match[1] == "project-name" { + continue + } + projectsMap[match[1]] = struct{}{} + } + } + return maps.Keys(projectsMap) +} + +func scrapePage(pageUrl string) (string, error) { + req, err := http.NewRequest("GET", pageUrl, nil) + if err != nil { + return "", err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + if res.StatusCode != http.StatusOK { + return "", fmt.Errorf("got status code %d", res.StatusCode) + } + buffer := bytes.NewBuffer(nil) + if _, err := io.Copy(buffer, res.Body); err != nil { + return "", fmt.Errorf("failed to read response body: %v", err) + } + return buffer.String(), nil +}