From e7c633d44e2714623e3dc24aff995bcfd7cd7293 Mon Sep 17 00:00:00 2001 From: Lei Jin Date: Wed, 30 Oct 2024 19:32:21 +0000 Subject: [PATCH 1/3] chore: Change go-sdk to v2 Signed-off-by: Lei Jin --- api/version.go | 2 +- go.mod | 3 +- go.sum | 2 + vendor/github.com/lacework/go-sdk/LICENSE | 201 ++ .../github.com/lacework/go-sdk/api/README.md | 61 + .../api/_templates/resource_groups/aws.json | 40 + .../api/_templates/resource_groups/azure.json | 70 + .../_templates/resource_groups/container.json | 50 + .../api/_templates/resource_groups/gcp.json | 70 + .../resource_groups/kubernetes.json | 49 + .../_templates/resource_groups/machine.json | 20 + .../api/_templates/resource_groups/oci.json | 50 + .../go-sdk/api/agent_access_tokens.go | 171 ++ .../lacework/go-sdk/api/agent_info.go | 100 + .../go-sdk/api/alert_channel_datadog.go | 90 + .../lacework/go-sdk/api/alert_channels.go | 233 +++ .../api/alert_channels_aws_cloudwatch.go | 51 + .../go-sdk/api/alert_channels_aws_s3.go | 57 + .../api/alert_channels_cisco_spark_webhook.go | 49 + .../go-sdk/api/alert_channels_email_user.go | 123 ++ .../go-sdk/api/alert_channels_gcp_pub_sub.go | 56 + .../go-sdk/api/alert_channels_ibm_qradar.go | 75 + .../api/alert_channels_jira_cloud_server.go | 110 ++ .../api/alert_channels_microsoft_teams.go | 49 + .../go-sdk/api/alert_channels_new_relic.go | 52 + .../go-sdk/api/alert_channels_pager_duty.go | 51 + .../api/alert_channels_service_now_rest.go | 82 + .../api/alert_channels_slack_channel.go | 51 + .../go-sdk/api/alert_channels_splunk.go | 61 + .../go-sdk/api/alert_channels_victorops.go | 51 + .../go-sdk/api/alert_channels_webhook.go | 51 + .../lacework/go-sdk/api/alert_profiles.go | 158 ++ .../lacework/go-sdk/api/alert_rules.go | 314 ++++ .../lacework/go-sdk/api/alert_templates.go | 69 + .../github.com/lacework/go-sdk/api/alerts.go | 181 ++ .../lacework/go-sdk/api/alerts_close.go | 82 + .../lacework/go-sdk/api/alerts_comment.go | 50 + .../lacework/go-sdk/api/alerts_details.go | 110 ++ .../go-sdk/api/alerts_details_events.go | 44 + .../go-sdk/api/alerts_details_integrations.go | 92 + .../api/alerts_details_investigation.go | 45 + .../go-sdk/api/alerts_details_related.go | 61 + .../go-sdk/api/alerts_details_timeline.go | 75 + .../lacework/go-sdk/api/alerts_search.go | 68 + vendor/github.com/lacework/go-sdk/api/api.go | 167 ++ vendor/github.com/lacework/go-sdk/api/auth.go | 165 ++ .../lacework/go-sdk/api/callbacks.go | 39 + .../github.com/lacework/go-sdk/api/client.go | 278 +++ .../lacework/go-sdk/api/cloud_accounts.go | 296 +++ .../go-sdk/api/cloud_accounts_aws_cfg.go | 57 + .../go-sdk/api/cloud_accounts_aws_ct_sqs.go | 92 + .../api/cloud_accounts_aws_eks_audit.go | 57 + .../go-sdk/api/cloud_accounts_aws_gov_cfg.go | 57 + .../go-sdk/api/cloud_accounts_aws_gov_ct.go | 58 + .../go-sdk/api/cloud_accounts_aws_sidekick.go | 84 + .../api/cloud_accounts_aws_sidekick_org.go | 107 ++ .../go-sdk/api/cloud_accounts_az_al.go | 58 + .../go-sdk/api/cloud_accounts_az_cfg.go | 57 + .../go-sdk/api/cloud_accounts_azure_ad_al.go | 59 + .../api/cloud_accounts_azure_sidekick.go | 88 + .../api/cloud_accounts_gcp_al_pubsub.go | 63 + .../go-sdk/api/cloud_accounts_gcp_at.go | 77 + .../go-sdk/api/cloud_accounts_gcp_cfg.go | 80 + .../api/cloud_accounts_gcp_gke_audit.go | 62 + .../go-sdk/api/cloud_accounts_gcp_sidekick.go | 115 ++ .../go-sdk/api/cloud_accounts_oci_cfg.go | 60 + .../go-sdk/api/compliance_evaluations.go | 68 + .../go-sdk/api/compliance_evaluations_aws.go | 54 + .../lacework/go-sdk/api/component_data.go | 208 +++ .../lacework/go-sdk/api/components.go | 81 + .../go-sdk/api/container_registries.go | 275 +++ ...container_registries_aws_ecr_access_key.go | 98 + .../container_registries_aws_ecr_iam_role.go | 69 + .../api/container_registries_dockerhub.go | 77 + .../api/container_registries_dockerhub_v2.go | 76 + .../api/container_registries_gcp_gar.go | 81 + .../api/container_registries_gcp_gcr.go | 71 + .../go-sdk/api/container_registries_ghcr.go | 82 + .../container_registries_inline_scanner.go | 67 + .../api/container_registries_proxy_scanner.go | 69 + .../lacework/go-sdk/api/data_export_rules.go | 130 ++ .../lacework/go-sdk/api/datasources.go | 88 + .../lacework/go-sdk/api/entities.go | 92 + .../go-sdk/api/entities_containers.go | 158 ++ .../lacework/go-sdk/api/entities_images.go | 124 ++ .../go-sdk/api/entities_machine_details.go | 171 ++ .../lacework/go-sdk/api/entities_machines.go | 164 ++ .../lacework/go-sdk/api/entities_users.go | 87 + .../github.com/lacework/go-sdk/api/errors.go | 113 ++ .../lacework/go-sdk/api/feature_flags.go | 27 + vendor/github.com/lacework/go-sdk/api/http.go | 258 +++ .../lacework/go-sdk/api/inventory.go | 90 + .../lacework/go-sdk/api/inventory_aws.go | 62 + .../github.com/lacework/go-sdk/api/logging.go | 156 ++ vendor/github.com/lacework/go-sdk/api/lql.go | 133 ++ .../lacework/go-sdk/api/lql_delete.go | 47 + .../lacework/go-sdk/api/lql_execute.go | 159 ++ .../lacework/go-sdk/api/lql_validate.go | 32 + .../github.com/lacework/go-sdk/api/metrics.go | 110 ++ .../lacework/go-sdk/api/organization_info.go | 53 + .../github.com/lacework/go-sdk/api/policy.go | 364 ++++ .../lacework/go-sdk/api/policy_exceptions.go | 111 ++ .../github.com/lacework/go-sdk/api/reader.go | 33 + .../api/report_rule_notification_types.go | 269 +++ .../lacework/go-sdk/api/report_rules.go | 305 +++ .../github.com/lacework/go-sdk/api/reports.go | 123 ++ .../lacework/go-sdk/api/reports_aws.go | 174 ++ .../lacework/go-sdk/api/reports_azure.go | 183 ++ .../go-sdk/api/reports_definitions.go | 237 +++ .../go-sdk/api/reports_distributions.go | 205 ++ .../lacework/go-sdk/api/reports_gcp.go | 204 ++ .../lacework/go-sdk/api/resource_groups.go | 262 +++ .../github.com/lacework/go-sdk/api/schemas.go | 44 + .../lacework/go-sdk/api/team_members.go | 295 +++ .../lacework/go-sdk/api/user_profile.go | 84 + vendor/github.com/lacework/go-sdk/api/v2.go | 265 +++ .../lacework/go-sdk/api/v2_configs.go | 34 + .../lacework/go-sdk/api/v2_configs_azure.go | 56 + .../lacework/go-sdk/api/v2_configs_gcp.go | 56 + .../lacework/go-sdk/api/v2_recommendations.go | 143 ++ .../go-sdk/api/v2_recommendations_aws.go | 70 + .../go-sdk/api/v2_recommendations_azure.go | 87 + .../go-sdk/api/v2_recommendations_gcp.go | 87 + .../lacework/go-sdk/api/v2_search_filters.go | 119 ++ .../lacework/go-sdk/api/v2_suppressions.go | 101 + .../go-sdk/api/v2_suppressions_aws.go | 29 + .../go-sdk/api/v2_suppressions_azure.go | 29 + .../go-sdk/api/v2_suppressions_gcp.go | 29 + .../lacework/go-sdk/api/v2_vulnerabilities.go | 772 ++++++++ .../v2_vulnerabilities_software_packages.go | 205 ++ .../github.com/lacework/go-sdk/api/version.go | 10 + .../go-sdk/api/vulnerability_exceptions.go | 474 +++++ .../api/vulnerability_exceptions_container.go | 91 + .../api/vulnerability_exceptions_host.go | 88 + .../go-sdk/cli/cdk/client/go/client.go | 215 +++ .../go-sdk/cli/cdk/go/proto/v1/cdk.pb.go | 708 +++++++ .../go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go | 226 +++ .../lacework/go-sdk/cli/cmd/access_token.go | 100 + .../lacework/go-sdk/cli/cmd/account.go | 88 + .../lacework/go-sdk/cli/cmd/agent.go | 395 ++++ .../go-sdk/cli/cmd/agent_aws-install_ec2ic.go | 205 ++ .../cli/cmd/agent_aws-install_ec2ssh.go | 212 +++ .../cli/cmd/agent_aws-install_ec2ssm.go | 405 ++++ .../go-sdk/cli/cmd/agent_gcp-install-osl.go | 215 +++ .../lacework/go-sdk/cli/cmd/agent_install.go | 384 ++++ .../lacework/go-sdk/cli/cmd/agent_list.go | 241 +++ .../lacework/go-sdk/cli/cmd/alert.go | 163 ++ .../lacework/go-sdk/cli/cmd/alert_channel.go | 227 +++ .../lacework/go-sdk/cli/cmd/alert_close.go | 172 ++ .../lacework/go-sdk/cli/cmd/alert_comment.go | 90 + .../lacework/go-sdk/cli/cmd/alert_list.go | 291 +++ .../go-sdk/cli/cmd/alert_list_fixable.go | 108 ++ .../lacework/go-sdk/cli/cmd/alert_profiles.go | 458 +++++ .../lacework/go-sdk/cli/cmd/alert_rules.go | 378 ++++ .../lacework/go-sdk/cli/cmd/alert_show.go | 109 ++ .../go-sdk/cli/cmd/alert_show_details.go | 107 ++ .../go-sdk/cli/cmd/alert_show_events.go | 16 + .../go-sdk/cli/cmd/alert_show_integrations.go | 55 + .../cli/cmd/alert_show_investigation.go | 52 + .../go-sdk/cli/cmd/alert_show_related.go | 59 + .../go-sdk/cli/cmd/alert_show_timeline.go | 57 + .../github.com/lacework/go-sdk/cli/cmd/api.go | 133 ++ .../github.com/lacework/go-sdk/cli/cmd/aws.go | 224 +++ .../lacework/go-sdk/cli/cmd/awsiam.go | 320 ++++ .../lacework/go-sdk/cli/cmd/cache.go | 317 ++++ .../github.com/lacework/go-sdk/cli/cmd/cdk.go | 177 ++ .../lacework/go-sdk/cli/cmd/cli_state.go | 517 ++++++ .../lacework/go-sdk/cli/cmd/cli_unix.go | 101 + .../lacework/go-sdk/cli/cmd/cli_windows.go | 63 + .../lacework/go-sdk/cli/cmd/cloud_account.go | 339 ++++ .../lacework/go-sdk/cli/cmd/compliance.go | 572 ++++++ .../lacework/go-sdk/cli/cmd/compliance_aws.go | 761 ++++++++ .../go-sdk/cli/cmd/compliance_azure.go | 765 ++++++++ .../lacework/go-sdk/cli/cmd/compliance_gcp.go | 845 +++++++++ .../lacework/go-sdk/cli/cmd/component.go | 1295 +++++++++++++ .../lacework/go-sdk/cli/cmd/component_args.go | 179 ++ .../lacework/go-sdk/cli/cmd/component_dev.go | 675 +++++++ .../lacework/go-sdk/cli/cmd/configure.go | 426 +++++ .../cli/cmd/configure_switch_profile.go | 79 + .../go-sdk/cli/cmd/container_registry.go | 199 ++ .../go-sdk/cli/cmd/content_library.go | 205 ++ .../lacework/go-sdk/cli/cmd/docs.go | 98 + .../lacework/go-sdk/cli/cmd/emoji.go | 30 + .../lacework/go-sdk/cli/cmd/emoji_unix.go | 31 + .../lacework/go-sdk/cli/cmd/emoji_windows.go | 27 + .../lacework/go-sdk/cli/cmd/errors.go | 137 ++ .../lacework/go-sdk/cli/cmd/errors_lql.go | 101 + .../lacework/go-sdk/cli/cmd/flags.go | 124 ++ .../github.com/lacework/go-sdk/cli/cmd/gcp.go | 111 ++ .../lacework/go-sdk/cli/cmd/generate.go | 323 ++++ .../lacework/go-sdk/cli/cmd/generate_aws.go | 1645 +++++++++++++++++ .../cli/cmd/generate_aws_controltower.go | 730 ++++++++ .../go-sdk/cli/cmd/generate_aws_eks_audit.go | 983 ++++++++++ .../lacework/go-sdk/cli/cmd/generate_azure.go | 895 +++++++++ .../go-sdk/cli/cmd/generate_cloud_account.go | 31 + .../go-sdk/cli/cmd/generate_execute.go | 477 +++++ .../lacework/go-sdk/cli/cmd/generate_gcp.go | 802 ++++++++ .../lacework/go-sdk/cli/cmd/generate_gke.go | 505 +++++ .../lacework/go-sdk/cli/cmd/generate_k8s.go | 24 + .../lacework/go-sdk/cli/cmd/generate_oci.go | 438 +++++ .../lacework/go-sdk/cli/cmd/grpc.go | 50 + .../lacework/go-sdk/cli/cmd/honeyvent.go | 225 +++ .../go-sdk/cli/cmd/integration_aws.go | 168 ++ .../cli/cmd/integration_aws_cloudwatch.go | 64 + .../cli/cmd/integration_aws_govcloud.go | 140 ++ .../cli/cmd/integration_aws_s3_channel.go | 80 + .../go-sdk/cli/cmd/integration_azure.go | 211 +++ .../go-sdk/cli/cmd/integration_cisco_webex.go | 64 + .../cli/cmd/integration_ctr_reg_limits.go | 125 ++ .../go-sdk/cli/cmd/integration_datadog.go | 96 + .../go-sdk/cli/cmd/integration_docker_hub.go | 128 ++ .../go-sdk/cli/cmd/integration_docker_v2.go | 112 ++ .../go-sdk/cli/cmd/integration_ecr.go | 212 +++ .../go-sdk/cli/cmd/integration_email.go | 68 + .../go-sdk/cli/cmd/integration_gar.go | 166 ++ .../go-sdk/cli/cmd/integration_gcp.go | 196 ++ .../cli/cmd/integration_gcp_pub_sub_audit.go | 121 ++ .../cmd/integration_gcp_pub_sub_channel.go | 109 ++ .../go-sdk/cli/cmd/integration_gcr.go | 156 ++ .../go-sdk/cli/cmd/integration_ghcr.go | 124 ++ .../cli/cmd/integration_inline_scanner.go | 93 + .../go-sdk/cli/cmd/integration_jira.go | 171 ++ .../cli/cmd/integration_microsoft_teams.go | 64 + .../cli/cmd/integration_new_relic_channel.go | 71 + .../go-sdk/cli/cmd/integration_oci.go | 107 ++ .../go-sdk/cli/cmd/integration_pagerduty.go | 64 + .../cli/cmd/integration_proxy_scanner.go | 110 ++ .../cli/cmd/integration_qradar_channel.go | 85 + .../cmd/integration_service_now_channel.go | 112 ++ .../cli/cmd/integration_slack_channel.go | 64 + .../go-sdk/cli/cmd/integration_splunk.go | 106 ++ .../go-sdk/cli/cmd/integration_victorops.go | 64 + .../go-sdk/cli/cmd/integration_webhook.go | 64 + .../github.com/lacework/go-sdk/cli/cmd/lql.go | 590 ++++++ .../lacework/go-sdk/cli/cmd/lql_create.go | 156 ++ .../lacework/go-sdk/cli/cmd/lql_delete.go | 57 + .../lacework/go-sdk/cli/cmd/lql_library.go | 117 ++ .../lacework/go-sdk/cli/cmd/lql_list.go | 88 + .../lacework/go-sdk/cli/cmd/lql_preview.go | 117 ++ .../lacework/go-sdk/cli/cmd/lql_show.go | 81 + .../lacework/go-sdk/cli/cmd/lql_sources.go | 195 ++ .../lacework/go-sdk/cli/cmd/lql_update.go | 149 ++ .../lacework/go-sdk/cli/cmd/lql_validate.go | 108 ++ .../lacework/go-sdk/cli/cmd/migration.go | 179 ++ .../lacework/go-sdk/cli/cmd/outputs.go | 150 ++ .../go-sdk/cli/cmd/package_manifest.go | 560 ++++++ .../lacework/go-sdk/cli/cmd/policy.go | 807 ++++++++ .../lacework/go-sdk/cli/cmd/policy_create.go | 147 ++ .../lacework/go-sdk/cli/cmd/policy_delete.go | 94 + .../lacework/go-sdk/cli/cmd/policy_disable.go | 71 + .../lacework/go-sdk/cli/cmd/policy_enable.go | 75 + .../go-sdk/cli/cmd/policy_exceptions.go | 640 +++++++ .../lacework/go-sdk/cli/cmd/policy_library.go | 552 ++++++ .../lacework/go-sdk/cli/cmd/policy_update.go | 225 +++ .../lacework/go-sdk/cli/cmd/prompt.go | 32 + .../go-sdk/cli/cmd/report_definitions.go | 375 ++++ .../cli/cmd/report_definitions_create.go | 343 ++++ .../go-sdk/cli/cmd/report_definitions_diff.go | 166 ++ .../cli/cmd/report_definitions_revert.go | 67 + .../cli/cmd/report_definitions_update.go | 345 ++++ .../go-sdk/cli/cmd/report_distributions.go | 226 +++ .../cli/cmd/report_distributions_create.go | 441 +++++ .../cli/cmd/report_distributions_update.go | 360 ++++ .../lacework/go-sdk/cli/cmd/report_rules.go | 412 +++++ .../go-sdk/cli/cmd/resource_group_v2.go | 94 + .../go-sdk/cli/cmd/resource_groups.go | 254 +++ .../lacework/go-sdk/cli/cmd/root.go | 431 +++++ .../lacework/go-sdk/cli/cmd/suppressions.go | 271 +++ .../go-sdk/cli/cmd/suppressions_aws.go | 437 +++++ .../go-sdk/cli/cmd/suppressions_azure.go | 414 +++++ .../go-sdk/cli/cmd/suppressions_gcp.go | 368 ++++ .../lacework/go-sdk/cli/cmd/table_render.go | 80 + .../lacework/go-sdk/cli/cmd/team_members.go | 405 ++++ .../lacework/go-sdk/cli/cmd/telemetry.go | 96 + .../test_resources/content-library/.version | 1 + .../content-library-nonzero.sh | 2 + .../content-library-nonzero.sh.sig | 1 + .../content-library-noparse.sh | 2 + .../content-library-noparse.sh.sig | 1 + .../lacework/go-sdk/cli/cmd/version.go | 294 +++ .../lacework/go-sdk/cli/cmd/vuln_container.go | 142 ++ .../cmd/vuln_container_list_assessments.go | 679 +++++++ .../cli/cmd/vuln_container_list_registries.go | 55 + .../go-sdk/cli/cmd/vuln_container_scan.go | 332 ++++ .../cmd/vuln_container_show_assessments.go | 783 ++++++++ .../lacework/go-sdk/cli/cmd/vuln_host.go | 247 +++ .../cli/cmd/vuln_host_gen_package_manifest.go | 48 + .../go-sdk/cli/cmd/vuln_host_list_cves.go | 352 ++++ .../go-sdk/cli/cmd/vuln_host_list_hosts.go | 237 +++ .../cmd/vuln_host_scan_package_manifest.go | 329 ++++ .../cli/cmd/vuln_host_show_assessment.go | 625 +++++++ .../lacework/go-sdk/cli/cmd/vuln_html.go | 370 ++++ .../lacework/go-sdk/cli/cmd/vulnerability.go | 362 ++++ .../cmd/vulnerability_exception_container.go | 188 ++ .../cli/cmd/vulnerability_exception_host.go | 177 ++ .../go-sdk/cli/cmd/vulnerabilty_exceptions.go | 391 ++++ .../go-sdk/internal/archive/detect.go | 89 + .../lacework/go-sdk/internal/archive/gz.go | 34 + .../lacework/go-sdk/internal/archive/tar.go | 64 + .../go-sdk/internal/array/contains.go | 66 + .../lacework/go-sdk/internal/array/join.go | 31 + .../lacework/go-sdk/internal/array/sort.go | 37 + .../lacework/go-sdk/internal/array/unique.go | 33 + .../lacework/go-sdk/internal/cache/cache.go | 34 + .../internal/capturer/capture_output.go | 62 + .../lacework/go-sdk/internal/databox/blob.go | 34 + .../lacework/go-sdk/internal/databox/box.go | 91 + .../go-sdk/internal/failon/count_operation.go | 49 + .../lacework/go-sdk/internal/file/file.go | 55 + .../lacework/go-sdk/internal/format/secret.go | 32 + .../go-sdk/internal/format/strings.go | 46 + .../lacework/go-sdk/internal/intgguid/rand.go | 53 + .../go-sdk/internal/lacework/server.go | 97 + .../lacework/go-sdk/internal/pointer/bool.go | 10 + .../go-sdk/internal/unique/strings.go | 19 + .../go-sdk/internal/validate/email.go | 22 + .../go-sdk/lwcloud/gcp/helpers/helper.go | 79 + .../lwcloud/gcp/resources/folders/folders.go | 107 ++ .../gcp/resources/instances/instances.go | 238 +++ .../lwcloud/gcp/resources/models/models.go | 70 + .../gcp/resources/projects/projects.go | 132 ++ .../lacework/go-sdk/lwcomponent/DESIGN.md | 158 ++ .../lacework/go-sdk/lwcomponent/api_info.go | 38 + .../lacework/go-sdk/lwcomponent/catalog.go | 468 +++++ .../go-sdk/lwcomponent/cdk_component.go | 239 +++ .../go-sdk/lwcomponent/cdk_executable.go | 119 ++ .../lacework/go-sdk/lwcomponent/component.go | 769 ++++++++ .../lacework/go-sdk/lwcomponent/dev_info.go | 41 + .../lacework/go-sdk/lwcomponent/error.go | 38 + .../lacework/go-sdk/lwcomponent/executable.go | 99 + .../lacework/go-sdk/lwcomponent/host_info.go | 152 ++ .../lacework/go-sdk/lwcomponent/http.go | 81 + .../lacework/go-sdk/lwcomponent/library.go | 34 + .../lacework/go-sdk/lwcomponent/minisign.go | 72 + .../lacework/go-sdk/lwcomponent/staging.go | 253 +++ .../lacework/go-sdk/lwcomponent/status.go | 84 + .../lwcomponent/test_resources/env-vars.sh | 2 + .../lwcomponent/test_resources/env-vars.sig | 1 + .../lwcomponent/test_resources/hello-world.sh | 2 + .../test_resources/hello-world.sig | 1 + .../test_resources/hello-world2.sh | 4 + .../test_resources/hello-world2.sig | 1 + .../lacework/go-sdk/lwcomponent/types.go | 50 + .../lacework/go-sdk/lwconfig/README.md | 46 + .../lacework/go-sdk/lwconfig/config.go | 184 ++ .../lacework/go-sdk/lwdomain/README.md | 43 + .../lacework/go-sdk/lwdomain/domain.go | 99 + .../lacework/go-sdk/lwgenerate/aws/aws.go | 1406 ++++++++++++++ .../aws_controltower/aws_controltower.go | 477 +++++ .../lwgenerate/aws_eks_audit/aws_eks_audit.go | 697 +++++++ .../lacework/go-sdk/lwgenerate/azure/azure.go | 671 +++++++ .../lacework/go-sdk/lwgenerate/constants.go | 43 + .../lacework/go-sdk/lwgenerate/gcp/gcp.go | 932 ++++++++++ .../lacework/go-sdk/lwgenerate/gcp/gke.go | 256 +++ .../go-sdk/lwgenerate/gcp/service_account.go | 110 ++ .../lacework/go-sdk/lwgenerate/hcl.go | 662 +++++++ .../lacework/go-sdk/lwgenerate/oci/oci.go | 177 ++ .../lacework/go-sdk/lwlogger/README.md | 48 + .../lacework/go-sdk/lwlogger/logger.go | 198 ++ .../lacework/go-sdk/lwrunner/awsrunner.go | 422 +++++ .../lacework/go-sdk/lwrunner/gcprunner.go | 122 ++ .../lacework/go-sdk/lwrunner/runner.go | 227 +++ .../lacework/go-sdk/lwrunner/ssh.go | 103 ++ .../lacework/go-sdk/lwseverity/severity.go | 172 ++ .../lacework/go-sdk/lwtime/README.md | 112 ++ .../lacework/go-sdk/lwtime/epoch.go | 39 + .../lacework/go-sdk/lwtime/epochstring.go | 33 + .../lacework/go-sdk/lwtime/nanotime.go | 33 + .../lacework/go-sdk/lwtime/nattime.go | 201 ++ .../lacework/go-sdk/lwtime/reltime.go | 257 +++ .../lacework/go-sdk/lwtime/rfc1123z.go | 29 + .../lacework/go-sdk/lwtime/rfc3339.go | 5 + .../lacework/go-sdk/lwupdater/README.md | 46 + .../lacework/go-sdk/lwupdater/updater.go | 223 +++ vendor/modules.txt | 39 + 375 files changed, 68997 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/lacework/go-sdk/LICENSE create mode 100644 vendor/github.com/lacework/go-sdk/api/README.md create mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/aws.json create mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/azure.json create mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/container.json create mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/gcp.json create mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/kubernetes.json create mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/machine.json create mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/oci.json create mode 100644 vendor/github.com/lacework/go-sdk/api/agent_access_tokens.go create mode 100644 vendor/github.com/lacework/go-sdk/api/agent_info.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channel_datadog.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_aws_cloudwatch.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_aws_s3.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_cisco_spark_webhook.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_email_user.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_gcp_pub_sub.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_ibm_qradar.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_jira_cloud_server.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_microsoft_teams.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_new_relic.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_pager_duty.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_service_now_rest.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_slack_channel.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_splunk.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_victorops.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_webhook.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_profiles.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_rules.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alert_templates.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_close.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_comment.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_events.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_integrations.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_investigation.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_related.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_timeline.go create mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_search.go create mode 100644 vendor/github.com/lacework/go-sdk/api/api.go create mode 100644 vendor/github.com/lacework/go-sdk/api/auth.go create mode 100644 vendor/github.com/lacework/go-sdk/api/callbacks.go create mode 100644 vendor/github.com/lacework/go-sdk/api/client.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_cfg.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_ct_sqs.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_cfg.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_ct.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick_org.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_al.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_cfg.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_ad_al.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_sidekick.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_al_pubsub.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_at.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_cfg.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_gke_audit.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_sidekick.go create mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_oci_cfg.go create mode 100644 vendor/github.com/lacework/go-sdk/api/compliance_evaluations.go create mode 100644 vendor/github.com/lacework/go-sdk/api/compliance_evaluations_aws.go create mode 100644 vendor/github.com/lacework/go-sdk/api/component_data.go create mode 100644 vendor/github.com/lacework/go-sdk/api/components.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_access_key.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_iam_role.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub_v2.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gar.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gcr.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_ghcr.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_inline_scanner.go create mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_proxy_scanner.go create mode 100644 vendor/github.com/lacework/go-sdk/api/data_export_rules.go create mode 100644 vendor/github.com/lacework/go-sdk/api/datasources.go create mode 100644 vendor/github.com/lacework/go-sdk/api/entities.go create mode 100644 vendor/github.com/lacework/go-sdk/api/entities_containers.go create mode 100644 vendor/github.com/lacework/go-sdk/api/entities_images.go create mode 100644 vendor/github.com/lacework/go-sdk/api/entities_machine_details.go create mode 100644 vendor/github.com/lacework/go-sdk/api/entities_machines.go create mode 100644 vendor/github.com/lacework/go-sdk/api/entities_users.go create mode 100644 vendor/github.com/lacework/go-sdk/api/errors.go create mode 100644 vendor/github.com/lacework/go-sdk/api/feature_flags.go create mode 100644 vendor/github.com/lacework/go-sdk/api/http.go create mode 100644 vendor/github.com/lacework/go-sdk/api/inventory.go create mode 100644 vendor/github.com/lacework/go-sdk/api/inventory_aws.go create mode 100644 vendor/github.com/lacework/go-sdk/api/logging.go create mode 100644 vendor/github.com/lacework/go-sdk/api/lql.go create mode 100644 vendor/github.com/lacework/go-sdk/api/lql_delete.go create mode 100644 vendor/github.com/lacework/go-sdk/api/lql_execute.go create mode 100644 vendor/github.com/lacework/go-sdk/api/lql_validate.go create mode 100644 vendor/github.com/lacework/go-sdk/api/metrics.go create mode 100644 vendor/github.com/lacework/go-sdk/api/organization_info.go create mode 100644 vendor/github.com/lacework/go-sdk/api/policy.go create mode 100644 vendor/github.com/lacework/go-sdk/api/policy_exceptions.go create mode 100644 vendor/github.com/lacework/go-sdk/api/reader.go create mode 100644 vendor/github.com/lacework/go-sdk/api/report_rule_notification_types.go create mode 100644 vendor/github.com/lacework/go-sdk/api/report_rules.go create mode 100644 vendor/github.com/lacework/go-sdk/api/reports.go create mode 100644 vendor/github.com/lacework/go-sdk/api/reports_aws.go create mode 100644 vendor/github.com/lacework/go-sdk/api/reports_azure.go create mode 100644 vendor/github.com/lacework/go-sdk/api/reports_definitions.go create mode 100644 vendor/github.com/lacework/go-sdk/api/reports_distributions.go create mode 100644 vendor/github.com/lacework/go-sdk/api/reports_gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/api/resource_groups.go create mode 100644 vendor/github.com/lacework/go-sdk/api/schemas.go create mode 100644 vendor/github.com/lacework/go-sdk/api/team_members.go create mode 100644 vendor/github.com/lacework/go-sdk/api/user_profile.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_configs.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_configs_azure.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_configs_gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_recommendations.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_recommendations_aws.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_recommendations_azure.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_recommendations_gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_search_filters.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_suppressions.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_suppressions_aws.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_suppressions_azure.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_suppressions_gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities.go create mode 100644 vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities_software_packages.go create mode 100644 vendor/github.com/lacework/go-sdk/api/version.go create mode 100644 vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go create mode 100644 vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go create mode 100644 vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cdk/client/go/client.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk.pb.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/access_token.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/account.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ic.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssh.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssm.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_gcp-install-osl.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_install.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_list.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_channel.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_close.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_comment.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_list.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_list_fixable.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_profiles.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_rules.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_details.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_events.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_integrations.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_investigation.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_related.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_timeline.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/api.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/aws.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/awsiam.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cache.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cdk.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cli_state.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cli_unix.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cli_windows.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cloud_account.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/compliance.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/compliance_aws.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/compliance_azure.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/compliance_gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/component.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/component_args.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/component_dev.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/configure.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/configure_switch_profile.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/container_registry.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/content_library.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/docs.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/emoji.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/emoji_unix.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/emoji_windows.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/errors.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/errors_lql.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/flags.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_controltower.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_eks_audit.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_azure.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_cloud_account.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_execute.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_gke.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_k8s.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_oci.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/grpc.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/honeyvent.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_cloudwatch.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_govcloud.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_s3_channel.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_azure.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_cisco_webex.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_ctr_reg_limits.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_datadog.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_hub.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_v2.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_ecr.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_email.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gar.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_audit.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_channel.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcr.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_ghcr.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_inline_scanner.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_jira.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_microsoft_teams.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_new_relic_channel.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_oci.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_pagerduty.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_proxy_scanner.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_qradar_channel.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_service_now_channel.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_slack_channel.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_splunk.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_victorops.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_webhook.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_create.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_delete.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_library.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_list.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_preview.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_show.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_sources.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_update.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_validate.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/migration.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/outputs.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/package_manifest.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_create.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_delete.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_disable.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_enable.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_exceptions.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_library.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_update.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/prompt.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_create.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_diff.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_revert.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_update.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_create.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_update.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_rules.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/resource_group_v2.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/resource_groups.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/root.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/suppressions.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_aws.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_azure.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/table_render.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/team_members.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/telemetry.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/.version create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh.sig create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh.sig create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/version.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_assessments.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_registries.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_scan.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_show_assessments.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_gen_package_manifest.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_cves.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_hosts.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_scan_package_manifest.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_show_assessment.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_html.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_container.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_host.go create mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vulnerabilty_exceptions.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/archive/detect.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/archive/gz.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/archive/tar.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/array/contains.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/array/join.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/array/sort.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/array/unique.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/cache/cache.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/capturer/capture_output.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/databox/blob.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/databox/box.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/failon/count_operation.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/file/file.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/format/secret.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/format/strings.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/intgguid/rand.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/lacework/server.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/pointer/bool.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/unique/strings.go create mode 100644 vendor/github.com/lacework/go-sdk/internal/validate/email.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/helpers/helper.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/folders/folders.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/instances/instances.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/models/models.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/projects/projects.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/DESIGN.md create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/api_info.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/catalog.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/cdk_component.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/cdk_executable.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/component.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/dev_info.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/error.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/executable.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/host_info.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/http.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/library.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/minisign.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/staging.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/status.go create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sh create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sig create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sh create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sig create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sh create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sig create mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/types.go create mode 100644 vendor/github.com/lacework/go-sdk/lwconfig/README.md create mode 100644 vendor/github.com/lacework/go-sdk/lwconfig/config.go create mode 100644 vendor/github.com/lacework/go-sdk/lwdomain/README.md create mode 100644 vendor/github.com/lacework/go-sdk/lwdomain/domain.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/aws/aws.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/aws_controltower/aws_controltower.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/aws_eks_audit/aws_eks_audit.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/azure/azure.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/constants.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gcp.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gke.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/gcp/service_account.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/hcl.go create mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/oci/oci.go create mode 100644 vendor/github.com/lacework/go-sdk/lwlogger/README.md create mode 100644 vendor/github.com/lacework/go-sdk/lwlogger/logger.go create mode 100644 vendor/github.com/lacework/go-sdk/lwrunner/awsrunner.go create mode 100644 vendor/github.com/lacework/go-sdk/lwrunner/gcprunner.go create mode 100644 vendor/github.com/lacework/go-sdk/lwrunner/runner.go create mode 100644 vendor/github.com/lacework/go-sdk/lwrunner/ssh.go create mode 100644 vendor/github.com/lacework/go-sdk/lwseverity/severity.go create mode 100644 vendor/github.com/lacework/go-sdk/lwtime/README.md create mode 100644 vendor/github.com/lacework/go-sdk/lwtime/epoch.go create mode 100644 vendor/github.com/lacework/go-sdk/lwtime/epochstring.go create mode 100644 vendor/github.com/lacework/go-sdk/lwtime/nanotime.go create mode 100644 vendor/github.com/lacework/go-sdk/lwtime/nattime.go create mode 100644 vendor/github.com/lacework/go-sdk/lwtime/reltime.go create mode 100644 vendor/github.com/lacework/go-sdk/lwtime/rfc1123z.go create mode 100644 vendor/github.com/lacework/go-sdk/lwtime/rfc3339.go create mode 100644 vendor/github.com/lacework/go-sdk/lwupdater/README.md create mode 100644 vendor/github.com/lacework/go-sdk/lwupdater/updater.go diff --git a/api/version.go b/api/version.go index 7b8f06b8c..0c9b1c1b9 100644 --- a/api/version.go +++ b/api/version.go @@ -1,5 +1,5 @@ // Code generated by: scripts/version_updater.sh -// File generated at: 20241030175518 +// File generated at: 20241030194552 // // <<< DO NOT EDIT >>> // diff --git a/go.mod b/go.mod index a778edf3f..37ea0bde0 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/lacework/go-sdk +module github.com/lacework/go-sdk/v2 go 1.21 @@ -62,6 +62,7 @@ require ( github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.0 github.com/hashicorp/consul/sdk v0.13.1 + github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65 github.com/mattn/go-isatty v0.0.18 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/otiai10/copy v1.14.0 diff --git a/go.sum b/go.sum index 2fcb558a6..2cec564f6 100644 --- a/go.sum +++ b/go.sum @@ -355,6 +355,8 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kyokomi/emoji/v2 v2.2.12 h1:sSVA5nH9ebR3Zji1o31wu3yOwD1zKXQA2z0zUyeit60= github.com/kyokomi/emoji/v2 v2.2.12/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= +github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65 h1:A4LDKoyuC0fKknf7Nd6BM3MkFqzlbmjs0gXDPsH5szQ= +github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65/go.mod h1:l0kCskNExDs1E8fBfpaZeafC42pmKucdXn3nZO1iyLI= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= diff --git a/vendor/github.com/lacework/go-sdk/LICENSE b/vendor/github.com/lacework/go-sdk/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/lacework/go-sdk/api/README.md b/vendor/github.com/lacework/go-sdk/api/README.md new file mode 100644 index 000000000..4e3267824 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/README.md @@ -0,0 +1,61 @@ +# API Client + +A Golang API client for interacting with [Lacework APIs](https://docs.lacework.net/api/about-the-lacework-api). + +## Usage + +Download the library into your `$GOPATH`: + + $ go get github.com/lacework/go-sdk/api + +Import the library into your tool: + +```go +import "github.com/lacework/go-sdk/api" +``` + +## Requirements + +To interact with Lacework's API you need to have: + +1. A Lacework account +2. Either API access keys or token for authentication + +## Examples + +Create a new Lacework client that will automatically generate a new access token +from the provided set of API keys, then hit the `/api/v2/AlertChannels` endpoint +to list all available alert channels in your account: +```go +package main + +import ( + "fmt" + "log" + + "github.com/lacework/go-sdk/api" +) + +func main() { + lacework, err := api.NewClient("account", + api.WithTokenFromKeys("KEY", "SECRET"), + ) + if err != nil { + log.Fatal(err) + } + + alertChannels, err := lacework.V2.AlertChannels.List() + if err != nil { + log.Fatal(err) + } + + for _, channel := range alertChannels.Data { + fmt.Printf("Alert channel: %s\n", channel.Name) + } + // Output: + // + // Alert channel: DEFAULT EMAIL +} +``` + +Look at the [_examples/](_examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/aws.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/aws.json new file mode 100644 index 000000000..58e86a3c0 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/aws.json @@ -0,0 +1,40 @@ +{ + "filters": { + "filter0": { + "field": "Account", + "operation": "EQUALS", + "values": [ + "123456789012" + ] + }, + "filter1": { + "field": "Resource Tag", + "operation": "INCLUDES", + "key": "Hostname", + "values": [ + "*" + ] + }, + "filter2": { + "field": "Region", + "operation": "STARTS_WITH", + "values": [ + "ap-south" + ] + } + }, + "expression": { + "operator": "AND", + "children": [ + { + "filterName": "filter0" + }, + { + "filterName": "filter1" + }, + { + "filterName": "filter2" + } + ] + } +} \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/azure.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/azure.json new file mode 100644 index 000000000..a373c8ddf --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/azure.json @@ -0,0 +1,70 @@ +{ + "filters": { + "filter0": { + "field": "Subscription ID", + "operation": "EQUALS", + "values": [ + "0fe75302-1906-45ec-bde1-79b76899dd74" + ] + }, + "filter1": { + "field": "Subscription Name", + "operation": "STARTS_WITH", + "values": [ + "prod" + ] + }, + "filter2": { + "field": "Tenant ID", + "operation": "EQUALS", + "values": [ + "b329d4bf-4587-4ccf-e132-84e7025fa22d" + ] + }, + "filter3": { + "field": "Tenant Name", + "operation": "INCLUDES", + "values": [ + "*" + ] + }, + "filter4": { + "field": "Resource Tag", + "operation": "EQUALS", + "key": "Env", + "values": [ + "dev" + ] + }, + "filter5": { + "field": "Region", + "operation": "EQUALS", + "values": [ + "westus2" + ] + } + }, + "expression": { + "operator": "AND", + "children": [ + { + "filterName": "filter0" + }, + { + "filterName": "filter1" + }, + { + "filterName": "filter2" + }, + { + "filterName": "filter3" + }, + { + "filterName": "filter4" + }, + { + "filterName": "filter5" + } + ] + } + } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/container.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/container.json new file mode 100644 index 000000000..768b49d98 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/container.json @@ -0,0 +1,50 @@ +{ + "filters": { + "filter0": { + "field": "Image Tag", + "operation": "EQUALS", + "values": [ + "1.17.1" + ] + }, + "filter1": { + "field": "Container Label", + "operation": "INCLUDES", + "key": "app", + "values": [ + "*" + ] + }, + "filter2": { + "field": "Image Repo", + "operation": "EQUALS", + "values": [ + "parrotsec/core" + ] + }, + "filter3": { + "field": "Image Registry", + "operation": "EQUALS", + "values": [ + "k8s.gcr.io" + ] + } + }, + "expression": { + "operator": "AND", + "children": [ + { + "filterName": "filter0" + }, + { + "filterName": "filter1" + }, + { + "filterName": "filter2" + }, + { + "filterName": "filter3" + } + ] + } + } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/gcp.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/gcp.json new file mode 100644 index 000000000..9baedb611 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/gcp.json @@ -0,0 +1,70 @@ +{ + "filters": { + "filter0": { + "field": "Project ID", + "operation": "EQUALS", + "values": [ + "abc-demo-project-123" + ] + }, + "filter1": { + "field": "Organization ID", + "operation": "EQUALS", + "values": [ + "123456789012" + ] + }, + "filter2": { + "field": "Organization Name", + "operation": "STARTS_WITH", + "values": [ + "somecompany" + ] + }, + "filter3": { + "field": "Folder", + "operation": "EQUALS", + "values": [ + "1234567890123" + ] + }, + "filter4": { + "field": "Resource Label", + "operation": "EQUALS", + "key": "Env", + "values": [ + "dev" + ] + }, + "filter5": { + "field": "Region", + "operation": "EQUALS", + "values": [ + "australia-southeast2" + ] + } + }, + "expression": { + "operator": "AND", + "children": [ + { + "filterName": "filter0" + }, + { + "filterName": "filter1" + }, + { + "filterName": "filter2" + }, + { + "filterName": "filter3" + }, + { + "filterName": "filter4" + }, + { + "filterName": "filter5" + } + ] + } + } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/kubernetes.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/kubernetes.json new file mode 100644 index 000000000..a91d047c0 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/kubernetes.json @@ -0,0 +1,49 @@ +{ + "filters": { + "filter1": { + "field": "AWS Account", + "operation": "EQUALS", + "values": [ + "123456789012" + ] + }, + "filter2": { + "field": "AWS Region", + "operation": "EQUALS", + "values": [ + "us-west-2" + ] + }, + "filter3": { + "field": "Cluster Name", + "operation": "EQUALS", + "values": [ + "*" + ] + }, + "filter4": { + "field": "Namespace", + "operation": "EQUALS", + "values": [ + "prod" + ] + } + }, + "expression": { + "operator": "OR", + "children": [ + { + "filterName": "filter1" + }, + { + "filterName": "filter2" + }, + { + "filterName": "filter3" + }, + { + "filterName": "filter4" + } + ] + } +} \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/machine.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/machine.json new file mode 100644 index 000000000..ff31679c3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/machine.json @@ -0,0 +1,20 @@ +{ + "filters": { + "filter0": { + "field": "Machine Tag", + "operation": "EQUALS", + "key": "cluster", + "values": [ + "dev" + ] + } + }, + "expression": { + "operator": "OR", + "children": [ + { + "filterName": "filter0" + } + ] + } + } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/oci.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/oci.json new file mode 100644 index 000000000..bcab5eb71 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/oci.json @@ -0,0 +1,50 @@ +{ + "filters": { + "filter0": { + "field": "Compartment ID", + "operation": "EQUALS", + "values": [ + "ocid1.tenancy.oc1..abaaaabaqp6mzxi6z3xouvhawvsekntanafelw5vmwdjw5vqmgvlegcs2s6q" + ] + }, + "filter1": { + "field": "Compartment Name", + "operation": "INCLUDES", + "values": [ + "prod" + ] + }, + "filter2": { + "field": "Region", + "operation": "STARTS_WITH", + "values": [ + "us-" + ] + }, + "filter3": { + "field": "Resource Tag", + "operation": "EQUALS", + "key": "\"definedTags\".\"Oracle-Tags\".\"CreatedBy\"", + "values": [ + "default/my.email@somecompany.net" + ] + } + }, + "expression": { + "operator": "AND", + "children": [ + { + "filterName": "filter0" + }, + { + "filterName": "filter1" + }, + { + "filterName": "filter2" + }, + { + "filterName": "filter3" + } + ] + } + } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/agent_access_tokens.go b/vendor/github.com/lacework/go-sdk/api/agent_access_tokens.go new file mode 100644 index 000000000..e22d68bca --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/agent_access_tokens.go @@ -0,0 +1,171 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "time" + + "github.com/pkg/errors" +) + +// AgentAccessTokensService is the service that interacts with +// the AgentAccessTokens schema from the Lacework APIv2 Server +type AgentAccessTokensService struct { + client *Client +} + +// List returns a list of Agent Access Tokens +func (svc *AgentAccessTokensService) List() (response AgentAccessTokensResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2AgentAccessTokens, nil, &response) + return +} + +// Create creates a single Agent Access Token +func (svc *AgentAccessTokensService) Create(alias, desc string) ( + response AgentAccessTokenResponse, + err error, +) { + if alias == "" { + err = errors.New("token alias is required") + return + } + + err = svc.client.RequestEncoderDecoder("POST", + apiV2AgentAccessTokens, + AgentAccessTokenRequest{ + TokenAlias: alias, + Enabled: 1, + Props: &AgentAccessTokenProps{ + Description: desc, + }, + }, + &response, + ) + return +} + +// Get returns an Agent Access Token with the matching ID (token) +func (svc *AgentAccessTokensService) Get(token string) ( + response AgentAccessTokenResponse, + err error, +) { + err = svc.client.RequestDecoder("GET", + fmt.Sprintf(apiV2AgentAccessTokenFromID, token), + nil, + &response, + ) + return +} + +// Update updates an Agent Access Token with the provided request data +func (svc *AgentAccessTokensService) Update(token string, data AgentAccessTokenRequest) ( + response AgentAccessTokenResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("PATCH", + fmt.Sprintf(apiV2AgentAccessTokenFromID, token), + data, + &response, + ) + return +} + +// UpdateState updates only the state of an Agent Access Token (enable or disable) +func (svc *AgentAccessTokensService) UpdateState(token string, enable bool) ( + response AgentAccessTokenResponse, + err error, +) { + + request := AgentAccessTokenRequest{Enabled: 0} + if enable { + request.Enabled = 1 + } + err = svc.client.RequestEncoderDecoder("PATCH", + fmt.Sprintf(apiV2AgentAccessTokenFromID, token), + request, + &response, + ) + return +} + +// SearchAlias will search for an Agent Access Token that matches the provider token alias +func (svc *AgentAccessTokensService) SearchAlias(alias string) ( + response AgentAccessTokensResponse, + err error, +) { + + if alias == "" { + err = errors.New("specify a token alias to search") + return + } + err = svc.client.RequestEncoderDecoder("POST", + apiV2AgentAccessTokensSearch, + SearchFilter{ + Filters: []Filter{ + { + Field: "tokenAlias", + Expression: "eq", + Value: alias, + }, + }, + }, + &response, + ) + return +} + +type AgentAccessToken struct { + AccessToken string `json:"accessToken"` + CreatedTime time.Time `json:"createdTime"` + Props AgentAccessTokenProps `json:"props,omitempty"` + TokenAlias string `json:"tokenAlias"` + Enabled int `json:"tokenEnabled"` + Version string `json:"version"` +} + +func (t AgentAccessToken) State() bool { + return t.Enabled == 1 +} + +func (t AgentAccessToken) PrettyState() string { + if t.State() { + return "Enabled" + } + return "Disabled" +} + +type AgentAccessTokenProps struct { + CreatedTime time.Time `json:"createdTime,omitempty"` + Description string `json:"description,omitempty"` +} + +type AgentAccessTokenResponse struct { + Data AgentAccessToken `json:"data"` +} + +type AgentAccessTokensResponse struct { + Data []AgentAccessToken `json:"data"` +} + +type AgentAccessTokenRequest struct { + Enabled int `json:"tokenEnabled"` + TokenAlias string `json:"tokenAlias,omitempty"` + Props *AgentAccessTokenProps `json:"props,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/agent_info.go b/vendor/github.com/lacework/go-sdk/api/agent_info.go new file mode 100644 index 000000000..6b69370fc --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/agent_info.go @@ -0,0 +1,100 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "time" +) + +// AgentInfoService is the service that interacts with +// the AgentInfo schema from the Lacework APIv2 Server +type AgentInfoService struct { + client *Client +} + +func (svc *AgentInfoService) Search(response interface{}, filters SearchFilter) error { + return svc.client.RequestEncoderDecoder("POST", apiV2AgentInfoSearch, filters, response) +} + +type AgentInfoResponse struct { + Data []AgentInfo `json:"data"` + Paging V2Pagination `json:"paging"` + + v2PageMetadata `json:"-"` +} + +// Fulfill Pageable interface (look at api/v2.go) +func (r AgentInfoResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *AgentInfoResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +type AgentInfo struct { + AgentVersion string `json:"agentVersion"` + CreatedTime time.Time `json:"createdTime"` + Hostname string `json:"hostname"` + IpAddr string `json:"ipAddr"` + LastUpdate time.Time `json:"lastUpdate"` + Mid int `json:"mid"` + Mode string `json:"mode"` + Os string `json:"os"` + Status string `json:"status"` + Tags struct { + // Shared Tags + Arch string `json:"arch,omitempty"` + ExternalIP string `json:"ExternalIp,omitempty"` + Hostname string `json:"Hostname,omitempty"` + InstanceID string `json:"InstanceId,omitempty"` + InternalIP string `json:"InternalIp,omitempty"` + LwTokenShort string `json:"LwTokenShort,omitempty"` + Os string `json:"os,omitempty"` + VMInstanceType string `json:"VmInstanceType,omitempty"` + VMProvider string `json:"VmProvider,omitempty"` + Zone string `json:"Zone,omitempty"` + + // AWS Tags + Account string `json:"Account,omitempty"` + AmiID string `json:"AmiId,omitempty"` + Name string `json:"Name,omitempty"` + SubnetID string `json:"SubnetId,omitempty"` + VpcID string `json:"VpcId,omitempty"` + + // GCP Tags + Cluster string `json:"Cluster,omitempty"` + ClusterLocation string `json:"cluster-location,omitempty"` + ClusterName string `json:"cluster-name,omitempty"` + ClusterUID string `json:"cluster-uid,omitempty"` + CreatedBy string `json:"created-by,omitempty"` + EnableOSLogin string `json:"enable-oslogin,omitempty"` + Env string `json:"Env,omitempty"` + GCEtags string `json:"GCEtags,omitempty"` + GCIEnsureGKEDocker string `json:"gci-ensure-gke-docker,omitempty"` + GCIUpdateStrategy string `json:"gci-update-strategy,omitempty"` + GoogleComputeEnablePCID string `json:"google-compute-enable-pcid,omitempty"` + InstanceName string `json:"InstanceName,omitempty"` + InstanceTemplate string `json:"InstanceTemplate,omitempty"` + KubeLabels string `json:"kube-labels,omitempty"` + LWKubernetesCluster string `json:"lw_KubernetesCluster,omitempty"` + NumericProjectID string `json:"NumericProjectId,omitempty"` + ProjectID string `json:"ProjectId,omitempty"` + } `json:"tags"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channel_datadog.go b/vendor/github.com/lacework/go-sdk/api/alert_channel_datadog.go new file mode 100644 index 000000000..31b1c524b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channel_datadog.go @@ -0,0 +1,90 @@ +// +// Author:: Vatasha White () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "github.com/pkg/errors" + +const ( + // The list of valid inputs for DatadogSite field + DatadogSiteEu datadogSite = "eu" + DatadogSiteCom datadogSite = "com" + + // The list of valid inputs for DatadogService field + DatadogServiceLogsDetails datadogService = "Logs Detail" + DatadogServiceEventsSummary datadogService = "Events Summary" + DatadogServiceLogsSummary datadogService = "Logs Summary" +) + +// GetDatadog gets a single instance of a Datadog alert channel +// with the corresponding integration guid +func (svc *AlertChannelsService) GetDatadog(guid string) (response DatadogAlertChannelResponseV2, err error) { + err = svc.get(guid, &response) + return +} + +// UpdateDatadog updates a single instance of a Datadog integration on the Lacework server +func (svc *AlertChannelsService) UpdateDatadog(data AlertChannel) (response DatadogAlertChannelResponseV2, err error) { + err = svc.update(data.ID(), data, &response) + return +} + +// DatadogSite returns the datadogSite type for the corresponding string input +func DatadogSite(site string) (datadogSite, error) { + if val, ok := datadogSites[site]; ok { + return val, nil + } + return "", errors.Errorf("%v is not a valid Datadog Site", site) +} + +// DatadogService returns the datadogService type for the corresponding string input +func DatadogService(service string) (datadogService, error) { + if val, ok := datadogServices[service]; ok { + return val, nil + } + return "", errors.Errorf("%v is not a valid Datadog Service", service) +} + +type datadogSite string +type datadogService string + +type DatadogDataV2 struct { + ApiKey string `json:"apiKey"` + DatadogSite datadogSite `json:"datadogSite,omitempty"` + DatadogType datadogService `json:"datadogType,omitempty"` +} + +type DatadogAlertChannelV2 struct { + v2CommonIntegrationData + Data DatadogDataV2 `json:"data"` +} + +type DatadogAlertChannelResponseV2 struct { + Data DatadogAlertChannelV2 `json:"data"` +} + +var datadogSites = map[string]datadogSite{ + string(DatadogSiteEu): DatadogSiteEu, + string(DatadogSiteCom): DatadogSiteCom, +} + +var datadogServices = map[string]datadogService{ + string(DatadogServiceLogsDetails): DatadogServiceLogsDetails, + string(DatadogServiceEventsSummary): DatadogServiceEventsSummary, + string(DatadogServiceLogsSummary): DatadogServiceLogsSummary, +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels.go b/vendor/github.com/lacework/go-sdk/api/alert_channels.go new file mode 100644 index 000000000..9c26df485 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels.go @@ -0,0 +1,233 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +// AlertChannelsService is the service that interacts with +// the AlertChannels schema from the Lacework APIv2 Server +type AlertChannelsService struct { + client *Client +} + +// NewAlertChannel returns an instance of the AlertChannelRaw struct with the +// provided Alert Channel integration type, name and raw data as an interface{}. +// +// NOTE: This function must be used by any Alert Channel type. +// +// Basic usage: Initialize a new EmailUserAlertChannel struct, then +// +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// emailAlertChan := api.NewAlertChannel("foo", +// api.EmailUserAlertChannelType, +// api.EmailUserData{ +// ChannelProps: api.EmailUserChannelProps{ +// Recipients: []string{"name@example.com"}, +// }, +// }, +// ) +// +// client.V2.AlertChannels.Create(emailAlertChan) +func NewAlertChannel(name string, iType alertChannelType, data interface{}) AlertChannelRaw { + return AlertChannelRaw{ + v2CommonIntegrationData: v2CommonIntegrationData{ + Name: name, + Type: iType.String(), + Enabled: 1, + }, + Data: data, + } +} + +// AlertChannel is an interface that helps us implement a few functions +// that any Alert Channel might use, there are some cases, like during +// Update, where we need to get the ID of the Alert Channel and its type, +// this will allow users to pass any Alert Channel that implements these +// methods +type AlertChannel interface { + ID() string + AlertChannelType() alertChannelType +} + +type alertChannelType int + +const ( + // NoneAlertChannelType type that defines a non-existing Alert Channel integration + NoneAlertChannelType alertChannelType = iota + EmailUserAlertChannelType + SlackChannelAlertChannelType + AwsS3AlertChannelType + CloudwatchEbAlertChannelType + DatadogAlertChannelType + WebhookAlertChannelType + VictorOpsAlertChannelType + CiscoSparkWebhookAlertChannelType + MicrosoftTeamsAlertChannelType + GcpPubSubAlertChannelType + SplunkHecAlertChannelType + ServiceNowRestAlertChannelType + NewRelicInsightsAlertChannelType + PagerDutyApiAlertChannelType + IbmQRadarAlertChannelType + JiraAlertChannelType +) + +// AlertChannelTypes is the list of available Alert Channel integration types +var AlertChannelTypes = map[alertChannelType]string{ + NoneAlertChannelType: "None", + EmailUserAlertChannelType: "EmailUser", + SlackChannelAlertChannelType: "SlackChannel", + AwsS3AlertChannelType: "AwsS3", + CloudwatchEbAlertChannelType: "CloudwatchEb", + DatadogAlertChannelType: "Datadog", + WebhookAlertChannelType: "Webhook", + VictorOpsAlertChannelType: "VictorOps", + CiscoSparkWebhookAlertChannelType: "CiscoSparkWebhook", + MicrosoftTeamsAlertChannelType: "MicrosoftTeams", + GcpPubSubAlertChannelType: "GcpPubsub", + SplunkHecAlertChannelType: "SplunkHec", + ServiceNowRestAlertChannelType: "ServiceNowRest", + NewRelicInsightsAlertChannelType: "NewRelicInsights", + PagerDutyApiAlertChannelType: "PagerDutyApi", + IbmQRadarAlertChannelType: "IbmQradar", + JiraAlertChannelType: "Jira", +} + +// String returns the string representation of a Alert Channel integration type +func (i alertChannelType) String() string { + return AlertChannelTypes[i] +} + +// FindAlertChannelType looks up inside the list of available alert channel types +// the matching type from the provided string, if none, returns NoneAlertChannelType +func FindAlertChannelType(alertChannel string) (alertChannelType, bool) { + for cType, cStr := range AlertChannelTypes { + if cStr == alertChannel { + return cType, true + } + } + return NoneAlertChannelType, false +} + +// List returns a list of Alert Channel integrations +func (svc *AlertChannelsService) List() (response AlertChannelsResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2AlertChannels, nil, &response) + return +} + +// Create creates a single Alert Channel integration +func (svc *AlertChannelsService) Create(integration AlertChannelRaw) ( + response AlertChannelResponse, + err error, +) { + err = svc.create(integration, &response) + return +} + +// Delete deletes a Alert Channel integration that matches the provided guid +func (svc *AlertChannelsService) Delete(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2AlertChannelFromGUID, guid), + nil, + nil, + ) +} + +// Test tests an Alert Channel integration that matches the provided guid +func (svc *AlertChannelsService) Test(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + apiPath := fmt.Sprintf(apiV2AlertChannelTest, guid) + return svc.client.RequestDecoder("POST", apiPath, nil, nil) +} + +// Get returns a raw response of the Alert Channel with the matching integration guid. +// +// To return a more specific Go struct of a Alert Channel integration, use the proper +// method such as GetEmailUser() where the function name is composed by: +// +// Get(guid) +// +// Where is the Alert Channel integration type. +func (svc *AlertChannelsService) Get(guid string, response interface{}) error { + return svc.get(guid, &response) +} + +type AlertChannelRaw struct { + v2CommonIntegrationData + Data interface{} `json:"data,omitempty"` +} + +func (alert AlertChannelRaw) GetData() any { + return alert.Data +} + +func (alert AlertChannelRaw) GetCommon() v2CommonIntegrationData { + return alert.v2CommonIntegrationData +} + +func (alert AlertChannelRaw) AlertChannelType() alertChannelType { + t, _ := FindAlertChannelType(alert.Type) + return t +} + +type AlertChannelResponse struct { + Data AlertChannelRaw `json:"data"` +} + +type AlertChannelsResponse struct { + Data []AlertChannelRaw `json:"data"` +} + +func (svc *AlertChannelsService) create(data interface{}, response interface{}) error { + return svc.client.RequestEncoderDecoder("POST", apiV2AlertChannels, data, response) +} + +func (svc *AlertChannelsService) get(guid string, response interface{}) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + apiPath := fmt.Sprintf(apiV2AlertChannelFromGUID, guid) + return svc.client.RequestDecoder("GET", apiPath, nil, response) +} + +func (svc *AlertChannelsService) update(guid string, data interface{}, response interface{}) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + apiPath := fmt.Sprintf(apiV2AlertChannelFromGUID, guid) + return svc.client.RequestEncoderDecoder("PATCH", apiPath, data, response) +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_cloudwatch.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_cloudwatch.go new file mode 100644 index 000000000..65ed9971d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_cloudwatch.go @@ -0,0 +1,51 @@ +// +// Author:: Vatasha White () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetCloudwatchEb gets a single instance of an AWS Cloudwatch alert channel +// with the corresponding integration guid +func (svc *AlertChannelsService) GetCloudwatchEb(guid string) ( + response CloudwatchEbAlertChannelResponseV2, err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateCloudwatchEb Update AWSCloudWatch updates a single instance of an AWS +// cloudwatch integration on the Lacework server +func (svc *AlertChannelsService) UpdateCloudwatchEb(data AlertChannel) ( + response CloudwatchEbAlertChannelResponseV2, err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type CloudwatchEbDataV2 struct { + EventBusArn string `json:"eventBusArn"` + IssueGrouping string `json:"issueGrouping,omitempty"` +} + +type CloudwatchEbAlertChannelV2 struct { + v2CommonIntegrationData + Data CloudwatchEbDataV2 `json:"data"` +} + +type CloudwatchEbAlertChannelResponseV2 struct { + Data CloudwatchEbAlertChannelV2 `json:"data"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_s3.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_s3.go new file mode 100644 index 000000000..9e955d51c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_s3.go @@ -0,0 +1,57 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAwsS3 gets a single AwsS3 alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetAwsS3(guid string) ( + response AwsS3AlertChannelResponseV2, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAwsS3 updates a single AwsS3 integration on the Lacework Server +func (svc *AlertChannelsService) UpdateAwsS3(data AlertChannel) ( + response AwsS3AlertChannelResponseV2, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsS3AlertChannelResponseV2 struct { + Data AwsS3AlertChannelV2 `json:"data"` +} + +type AwsS3AlertChannelV2 struct { + v2CommonIntegrationData + Data AwsS3DataV2 `json:"data"` +} + +type AwsS3DataV2 struct { + Credentials AwsS3Credentials `json:"s3CrossAccountCredentials"` +} + +type AwsS3Credentials struct { + RoleArn string `json:"roleArn"` + ExternalID string `json:"externalId"` + BucketArn string `json:"bucketArn"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_cisco_spark_webhook.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_cisco_spark_webhook.go new file mode 100644 index 000000000..3b0af6692 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_cisco_spark_webhook.go @@ -0,0 +1,49 @@ +// +// Author:: Vatasha White () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetCiscoSparkWebhook gets a single instance of a Cisco Spark webhook alert channel +// with the corresponding integration guid +func (svc *AlertChannelsService) GetCiscoSparkWebhook(guid string) ( + response CiscoSparkWebhookAlertChannelResponseV2, err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateCiscoSparkWebhook updates a single instance of Cisco Spark webhook integration on the Lacework server +func (svc *AlertChannelsService) UpdateCiscoSparkWebhook(data AlertChannel) ( + response CiscoSparkWebhookAlertChannelResponseV2, err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type CiscoSparkWebhookDataV2 struct { + Webhook string `json:"webhook"` +} + +type CiscoSparkWebhookAlertChannelV2 struct { + v2CommonIntegrationData + Data CiscoSparkWebhookDataV2 `json:"data"` +} + +type CiscoSparkWebhookAlertChannelResponseV2 struct { + Data CiscoSparkWebhookAlertChannelV2 `json:"data"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_email_user.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_email_user.go new file mode 100644 index 000000000..a68ac7c26 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_email_user.go @@ -0,0 +1,123 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/json" + "strings" +) + +// GetEmailUser gets a single EmailUser alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetEmailUser(guid string) ( + response EmailUserAlertChannelResponse, + err error, +) { + + // by default, expect the correct response, if not, try the workaround + err = svc.get(guid, &response) + if err == nil { + return + } + + // Workaround from APIv2 + // Bug: https://lacework.atlassian.net/browse/RAIN-20070 + // + // This means that the response.Data.Data.ChannelProps.Recipients is a 'string' + // instead of '[]string'. We will try to deserialize and cast to correct response + var getResponse emailUserGetAlertChannelResponse + err = svc.get(guid, &getResponse) + if err != nil { + return + } + + // convert GET response to a consistent response + response, err = convertGetEmailUserAlertChannelResponse(getResponse) + return +} + +// UpdateEmailUser updates a single EmailUser integration on the Lacework Server +func (svc *AlertChannelsService) UpdateEmailUser(data AlertChannel) ( + response EmailUserAlertChannelResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type EmailUserAlertChannelResponse struct { + Data EmailUserIntegration `json:"data"` +} + +type EmailUserIntegration struct { + v2CommonIntegrationData + Data EmailUserData `json:"data"` +} + +type EmailUserData struct { + ChannelProps EmailUserChannelProps `json:"channelProps"` + NotificationTypes struct { + Properties interface{} `json:"properties,omitempty"` + } `json:"notificationTypes"` +} + +type EmailUserChannelProps struct { + Recipients []string `json:"recipients"` +} + +// Workaround from APIv2 +// Bug: https://lacework.atlassian.net/browse/RAIN-20070 +type emailUserGetData struct { + ChannelProps struct { + Recipients interface{} `json:"recipients"` + } `json:"channelProps"` + NotificationTypes struct { + Properties interface{} `json:"properties,omitempty"` + } `json:"notificationTypes"` +} +type emailUserGetIntegration struct { + v2CommonIntegrationData + Data emailUserGetData `json:"data"` +} +type emailUserGetAlertChannelResponse struct { + Data emailUserGetIntegration `json:"data"` +} + +func convertGetEmailUserAlertChannelResponse( + res emailUserGetAlertChannelResponse) (EmailUserAlertChannelResponse, error) { + + recipientsString, ok := res.Data.Data.ChannelProps.Recipients.(string) + if ok { + // deserialize string + res.Data.Data.ChannelProps.Recipients = strings.Split(recipientsString, ",") + } + + return castEmailUserAlertChannelResponse(res) +} + +func castEmailUserAlertChannelResponse( + res interface{}) (r EmailUserAlertChannelResponse, err error) { + var j []byte + j, err = json.Marshal(res) + if err != nil { + return + } + err = json.Unmarshal(j, &r) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_gcp_pub_sub.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_gcp_pub_sub.go new file mode 100644 index 000000000..5f35c4703 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_gcp_pub_sub.go @@ -0,0 +1,56 @@ +// +// Author:: Vatasha White () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetGcpPubSub gets a single instance of a GCP Pub Sub alert channel with the corresponding guid +func (svc *AlertChannelsService) GetGcpPubSub(guid string) (response GcpPubSubAlertChannelResponseV2, err error) { + err = svc.get(guid, &response) + return +} + +// UpdateGcpPubSub updates a single instance of GCP Pub Sub integration on the Lacework server +func (svc *AlertChannelsService) UpdateGcpPubSub(data AlertChannel) ( + response GcpPubSubAlertChannelResponseV2, err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GcpPubSubDataV2 struct { + Credentials GcpPubSubCredentials `json:"credentials"` + IssueGrouping string `json:"issueGrouping"` + ProjectID string `json:"projectId"` + TopicID string `json:"topicId"` +} + +type GcpPubSubAlertChannelV2 struct { + v2CommonIntegrationData + Data GcpPubSubDataV2 `json:"data"` +} + +type GcpPubSubAlertChannelResponseV2 struct { + Data GcpPubSubAlertChannelV2 `json:"data"` +} + +type GcpPubSubCredentials struct { + ClientEmail string `json:"clientEmail"` + ClientID string `json:"clientId"` + PrivateKey string `json:"privateKey"` + PrivateKeyID string `json:"privateKeyId"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_ibm_qradar.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_ibm_qradar.go new file mode 100644 index 000000000..4c74ed0e1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_ibm_qradar.go @@ -0,0 +1,75 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "github.com/pkg/errors" + +const ( + // The list of valid inputs for QRadar Communication Type field + QRadarCommHttps qradarComm = "HTTPS" + QRadarCommHttpsSelfSigned qradarComm = "HTTPS Self Signed Cert" +) + +var qradarCommTypes = map[string]qradarComm{ + string(QRadarCommHttps): QRadarCommHttps, + string(QRadarCommHttpsSelfSigned): QRadarCommHttpsSelfSigned, +} + +// GetIbmQRadar gets a single IbmQRadar alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetIbmQRadar(guid string) ( + response IbmQRadarAlertChannelResponseV2, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateIbmQRadar updates a single IbmQRadar integration on the Lacework Server +func (svc *AlertChannelsService) UpdateIbmQRadar(data AlertChannel) ( + response IbmQRadarAlertChannelResponseV2, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type IbmQRadarAlertChannelResponseV2 struct { + Data IbmQRadarAlertChannelV2 `json:"data"` +} + +type IbmQRadarAlertChannelV2 struct { + v2CommonIntegrationData + Data IbmQRadarDataV2 `json:"data"` +} +type qradarComm string + +// QRadarComm returns the qradarComm type for the corresponding string input +func QRadarComm(site string) (qradarComm, error) { + if val, ok := qradarCommTypes[site]; ok { + return val, nil + } + return "", errors.Errorf("%v is not a valid QRadar communication type", site) +} + +type IbmQRadarDataV2 struct { + QRadarCommType qradarComm `json:"qradarCommType"` + HostURL string `json:"qradarHostUrl"` + HostPort int `json:"qradarHostPort,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_jira_cloud_server.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_jira_cloud_server.go new file mode 100644 index 000000000..ef3e2021e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_jira_cloud_server.go @@ -0,0 +1,110 @@ +// +// Author:: Vatasha White () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/base64" + "fmt" + "strings" +) + +type jiraIssueGrouping int + +const ( + NoneJiraIssueGrouping jiraIssueGrouping = iota + EventsJiraIssueGrouping + ResourcesJiraIssueGrouping +) + +var JiraIssueGroupings = map[jiraIssueGrouping]string{ + NoneJiraIssueGrouping: "", + EventsJiraIssueGrouping: "Events", + ResourcesJiraIssueGrouping: "Resources", +} + +var JiraIssueGroupingsSurvey = map[string]jiraIssueGrouping{ + "None": NoneJiraIssueGrouping, + "Events": EventsJiraIssueGrouping, + "Resources": ResourcesJiraIssueGrouping, +} + +func (i jiraIssueGrouping) String() string { + return JiraIssueGroupings[i] +} + +const ( + BidirectionalJiraConfiguration = "Bidirectional" + JiraCloudAlertType = "JIRA_CLOUD" + JiraServerAlertType = "JIRA_SERVER" +) + +// GetJira gets a single instance of a Jira Cloud or Jira Server alert channel with the corresponding guid +func (svc *AlertChannelsService) GetJira(guid string) (response JiraAlertChannelResponseV2, err error) { + err = svc.get(guid, &response) + return +} + +// UpdateJira updates a single instance of a Jira Cloud or Jira Server integration on the Lacework server +func (svc *AlertChannelsService) UpdateJira(data AlertChannel) (response JiraAlertChannelResponseV2, err error) { + err = svc.update(data.ID(), data, &response) + return +} + +type JiraDataV2 struct { + ApiToken string `json:"apiToken,omitempty"` // used for Jira Cloud + CustomTemplateFile string `json:"customTemplateFile,omitempty"` + IssueGrouping string `json:"issueGrouping,omitempty"` + IssueType string `json:"issueType"` + JiraType string `json:"jiraType"` + JiraUrl string `json:"jiraUrl"` + ProjectID string `json:"projectId"` + Username string `json:"username"` + Password string `json:"password,omitempty"` // used for Jira Server + Configuration string `json:"bidirectionalConfig,omitempty"` // used for bidirectional integration +} + +type JiraAlertChannelV2 struct { + v2CommonIntegrationData + Data JiraDataV2 `json:"data"` +} + +type JiraAlertChannelResponseV2 struct { + Data JiraAlertChannelV2 `json:"data"` +} + +func (jira *JiraDataV2) EncodeCustomTemplateFile(template string) { + encodedTemplate := base64.StdEncoding.EncodeToString([]byte(template)) + jira.CustomTemplateFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedTemplate) +} + +func (jira *JiraDataV2) DecodeCustomTemplateFile() (string, error) { + if len(jira.CustomTemplateFile) == 0 { + return "", nil + } + + var ( + b64 = strings.Split(jira.CustomTemplateFile, ",") + raw, err = base64.StdEncoding.DecodeString(b64[1]) + ) + if err != nil { + return "", err + } + + return string(raw), nil +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_microsoft_teams.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_microsoft_teams.go new file mode 100644 index 000000000..eccfcd649 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_microsoft_teams.go @@ -0,0 +1,49 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetMicrosoftTeams gets a single instance of a MicrosoftTeams alert channel +// with the corresponding integration guid +func (svc *AlertChannelsService) GetMicrosoftTeams(guid string) ( + response MicrosoftTeamsAlertChannelResponseV2, err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateMicrosoftTeams updates a single instance of a MicrosoftTeams integration on the Lacework server +func (svc *AlertChannelsService) UpdateMicrosoftTeams(data AlertChannel) ( + response MicrosoftTeamsAlertChannelResponseV2, err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type MicrosoftTeamsData struct { + TeamsURL string `json:"teamsUrl"` +} + +type MicrosoftTeamsAlertChannelV2 struct { + v2CommonIntegrationData + Data MicrosoftTeamsData `json:"data"` +} + +type MicrosoftTeamsAlertChannelResponseV2 struct { + Data MicrosoftTeamsAlertChannelV2 `json:"data"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_new_relic.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_new_relic.go new file mode 100644 index 000000000..d0866657c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_new_relic.go @@ -0,0 +1,52 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetNewRelicInsights gets a single NewRelic alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetNewRelicInsights(guid string) ( + response NewRelicInsightsAlertChannelResponseV2, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateNewRelicInsights updates a single NewRelic integration on the Lacework Server +func (svc *AlertChannelsService) UpdateNewRelicInsights(data AlertChannel) ( + response NewRelicInsightsAlertChannelResponseV2, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type NewRelicInsightsAlertChannelResponseV2 struct { + Data NewRelicInsightsAlertChannelV2 `json:"data"` +} + +type NewRelicInsightsAlertChannelV2 struct { + v2CommonIntegrationData + Data NewRelicInsightsDataV2 `json:"data"` +} + +type NewRelicInsightsDataV2 struct { + AccountID int `json:"accountId"` + InsertKey string `json:"insertKey"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_pager_duty.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_pager_duty.go new file mode 100644 index 000000000..c0b73a5b8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_pager_duty.go @@ -0,0 +1,51 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetPagerDutyApi gets a single PagerDuty alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetPagerDutyApi(guid string) ( + response PagerDutyApiAlertChannelResponseV2, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdatePagerDutyApi updates a single PagerDuty integration on the Lacework Server +func (svc *AlertChannelsService) UpdatePagerDutyApi(data AlertChannel) ( + response PagerDutyApiAlertChannelResponseV2, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type PagerDutyApiAlertChannelResponseV2 struct { + Data PagerDutyApiAlertChannelV2 `json:"data"` +} + +type PagerDutyApiAlertChannelV2 struct { + v2CommonIntegrationData + Data PagerDutyApiDataV2 `json:"data"` +} + +type PagerDutyApiDataV2 struct { + IntegrationKey string `json:"apiIntgKey"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_service_now_rest.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_service_now_rest.go new file mode 100644 index 000000000..95e6fcbdf --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_service_now_rest.go @@ -0,0 +1,82 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/base64" + "fmt" + "strings" +) + +// GetServiceNowRest gets a single ServiceNowRest alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetServiceNowRest(guid string) ( + response ServiceNowRestAlertChannelResponseV2, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateServiceNowRest updates a single ServiceNowRest integration on the Lacework Server +func (svc *AlertChannelsService) UpdateServiceNowRest(data AlertChannel) ( + response ServiceNowRestAlertChannelResponseV2, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +func (snow *ServiceNowRestDataV2) EncodeCustomTemplateFile(template string) { + encodedTemplate := base64.StdEncoding.EncodeToString([]byte(template)) + snow.CustomTemplateFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedTemplate) +} + +func (snow *ServiceNowRestDataV2) DecodeCustomTemplateFile() (string, error) { + if len(snow.CustomTemplateFile) == 0 { + return "", nil + } + + var ( + b64 = strings.Split(snow.CustomTemplateFile, ",") + raw, err = base64.StdEncoding.DecodeString(b64[1]) + ) + if err != nil { + return "", err + } + + return string(raw), nil +} + +type ServiceNowRestAlertChannelResponseV2 struct { + Data ServiceNowRestAlertChannelV2 `json:"data"` +} + +type ServiceNowRestAlertChannelV2 struct { + v2CommonIntegrationData + Data ServiceNowRestDataV2 `json:"data"` +} + +type ServiceNowRestDataV2 struct { + Username string `json:"userName"` + Password string `json:"password"` + InstanceURL string `json:"instanceUrl"` + CustomTemplateFile string `json:"customTemplateFile,omitempty"` + IssueGrouping string `json:"issueGrouping,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_slack_channel.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_slack_channel.go new file mode 100644 index 000000000..dec0668f3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_slack_channel.go @@ -0,0 +1,51 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetSlackChannel gets a single SlackChannel alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetSlackChannel(guid string) ( + response SlackChannelAlertChannelResponseV2, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateSlackChannel updates a single SlackChannel integration on the Lacework Server +func (svc *AlertChannelsService) UpdateSlackChannel(data AlertChannel) ( + response SlackChannelAlertChannelResponseV2, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type SlackChannelAlertChannelResponseV2 struct { + Data SlackChannelAlertChannelV2 `json:"data"` +} + +type SlackChannelAlertChannelV2 struct { + v2CommonIntegrationData + Data SlackChannelDataV2 `json:"data"` +} + +type SlackChannelDataV2 struct { + SlackUrl string `json:"slackUrl"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_splunk.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_splunk.go new file mode 100644 index 000000000..553e1d654 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_splunk.go @@ -0,0 +1,61 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetSplunkHec gets a single Splunk alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetSplunkHec(guid string) ( + response SplunkHecAlertChannelResponseV2, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateSplunkHec updates a single Splunk integration on the Lacework Server +func (svc *AlertChannelsService) UpdateSplunkHec(data AlertChannel) ( + response SplunkHecAlertChannelResponseV2, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type SplunkHecAlertChannelResponseV2 struct { + Data SplunkHecAlertChannelV2 `json:"data"` +} + +type SplunkHecAlertChannelV2 struct { + v2CommonIntegrationData + Data SplunkHecDataV2 `json:"data"` +} + +type SplunkHecDataV2 struct { + HecToken string `json:"hecToken"` + Channel string `json:"channel,omitempty"` + Host string `json:"host"` + Port int `json:"port"` + Ssl bool `json:"ssl"` + EventData SplunkHecEventDataV2 `json:"eventData"` +} + +type SplunkHecEventDataV2 struct { + Index string `json:"index"` + Source string `json:"source"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_victorops.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_victorops.go new file mode 100644 index 000000000..259ae234b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_victorops.go @@ -0,0 +1,51 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetVictorOps gets a single VictorOps alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetVictorOps(guid string) ( + response VictorOpsAlertChannelResponseV2, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateVictorOps updates a single VictorOps integration on the Lacework Server +func (svc *AlertChannelsService) UpdateVictorOps(data AlertChannel) ( + response VictorOpsAlertChannelResponseV2, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type VictorOpsAlertChannelResponseV2 struct { + Data VictorOpsAlertChannelV2 `json:"data"` +} + +type VictorOpsAlertChannelV2 struct { + v2CommonIntegrationData + Data VictorOpsDataV2 `json:"data"` +} + +type VictorOpsDataV2 struct { + Url string `json:"intgUrl"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_webhook.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_webhook.go new file mode 100644 index 000000000..c46b42902 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_channels_webhook.go @@ -0,0 +1,51 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetWebhook gets a single Webhook alert channel matching the +// provided integration guid +func (svc *AlertChannelsService) GetWebhook(guid string) ( + response WebhookAlertChannelResponseV2, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateWebhook updates a single Webhook integration on the Lacework Server +func (svc *AlertChannelsService) UpdateWebhook(data AlertChannel) ( + response WebhookAlertChannelResponseV2, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type WebhookAlertChannelResponseV2 struct { + Data WebhookAlertChannelV2 `json:"data"` +} + +type WebhookAlertChannelV2 struct { + v2CommonIntegrationData + Data WebhookDataV2 `json:"data"` +} + +type WebhookDataV2 struct { + WebhookUrl string `json:"webhookUrl"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_profiles.go b/vendor/github.com/lacework/go-sdk/api/alert_profiles.go new file mode 100644 index 000000000..3b74e8796 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_profiles.go @@ -0,0 +1,158 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +type v2alertProfilesService struct { + client *Client + Profiles *alertProfilesService + Templates *alertTemplatesService +} + +func NewV2AlertProfilesService(c *Client) *v2alertProfilesService { + return &v2alertProfilesService{c, + &alertProfilesService{c}, + &alertTemplatesService{c}, + } +} + +// AlertProfilesService is the service that interacts with +// the AlertProfiles schema from the Lacework APIv2 Server +type alertProfilesService struct { + client *Client +} + +// NewAlertProfile returns an instance of the AlertProfileConfig struct +// +// Basic usage: Initialize a new AlertProfileConfig struct, then +// +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// alertProfile := api.NewAlertProfile( +// "CUSTOM_PROFILE_NAME", +// "LW_HE_FILES_DEFAULT_PROFILE" +// []api.AlertTemplate{{ +// ... +// } +// }, +// ) +// +// client.V2.Alert.Profiles.Create(AlertProfile) +func NewAlertProfile(id string, extends string, alerts []AlertTemplate) AlertProfileConfig { + profile := AlertProfileConfig{ + Guid: id, + Extends: extends, + Alerts: alerts, + } + return profile +} + +// List returns a list of Alert Profiles +func (svc *alertProfilesService) List() (response AlertProfilesResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2AlertProfiles, nil, &response) + return +} + +// Create creates a single Alert Profile +func (svc *alertProfilesService) Create(profile AlertProfileConfig) ( + response AlertProfileResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("POST", apiV2AlertProfiles, profile, &response) + return +} + +// Delete deletes a Alert Profile that matches the provided guid +func (svc *alertProfilesService) Delete(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2AlertProfileFromGUID, guid), + nil, + nil, + ) +} + +// Update updates a single Alert Profile of the provided guid. +func (svc *alertProfilesService) Update(guid string, data []AlertTemplate) ( + response AlertProfileResponse, + err error, +) { + if guid == "" { + err = errors.New("specify a Guid") + return + } + body := alertTemplatesUpdate{data} + apiPath := fmt.Sprintf(apiV2AlertProfileFromGUID, guid) + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, body, &response) + return +} + +// Get returns a raw response of the Alert Profile with the matching guid. +func (svc *alertProfilesService) Get(guid string, response interface{}) error { + if guid == "" { + return errors.New("specify a Guid") + } + apiPath := fmt.Sprintf(apiV2AlertProfileFromGUID, guid) + return svc.client.RequestDecoder("GET", apiPath, nil, &response) +} + +type AlertProfile struct { + Guid string `json:"alertProfileId,omitempty" yaml:"alertProfileId,omitempty"` + Extends string `json:"extends" yaml:"extends"` + Fields []AlertProfileField `json:"fields,omitempty" yaml:"fields,omitempty"` + DescriptionKeys []AlertProfileDescriptionKeys `json:"descriptionKeys,omitempty" yaml:"descriptionKeys,omitempty"` + Alerts []AlertTemplate `json:"alerts" yaml:"alerts"` +} + +type AlertProfileConfig struct { + Guid string `json:"alertProfileId" yaml:"alertProfileId"` + Extends string `json:"extends" yaml:"extends"` + Alerts []AlertTemplate `json:"alerts" yaml:"alerts"` +} + +type AlertProfileField struct { + Name string `json:"name" yaml:"name"` +} + +type AlertProfileDescriptionKeys struct { + Name string `json:"name" yaml:"name"` + Spec string `json:"spec" yaml:"spec"` +} + +type AlertProfileResponse struct { + Data AlertProfile `json:"data" yaml:"data"` +} + +type AlertProfilesResponse struct { + Data []AlertProfile `json:"data" yaml:"data"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_rules.go b/vendor/github.com/lacework/go-sdk/api/alert_rules.go new file mode 100644 index 000000000..8cc28651f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_rules.go @@ -0,0 +1,314 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" +) + +// AlertRulesService is the service that interacts with +// the AlertRules schema from the Lacework APIv2 Server +type AlertRulesService struct { + client *Client +} + +// Valid inputs for AlertRule Source property +var AlertRuleSources = []string{"Agent", "AWS", "Azure", "GCP", "K8s", "OCI"} + +// Valid inputs for AlertRule Categories property +var AlertRuleCategories = []string{"Anomaly", "Policy", "Composite"} + +// Valid inputs for AlertRule SubCategories property +var AlertRuleSubCategories = []string{ + "Compliance", + "Application", + "Cloud Activity", + "File", + "Machine", + "User", + "Platform", + "Kubernetes Activity", + "Registry", + "SystemCall", + "Host Vulnerability", + "Container Vulnerability", + "Threat Intel", + // Deprecated eventCategory values + "App", + "Cloud", + "K8sActivity", +} + +type alertRuleSeverity int + +type AlertRuleSeverities []alertRuleSeverity + +const AlertRuleEventType = "Event" + +func (sevs AlertRuleSeverities) toInt() []int { + var res []int + for _, i := range sevs { + res = append(res, int(i)) + } + return res +} + +func (sevs AlertRuleSeverities) ToStringSlice() []string { + var res []string + for _, i := range sevs { + switch i { + case AlertRuleSeverityCritical: + res = append(res, "Critical") + case AlertRuleSeverityHigh: + res = append(res, "High") + case AlertRuleSeverityMedium: + res = append(res, "Medium") + case AlertRuleSeverityLow: + res = append(res, "Low") + case AlertRuleSeverityInfo: + res = append(res, "Info") + default: + continue + } + } + return res +} + +func NewAlertRuleSeverities(sevSlice []string) AlertRuleSeverities { + var res AlertRuleSeverities + for _, i := range sevSlice { + sev := convertSeverity(i) + if sev != AlertRuleSeverityUnknown { + res = append(res, sev) + } + } + return res +} + +func NewAlertRuleSeveritiesFromIntSlice(sevSlice []int) AlertRuleSeverities { + var res AlertRuleSeverities + for _, i := range sevSlice { + sev := convertSeverityInt(i) + if sev != AlertRuleSeverityUnknown { + res = append(res, sev) + } + } + return res +} + +func convertSeverity(sev string) alertRuleSeverity { + switch strings.ToLower(sev) { + case "critical": + return AlertRuleSeverityCritical + case "high": + return AlertRuleSeverityHigh + case "medium": + return AlertRuleSeverityMedium + case "low": + return AlertRuleSeverityLow + case "info": + return AlertRuleSeverityInfo + default: + return AlertRuleSeverityUnknown + } +} + +func convertSeverityInt(sev int) alertRuleSeverity { + switch sev { + case 1: + return AlertRuleSeverityCritical + case 2: + return AlertRuleSeverityHigh + case 3: + return AlertRuleSeverityMedium + case 4: + return AlertRuleSeverityLow + case 5: + return AlertRuleSeverityInfo + default: + return AlertRuleSeverityUnknown + } +} + +// Convert deprecated eventCatory values to subCategory values +func convertEventCategories(categories []string) []string { + var res []string + for _, c := range categories { + switch c { + case "App": + res = append(res, "Application") + case "Cloud": + res = append(res, "Cloud Activity") + case "K8sActivity": + res = append(res, "Kubernetes Activity") + default: + res = append(res, c) + } + } + return res +} + +const ( + AlertRuleSeverityCritical alertRuleSeverity = 1 + AlertRuleSeverityHigh alertRuleSeverity = 2 + AlertRuleSeverityMedium alertRuleSeverity = 3 + AlertRuleSeverityLow alertRuleSeverity = 4 + AlertRuleSeverityInfo alertRuleSeverity = 5 + AlertRuleSeverityUnknown alertRuleSeverity = 0 +) + +// NewAlertRule returns an instance of the AlertRule struct +// +// Basic usage: Initialize a new AlertRule struct, then +// +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// alertRule := api.NewAlertRule( +// "Foo", +// api.AlertRuleConfig{ +// Description: "My Alert Rule" +// Severities: api.AlertRuleSeverities{api.AlertRuleSeverityHigh, +// Channels: []string{"TECHALLY_000000000000AAAAAAAAAAAAAAAAAAAA"}, +// ResourceGroups: []string{"TECHALLY_111111111111AAAAAAAAAAAAAAAAAAAA"} +// }, +// }, +// ) +// +// client.V2.AlertRules.Create(alertRule) +func NewAlertRule(name string, rule AlertRuleConfig) AlertRule { + return AlertRule{ + Channels: rule.Channels, + Type: AlertRuleEventType, + Filter: AlertRuleFilter{ + Name: name, + Enabled: 1, + Description: rule.Description, + Severity: rule.Severities.toInt(), + ResourceGroups: rule.ResourceGroups, + AlertSubCategories: convertEventCategories(rule.AlertSubCategories), + AlertCategories: rule.AlertCategories, + AlertSources: rule.AlertSources, + }, + } +} + +func (rule AlertRuleFilter) Status() string { + if rule.Enabled == 1 { + return "Enabled" + } + return "Disabled" +} + +// List returns a list of Alert Rules +func (svc *AlertRulesService) List() (response AlertRulesResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2AlertRules, nil, &response) + return +} + +// Create creates a single Alert Rule +func (svc *AlertRulesService) Create(rule AlertRule) ( + response AlertRuleResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("POST", apiV2AlertRules, rule, &response) + return +} + +// Delete deletes a Alert Rule that matches the provided guid +func (svc *AlertRulesService) Delete(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2AlertRuleFromGUID, guid), + nil, + nil, + ) +} + +// Update updates a single Alert Rule of the provided guid. +func (svc *AlertRulesService) Update(data AlertRule) ( + response AlertRuleResponse, + err error, +) { + if data.Guid == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2AlertRuleFromGUID, data.Guid) + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) + return +} + +// Get returns a raw response of the Alert Rule with the matching guid. +func (svc *AlertRulesService) Get(guid string, response interface{}) error { + if guid == "" { + return errors.New("specify a Guid") + } + apiPath := fmt.Sprintf(apiV2AlertRuleFromGUID, guid) + return svc.client.RequestDecoder("GET", apiPath, nil, &response) +} + +type AlertRuleConfig struct { + Channels []string + Description string + Severities AlertRuleSeverities + ResourceGroups []string + AlertSubCategories []string + AlertCategories []string + AlertSources []string +} + +type AlertRule struct { + Guid string `json:"mcGuid,omitempty"` + Type string `json:"type"` + Channels []string `json:"intgGuidList"` + Filter AlertRuleFilter `json:"filters"` +} + +type AlertRuleFilter struct { + Name string `json:"name"` + Enabled int `json:"enabled"` + Description string `json:"description,omitempty"` + Severity []int `json:"severity"` + ResourceGroups []string `json:"resourceGroups"` + AlertSubCategories []string `json:"subCategory"` + AlertCategories []string `json:"category"` + AlertSources []string `json:"source,omitempty"` + CreatedOrUpdatedTime string `json:"createdOrUpdatedTime,omitempty"` + CreatedOrUpdatedBy string `json:"createdOrUpdatedBy,omitempty"` +} + +type AlertRuleResponse struct { + Data AlertRule `json:"data"` +} + +type AlertRulesResponse struct { + Data []AlertRule `json:"data"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_templates.go b/vendor/github.com/lacework/go-sdk/api/alert_templates.go new file mode 100644 index 000000000..306db66f3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alert_templates.go @@ -0,0 +1,69 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "fmt" + +type alertTemplatesService struct { + client *Client +} + +func (svc *alertTemplatesService) Create(alertProfileID string, template AlertTemplate) ( + response AlertProfileResponse, + err error, +) { + apiPath := fmt.Sprintf(apiV2AlertTemplates, alertProfileID) + err = svc.client.RequestEncoderDecoder("POST", apiPath, template, &response) + return +} + +func (svc *alertTemplatesService) Update(alertProfileID string, template AlertTemplate) ( + response AlertProfileResponse, + err error, +) { + body := alertTemplateUpdate{template.EventName, template.Description, template.Subject} + apiPath := fmt.Sprintf(apiV2AlertTemplatesFromGUID, alertProfileID, template.Name) + err = svc.client.RequestEncoderDecoder("POST", apiPath, body, &response) + return +} + +func (svc *alertTemplatesService) Delete(alertProfileID string, alertTemplateID string) ( + err error, +) { + apiPath := fmt.Sprintf(apiV2AlertTemplatesFromGUID, alertProfileID, alertTemplateID) + err = svc.client.RequestEncoderDecoder("POST", apiPath, nil, nil) + return +} + +type AlertTemplate struct { + Name string `json:"name" yaml:"name"` + EventName string `json:"eventName" yaml:"eventName"` + Description string `json:"description" yaml:"description"` + Subject string `json:"subject" yaml:"subject"` +} + +type alertTemplatesUpdate struct { + Alerts []AlertTemplate `json:"alerts" yaml:"alerts"` +} + +type alertTemplateUpdate struct { + EventName string `json:"eventName,omitempty" yaml:"eventName,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Subject string `json:"subject,omitempty" yaml:"subject,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts.go b/vendor/github.com/lacework/go-sdk/api/alerts.go new file mode 100644 index 000000000..3181b4acd --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts.go @@ -0,0 +1,181 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "sort" + "time" + + "github.com/lacework/go-sdk/lwseverity" + "github.com/lacework/go-sdk/lwtime" +) + +// AlertsService is a service that interacts with the Alerts +// endpoints from the Lacework Server +type AlertsService struct { + client *Client +} + +// ValidAlertSeverities is a list of all valid alert severities +var ValidAlertSeverities = []string{"critical", "high", "medium", "low", "info"} + +// ValidAlertStatuses is a list of all valid alert statuses +var ValidAlertStatuses = []string{"Open", "Closed"} + +type AlertInfo struct { + Subject string `json:"subject"` + Description string `json:"description"` +} + +type AlertSpec struct { + Profile string `json:"alertProfile"` + Name string `json:"name"` +} + +type AlertDerivedFields struct { + Category string `json:"category"` + SubCategory string `json:"sub_category"` + Source string `json:"source"` +} + +type Alert struct { + ID int `json:"alertId"` + Name string `json:"alertName"` + Type string `json:"alertType"` + Severity string `json:"severity"` + Info AlertInfo `json:"alertInfo"` + Spec AlertSpec `json:"alertSpec"` + Status string `json:"status"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + UpdateTime string `json:"lastUserUpdateTime"` + PolicyID string `json:"policyId"` + DerivedFields AlertDerivedFields `json:"derivedFields"` + Reachability string `json:"reachability"` +} + +func (a Alert) GetSeverity() string { + return a.Severity +} + +type Alerts []Alert + +// Sort by alert ID descending +func (a Alerts) SortByID() { + sort.Slice(a, func(i, j int) bool { + return a[i].ID > a[j].ID + }) +} + +// Sort by alert severity descending (from critical -> low) +func (a Alerts) SortBySeverity() { + lwseverity.SortSlice(a) +} + +type AlertsResponse struct { + Data Alerts `json:"data"` + Paging V2Pagination `json:"paging"` + + v2PageMetadata `json:"-"` +} + +// Fulfill Pageable interface (look at api/v2.go) +func (r AlertsResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *AlertsResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +func (svc *AlertsService) List() (response AlertsResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2Alerts, nil, &response) + return +} + +func (svc *AlertsService) ListAll() (response AlertsResponse, err error) { + response, err = svc.List() + if err != nil { + return + } + + var ( + all Alerts + pageOk bool + ) + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +func (svc *AlertsService) ListByTime(start, end time.Time) ( + response AlertsResponse, + err error, +) { + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf( + apiV2AlertsByTime, + start.UTC().Format(lwtime.RFC3339Milli), + end.UTC().Format(lwtime.RFC3339Milli), + ), + nil, + &response, + ) + return +} + +func (svc *AlertsService) ListAllByTime(start, end time.Time) ( + response AlertsResponse, + err error, +) { + response, err = svc.ListByTime(start, end) + if err != nil { + return + } + + var ( + all Alerts + pageOk bool + ) + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_close.go b/vendor/github.com/lacework/go-sdk/api/alerts_close.go new file mode 100644 index 000000000..ee1087ec1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts_close.go @@ -0,0 +1,82 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" +) + +type alertCloseReason int + +const ( + AlertCloseReasonOther alertCloseReason = iota + AlertCloseReasonFalsePositive + AlertCloseReasonNotEnoughInfo + AlertCloseReasonMalicious + AlertCloseReasonExpected + AlertCloseReasonExpectedBehavior +) + +// String returns the string representation of an Alert closure reason +func (i alertCloseReason) String() string { + return AlertCloseReasons[i] +} + +type alertCloseReasons map[alertCloseReason]string + +// AlertCloseReasons is the list of available Alert closure reasons +var AlertCloseReasons = alertCloseReasons{ + AlertCloseReasonOther: "Other", + AlertCloseReasonFalsePositive: "False positive", + AlertCloseReasonNotEnoughInfo: "Not enough information", + AlertCloseReasonMalicious: "Malicious and have resolution in place", + AlertCloseReasonExpected: "Expected because of routine testing", + AlertCloseReasonExpectedBehavior: "Expected Behavior", +} + +func (acr alertCloseReasons) GetOrderedReasonStrings() []string { + reasons := []string{} + for i := 0; i < len(acr); i++ { + reasons = append(reasons, acr[alertCloseReason(i)]) + } + return reasons +} + +type AlertCloseRequest struct { + AlertID int `json:"-"` + Reason int `json:"reason"` + Comment string `json:"comment,omitempty"` +} + +type AlertCloseResponse struct { + Message string `json:"message"` +} + +func (svc *AlertsService) Close(request AlertCloseRequest) ( + response AlertCloseResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder( + "POST", + fmt.Sprintf(apiV2AlertsClose, request.AlertID), + request, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_comment.go b/vendor/github.com/lacework/go-sdk/api/alerts_comment.go new file mode 100644 index 000000000..5e5e7091a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts_comment.go @@ -0,0 +1,50 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +type AlertsCommentRequest struct { + Comment string `json:"comment"` +} + +type AlertsCommentResponse struct { + Data AlertTimeline `json:"data"` +} + +func (svc *AlertsService) Comment(id int, comment string) ( + response AlertsCommentResponse, + err error, +) { + if comment == "" { + err = errors.New("alert comment must be provided") + return + } + err = svc.client.RequestEncoderDecoder( + "POST", + fmt.Sprintf(apiV2AlertsComment, id), + AlertsCommentRequest{Comment: comment}, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details.go b/vendor/github.com/lacework/go-sdk/api/alerts_details.go new file mode 100644 index 000000000..60bb20678 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts_details.go @@ -0,0 +1,110 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +type alertScope int + +const ( + AlertDetailsScope alertScope = iota + AlertInvestigationScope + AlertEventsScope + AlertRelatedAlertsScope + AlertIntegrationsScope + AlertTimelineScope +) + +var AlertScopes = map[alertScope]string{ + AlertDetailsScope: "Details", + AlertInvestigationScope: "Investigation", + AlertEventsScope: "Events", + AlertRelatedAlertsScope: "RelatedAlerts", + AlertIntegrationsScope: "Integrations", + AlertTimelineScope: "Timeline", +} + +func (i alertScope) String() string { + return AlertScopes[i] +} + +type AlertDetails struct { + Alert + EntityMap map[string]interface{} `json:"entityMap"` // @dhazekamp: this needs to be built out properly +} + +type AlertDetailsResponse struct { + Data AlertDetails `json:"data"` +} + +func (svc *AlertsService) Get(id int, scope alertScope) (interface{}, error) { + switch scope { + case AlertDetailsScope: + return svc.GetDetails(id) + case AlertInvestigationScope: + return svc.GetInvestigation(id) + case AlertEventsScope: + return svc.GetEvents(id) + case AlertRelatedAlertsScope: + return svc.GetRelatedAlerts(id) + case AlertIntegrationsScope: + return svc.GetIntegrations(id) + case AlertTimelineScope: + return svc.GetTimeline(id) + default: + return nil, errors.New(fmt.Sprintf("alert scope (%s) not recognized", scope)) + } +} + +func (svc *AlertsService) GetDetails(id int) ( + response AlertDetailsResponse, + err error, +) { + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf(apiV2AlertsDetails, id, AlertDetailsScope), + nil, + &response, + ) + return +} + +func (svc *AlertsService) Exists(id int) (bool, error) { + var response AlertDetailsResponse + err := svc.client.RequestDecoder( + "GET", + fmt.Sprintf(apiV2AlertsDetails, id, AlertDetailsScope), + nil, + &response, + ) + + if err == nil { + return true, nil + } + errResponse, ok := err.(*errorResponse) + if ok && errResponse.Response.StatusCode == http.StatusNotFound { + return false, nil + } + return false, err +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_events.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_events.go new file mode 100644 index 000000000..81ca38d14 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts_details_events.go @@ -0,0 +1,44 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" +) + +// @dhazekamp: is this the same structure as v2/Events? +// @dhazekamp: is this structure consistent across alerts (types) +type AlertEvent map[string]interface{} + +type AlertEventsResponse struct { + Data []AlertEvent `json:"data"` +} + +func (svc *AlertsService) GetEvents(id int) ( + response AlertEventsResponse, + err error, +) { + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf(apiV2AlertsDetails, id, AlertEventsScope), + nil, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_integrations.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_integrations.go new file mode 100644 index 000000000..dd2b79adb --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts_details_integrations.go @@ -0,0 +1,92 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" +) + +type AlertIntegrationChannelState struct { + Ok bool `json:"ok"` + LastUpdatedTime int `json:"lastUpdatedTime"` + LastSuccessfulTime int `json:"lastSuccessfulTime"` + Details map[string]interface{} `json:"details,omitempty"` +} + +type AlertIntegrationChannel struct { + IntgGuid string `json:"INTG_GUID,omitempty"` + Name string `json:"NAME"` + CreatedOrUpdatedTime string `json:"CREATED_OR_UPDATED_TIME,omitempty"` + CreatedOrUpdatedBy string `json:"CREATED_OR_UPDATED_BY,omitempty"` + Type string `json:"TYPE"` + Enabled int `json:"ENABLED"` + State AlertIntegrationChannelState `json:"STATE,omitempty"` + IsOrg int `json:"IS_ORG,omitempty"` + TypeName string `json:"TYPE_NAME,omitempty"` + EnvironmentGUID string `json:"ENV_GUID"` + Data map[string]interface{} `json:"DATA"` +} + +func (c AlertIntegrationChannel) Status() string { + if c.Enabled == 1 { + return "Enabled" + } + return "Disabled" +} + +func (c AlertIntegrationChannel) StateString() string { + if c.State.Ok { + return "Ok" + } + return "Pending" +} + +type AlertIntegrationContext struct { + ID string `json:"id"` + Link string `json:"link"` +} + +type AlertIntegration struct { + ID string `json:"alertIntegrationId"` + AlertID int `json:"alertId"` + Type string `json:"integrationType"` + Channel AlertIntegrationChannel `json:"alertChannel"` + Context AlertIntegrationContext `json:"integrationContext"` + IntgGUID string `json:"intgGuid"` + LastSyncTime string `json:"lastSyncTime"` + Status string `json:"status"` + Bidirectional bool `json:"isBidirectional"` +} + +type AlertIntegrationsResponse struct { + Data []AlertIntegration `json:"data"` +} + +func (svc *AlertsService) GetIntegrations(id int) ( + response AlertIntegrationsResponse, + err error, +) { + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf(apiV2AlertsDetails, id, AlertIntegrationsScope), + nil, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_investigation.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_investigation.go new file mode 100644 index 000000000..e4c9cf2da --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts_details_investigation.go @@ -0,0 +1,45 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" +) + +type AlertInvestigation struct { + Question string `json:"question"` + Answer string `json:"answer"` +} + +type AlertInvestigationResponse struct { + Data []AlertInvestigation `json:"data"` +} + +func (svc *AlertsService) GetInvestigation(id int) ( + response AlertInvestigationResponse, + err error, +) { + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf(apiV2AlertsDetails, id, AlertInvestigationScope), + nil, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_related.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_related.go new file mode 100644 index 000000000..2247fc989 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts_details_related.go @@ -0,0 +1,61 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "sort" +) + +type RelatedAlert struct { + ID string `json:"eventId"` + Name string `json:"eventName"` + Type string `json:"eventType"` + Severity string `json:"severity"` + Rank int `json:"rank"` + Info AlertInfo `json:"eventInfo"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` +} + +type RelatedAlerts []RelatedAlert + +type RelatedAlertsResponse struct { + Data RelatedAlerts `json:"data"` +} + +func (ra RelatedAlerts) SortRankDescending() RelatedAlerts { + sort.Slice(ra, func(i, j int) bool { + return ra[i].Rank > ra[j].Rank + }) + return ra +} + +func (svc *AlertsService) GetRelatedAlerts(id int) ( + response RelatedAlertsResponse, + err error, +) { + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf(apiV2AlertsDetails, id, AlertRelatedAlertsScope), + nil, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_timeline.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_timeline.go new file mode 100644 index 000000000..7363d4104 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts_details_timeline.go @@ -0,0 +1,75 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" +) + +type AlertTimelineMessage struct { + Format string `json:"format"` + Value string `json:"value"` +} + +type AlertTimelineUser struct { + UserGUID string `json:"userGuid"` + Name string `json:"username"` +} + +type AlertTimelineNewIntegrationContext struct { + AlertID int `json:"alertId"` + LastSyncTime string `json:"lastSyncTime"` + AlertIntegrationStatus string `json:"alertIntegrationStatus"` + Status string `json:"status"` + Bidirectional bool `json:"isBidirectional"` +} + +type AlertTimelineUpdateContext struct { + NewIntegration AlertTimelineNewIntegrationContext `json:"newIntegration"` +} + +type AlertTimeline struct { + ID int `json:"id"` + AlertID int `json:"alertId"` + EntryType string `json:"entryType"` + EntryAuthorType string `json:"entryAuthorType"` + IntgGUID string `json:"intgGuid"` + Message AlertTimelineMessage `json:"message"` + ExternalTime string `json:"externalTime"` + User AlertTimelineUser `json:"user"` + UpdateContext AlertTimelineUpdateContext `json:"updateContext"` + Channel AlertIntegrationChannel `json:"alertChannel"` +} + +type AlertTimelineResponse struct { + Data []AlertTimeline `json:"data"` +} + +func (svc *AlertsService) GetTimeline(id int) ( + response AlertTimelineResponse, + err error, +) { + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf(apiV2AlertsDetails, id, AlertTimelineScope), + nil, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_search.go b/vendor/github.com/lacework/go-sdk/api/alerts_search.go new file mode 100644 index 000000000..6c95691aa --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/alerts_search.go @@ -0,0 +1,68 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +type alertsFilterField string + +const ( + AlertsFilterFieldType alertsFilterField = "alertType" + AlertsFilterFieldSeverity alertsFilterField = "severity" + AlertsFilterFieldStatus alertsFilterField = "status" +) + +func (svc *AlertsService) Search(filter SearchFilter) ( + response AlertsResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder( + "POST", + apiV2AlertsSearch, + filter, + &response, + ) + return +} + +func (svc *AlertsService) SearchAll(filter SearchFilter) ( + response AlertsResponse, + err error, +) { + response, err = svc.Search(filter) + if err != nil { + return + } + + var ( + all Alerts + pageOk bool + ) + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/api.go b/vendor/github.com/lacework/go-sdk/api/api.go new file mode 100644 index 000000000..0cbe2be69 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/api.go @@ -0,0 +1,167 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "strings" +) + +const ( + // API v2 Endpoints + apiTokens = "v2/access/tokens" // Auth + + apiV2HoneyMetrics = "v2/metrics/honeycomb" + + apiV2UserProfile = "v2/UserProfile" + + apiV2ContainerRegistries = "v2/ContainerRegistries" + apiV2ContainerRegistryFromGUID = "v2/ContainerRegistries/%s" + + apiV2AlertChannels = "v2/AlertChannels" + apiV2AlertChannelFromGUID = "v2/AlertChannels/%s" + apiV2AlertChannelTest = "v2/AlertChannels/%s/test" + + apiV2AlertProfiles = "v2/AlertProfiles" + apiV2AlertProfileFromGUID = "v2/AlertProfiles/%s" + + apiV2AlertTemplates = "v2/AlertProfiles/%s/AlertTemplates" + apiV2AlertTemplatesFromGUID = "v2/AlertProfiles/%s/AlertTemplates/%s" + + apiV2AlertRules = "v2/AlertRules" + apiV2AlertRuleFromGUID = "v2/AlertRules/%s" + + apiV2CloudAccounts = "v2/CloudAccounts" + apiV2CloudAccountsWithParam = "v2/CloudAccounts/%s" + + apiV2AgentAccessTokens = "v2/AgentAccessTokens" + apiV2AgentAccessTokensSearch = "v2/AgentAccessTokens/search" + apiV2AgentAccessTokenFromID = "v2/AgentAccessTokens/%s" + + apiV2AgentInfoSearch = "v2/AgentInfo/search" + + apiV2PolicyExceptions = "v2/Exceptions?policyId=%s" + apiV2PolicyExceptionsFromExceptionID = "v2/Exceptions/%s?policyId=%s" + + apiV2InventorySearch = "v2/Inventory/search" + apiV2InventoryScanCsp = "v2/Inventory/scan?csp=%s" + + apiV2ComplianceEvaluationsSearch = "v2/Configs/ComplianceEvaluations/search" + + apiV2Components = "v2/Components?os=%s&arch=%s" + apiV2ComponentsVersions = "v2/Components/%d?os=%s&arch=%s" + apiV2ComponentsFetch = "v2/Components/Artifact/%d?os=%s&arch=%s&version=%s" + + apiV2ComponentDataRequest = "v2/ComponentData/requestUpload" + apiV2ComponentDataComplete = "v2/ComponentData/completeUpload" + + apiV2ConfigsAzure = "v2/Configs/AzureSubscriptions" + apiV2ConfigsAzureSubscriptions = "v2/Configs/AzureSubscriptions?tenantId=%s" + apiV2ConfigsGcp = "v2/Configs/GcpProjects" + apiV2ConfigsGcpProjects = "v2/Configs/GcpProjects?orgId=%s" + + apiV2FeatureFlags = "v2/FeatureFlags" + + apiV2Policies = "v2/Policies" + apiV2Queries = "v2/Queries" + apiV2QueriesExecute = "v2/Queries/execute" + apiV2QueriesValidate = "v2/Queries/validate" + + apiV2Reports = "v2/Reports?primaryQueryId=%s&format=%s&%s=%s" + apiV2ReportsSecondaryQuery = "v2/Reports?primaryQueryId=%s&secondaryQueryId=%s&format=%s&%s=%s" + + apiV2ReportDefinitions = "v2/ReportDefinitions" + apiV2ReportDefinitionsFromGUID = "v2/ReportDefinitions/%s" + apiV2ReportDefinitionsRevert = "v2/ReportDefinitions/%s?revertTo=%d" + apiV2ReportDefinitionsVersions = "v2/ReportDefinitions/%s?allVersions=true" + + apiV2ReportDistributions = "v2/ReportDistributions" + apiV2ReportDistributionsFromGUID = "v2/ReportDistributions/%s" + + apiV2ReportRules = "v2/ReportRules" + apiV2ReportRuleFromGUID = "v2/ReportRules/%s" + + apiV2ResourceGroups = "v2/ResourceGroups" + apiV2ResourceGroupsFromGUID = "v2/ResourceGroups/%s" + + apiV2Datasources = "v2/Datasources" + + apiV2DataExportRules = "v2/DataExportRules" + apiV2DataExportRulesFromGUID = "v2/DataExportRules/%s" + apiV2DataExportRulesSearch = "v2/DataExportRules/search" + + apiV2TeamMembers = "v2/TeamMembers" + apiV2TeamMembersFromGUID = "v2/TeamMembers/%s" + apiV2TeamMembersSearch = "v2/TeamMembers/search" + + apiV2VulnerabilitiesContainersSearch = "v2/Vulnerabilities/Containers/search" + apiV2VulnerabilitiesContainersScan = "v2/Vulnerabilities/Containers/scan" + apiV2VulnerabilitiesContainersScanStatus = "v2/Vulnerabilities/Containers/scan/%s" + apiV2VulnerabilitiesHostsSearch = "v2/Vulnerabilities/Hosts/search" + apiV2VulnerabilitiesSoftwarePackagesScan = "v2/Vulnerabilities/SoftwarePackages/scan" + + apiV2VulnerabilityExceptions = "v2/VulnerabilityExceptions" + apiV2VulnerabilityExceptionFromGUID = "v2/VulnerabilityExceptions/%s" + + apiV2EntitiesSearch = "v2/Entities/%s/search" + + apiV2Alerts = "v2/Alerts" + apiV2AlertsByTime = "v2/Alerts?startTime=%s&endTime=%s" + apiV2AlertsSearch = "v2/Alerts/search" + apiV2AlertsDetails = "v2/Alerts/%d?scope=%s" + apiV2AlertsComment = "v2/Alerts/%d/comment" + apiV2AlertsClose = "v2/Alerts/%d/close" + + apiV2OrganizationInfo = "v2/OrganizationInfo" + + apiSuppressions = "v2/suppressions/%s/allExceptions" + + apiRecommendations = "v2/recommendations/%s" + + apiV2MigrateGcpAtSes = "v2/migrateGcpAtSes" +) + +// WithApiV2 configures the client to use the API version 2 (/api/v2) +// for common API endpoints +// +// (no-op) DEPRECATED +func WithApiV2() Option { + return clientFunc(func(c *Client) error { + c.log.Warn("WithApiV2() has been deprecated, all clients now default to APIv2") + return nil + }) +} + +// ApiVersion returns the API client version +func (c *Client) ApiVersion() string { + return c.apiVersion +} + +// apiPath builds a path by using the current API version +func (c *Client) apiPath(p string) string { + if strings.HasPrefix(p, "/api/v") { + return p + } + + if strings.HasPrefix(p, "v1") || strings.HasPrefix(p, "v2") { + return fmt.Sprintf("/api/%s", p) + } + + return fmt.Sprintf("/api/%s/%s", c.apiVersion, p) +} diff --git a/vendor/github.com/lacework/go-sdk/api/auth.go b/vendor/github.com/lacework/go-sdk/api/auth.go new file mode 100644 index 000000000..4b02cb92b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/auth.go @@ -0,0 +1,165 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "time" + + "go.uber.org/zap" + + "github.com/lacework/go-sdk/internal/format" +) + +const DefaultTokenExpiryTime = 3600 + +// authConfig representing information like key_id, secret and token +// used for authenticating requests +type authConfig struct { + keyID string + secret string + token string + expiration int + expiresAt time.Time +} + +// WithApiKeys sets the key_id and secret used to generate API access tokens +func WithApiKeys(id, secret string) Option { + return clientFunc(func(c *Client) error { + if c.auth == nil { + c.auth = &authConfig{} + } + + c.log.Debug("setting up auth", + zap.String("key", id), + zap.String("secret", format.Secret(4, secret)), + ) + c.auth.keyID = id + c.auth.secret = secret + return nil + }) +} + +// WithTokenFromKeys sets the API access keys and triggers a new token generation +// NOTE: Order matters when using this option, use it at the end of a NewClient() func +func WithTokenFromKeys(id, secret string) Option { + return clientFunc(func(c *Client) error { + if c.auth == nil { + c.auth = &authConfig{} + } + + _, err := c.GenerateTokenWithKeys(id, secret) + return err + }) +} + +// WithToken sets the token used to authenticate the API requests +func WithToken(token string) Option { + return clientFunc(func(c *Client) error { + c.log.Debug("setting up auth", zap.String("token", format.Secret(4, token))) + c.auth.token = token + c.auth.expiresAt = time.Now().UTC().Add(DefaultTokenExpiryTime * time.Second) + return nil + }) +} + +// WithTokenAndExpiration sets the token used to authenticate the API requests +// and additionally configures the expiration of the token +func WithTokenAndExpiration(token string, expiration time.Time) Option { + return clientFunc(func(c *Client) error { + c.log.Debug("setting up auth", + zap.String("token", format.Secret(4, token)), + zap.Time("expires_at", expiration), + ) + c.auth.token = token + c.auth.expiresAt = expiration.UTC() + return nil + }) +} + +// WithExpirationTime configures the token expiration time +func WithExpirationTime(t int) Option { + return clientFunc(func(c *Client) error { + c.log.Debug("setting up auth", zap.Int("expiration", t)) + c.auth.expiration = t + c.auth.expiresAt = time.Now().UTC().Add(time.Duration(t) * time.Second) + return nil + }) +} + +func (c *Client) TokenExpired() bool { + return c.auth.expiresAt.Sub(time.Now().UTC()) <= 0 +} + +// GenerateToken generates a new access token +func (c *Client) GenerateToken() (*TokenData, error) { + if c.auth.keyID == "" || c.auth.secret == "" { + return nil, fmt.Errorf("unable to generate access token: auth keys missing") + } + + body, err := jsonReader(tokenRequest{c.auth.keyID, c.auth.expiration}) + if err != nil { + return nil, err + } + + request, err := c.NewRequest("POST", apiTokens, body) + if err != nil { + return nil, err + } + + var tokenData TokenData + res, err := c.DoDecoder(request, &tokenData) + if err != nil { + return nil, err + } + defer res.Body.Close() + + c.log.Debug("storing token", + zap.String("token", format.Secret(4, tokenData.Token)), + zap.Time("expires_at", tokenData.ExpiresAt), + ) + c.auth.token = tokenData.Token + c.auth.expiresAt = tokenData.ExpiresAt + if err != nil { + c.log.Error("failed to parse token expiration response", zap.Error(err)) + } + return &tokenData, nil +} + +// GenerateTokenWithKeys generates a new access token with the provided keys +func (c *Client) GenerateTokenWithKeys(keyID, secretKey string) (*TokenData, error) { + c.log.Debug("setting up auth", + zap.String("key", keyID), + zap.String("secret", format.Secret(4, secretKey)), + ) + c.auth.keyID = keyID + c.auth.secret = secretKey + return c.GenerateToken() +} + +type tokenRequest struct { + KeyID string `json:"keyId"` + ExpiryTime int `json:"expiryTime"` +} + +// APIv2 +type TokenData struct { + ExpiresAt time.Time `json:"expiresAt"` + Token string `json:"token"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/callbacks.go b/vendor/github.com/lacework/go-sdk/api/callbacks.go new file mode 100644 index 000000000..9239d6165 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/callbacks.go @@ -0,0 +1,39 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "net/http" + +type LifecycleCallbacks struct { + // RequestCallback is a function that will be executed after every client request + RequestCallback func(int, http.Header) error + + // TokenExpiredCallback is a function that the consumer can configure + // into the client so that it is run when the token expired + TokenExpiredCallback func() error +} + +// WithLifecycleCallbacks will configure the lifecycle callback functions +func WithLifecycleCallbacks(callbacks LifecycleCallbacks) Option { + return clientFunc(func(c *Client) error { + c.log.Debug("setting up client callbacks") + c.callbacks = callbacks + return nil + }) +} diff --git a/vendor/github.com/lacework/go-sdk/api/client.go b/vendor/github.com/lacework/go-sdk/api/client.go new file mode 100644 index 000000000..37d41b669 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/client.go @@ -0,0 +1,278 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "context" + "fmt" + "math/rand" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/lacework/go-sdk/lwdomain" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +const ( + defaultTimeout = 120 * time.Second + defaultTLSTimeout = 123 * time.Second +) + +type Client struct { + id string + account string + subaccount string + apiVersion string + baseURL *url.URL + auth *authConfig + c *http.Client + log *zap.Logger + headers map[string]string + callbacks LifecycleCallbacks + retries *backoff.ExponentialBackOff + + Policy *PolicyService + + V2 *V2Endpoints +} + +type Option interface { + apply(c *Client) error +} + +type clientFunc func(c *Client) error + +func (fn clientFunc) apply(c *Client) error { + return fn(c) +} + +// New generates a new Lacework API client +// +// Example of basic usage +// +// lacework, err := api.NewClient("demo") +// if err == nil { +// lacework.Integrations.List() +// } +func NewClient(account string, opts ...Option) (*Client, error) { + if account == "" { + return nil, errors.New("account cannot be empty") + } + + // verify if the user provided the full qualified domain name + if strings.Contains(account, ".lacework.net") { + domain, err := lwdomain.New(account) + if err != nil { + return nil, err + } + account = domain.String() + } + + baseURL, err := url.Parse(fmt.Sprintf("https://%s.lacework.net", account)) + if err != nil { + return nil, err + } + + defaultTransport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: defaultTransportDialContext(&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }), + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: defaultTLSTimeout, + ExpectContinueTimeout: 1 * time.Second, + } + + c := &Client{ + id: newID(), + account: account, + baseURL: baseURL, + apiVersion: "v2", + headers: map[string]string{ + "User-Agent": fmt.Sprintf("Go Client/%s", Version), + }, + auth: &authConfig{ + expiration: DefaultTokenExpiryTime, + }, + c: &http.Client{Timeout: defaultTimeout, + Transport: defaultTransport}, + } + + c.V2 = NewV2Endpoints(c) + + // init logger, this could change if a user calls api.WithLogLevel() + c.initLogger("") + + for _, opt := range opts { + if err := opt.apply(c); err != nil { + return c, err + } + } + + c.log.Info("api client created", + zap.String("url", c.baseURL.String()), + zap.String("version", c.apiVersion), + zap.String("log_level", c.log.Level().CapitalString()), + zap.Int("timeout", c.auth.expiration), + ) + return c, nil +} + +// CopyClient generates a copy of the provider Lacework API Go client +// +// Example of basic usage +// +// client, err := api.NewClient("demo") +// if err == nil { +// client.Integrations.List() +// } +// +// clientCopy, err := api.CopyClient(client, api.WithOrgAccess()) +// if err == nil { +// clientCopy.Integrations.List() +// } +func CopyClient(origin *Client, opts ...Option) (*Client, error) { + dest := new(Client) + *dest = *origin + + // no client should have the same ID + dest.id = newID() + + for _, opt := range opts { + if err := opt.apply(dest); err != nil { + return dest, err + } + } + return dest, nil +} + +// WithSubaccount sets a subaccount into an API client +func WithSubaccount(subaccount string) Option { + return clientFunc(func(c *Client) error { + if subaccount != "" { + c.log.Debug("setting up client", zap.String("subaccount", subaccount)) + c.subaccount = subaccount + c.log.Debug("setting up header", zap.String("Account-Name", subaccount)) + c.headers["Account-Name"] = subaccount + } + return nil + }) +} + +// WithTimeout changes the default client timeout +func WithTimeout(timeout time.Duration) Option { + return clientFunc(func(c *Client) error { + c.log.Debug("setting up client", zap.Reflect("timeout", timeout)) + c.c.Timeout = timeout + return nil + }) +} + +// WithRetries sets the retrying policy for API requests +func WithRetries(retries *backoff.ExponentialBackOff) Option { + return clientFunc(func(c *Client) error { + c.log.Debug("setting up retrying policy", zap.Reflect("retries", retries)) + c.retries = retries + return nil + }) +} + +// WithTransport changes the default transport to increase TLSHandshakeTimeout +func WithTransport(transport http.RoundTripper) Option { + return clientFunc(func(c *Client) error { + c.c.Transport = transport + return nil + }) +} + +// WithURL sets the base URL, this options is only available for test purposes +func WithURL(baseURL string) Option { + return clientFunc(func(c *Client) error { + u, err := url.Parse(baseURL) + if err != nil { + return err + } + + c.log.Debug("setting up client", zap.String("url", baseURL)) + c.baseURL = u + return nil + }) +} + +// WithHeader configures a HTTP Header to pass to every request +func WithHeader(header, value string) Option { + return clientFunc(func(c *Client) error { + if header != "" && value != "" { + c.log.Debug("setting up header", zap.String(header, value)) + c.headers[header] = value + } + return nil + }) +} + +// WithOrgAccess sets the Org-Access Header to access the organization level data sets +func WithOrgAccess() Option { + return clientFunc(func(c *Client) error { + c.log.Debug("setting up header", zap.String("Org-Access", "true")) + c.headers["Org-Access"] = "true" + return nil + }) +} + +// URL returns the base url configured +func (c *Client) URL() string { + return c.baseURL.String() +} + +// Retries returns the retrying policy configured +func (c *Client) Retries() *backoff.ExponentialBackOff { + return c.retries +} + +// ValidAuth verifies that the client has valid authentication +func (c *Client) ValidAuth() bool { + return c.auth.token != "" +} + +// OrgAccess check if the Org-Access header is set to 'true', if so, +// the client is configured to manage org level dataset +func (c *Client) OrgAccess() bool { + return c.headers["Org-Access"] == "true" +} + +// newID generates a new client id, this id is useful for logging purposes +// when there are more than one client running on the same machine +func newID() string { + now := time.Now().UTC().UnixNano() + seed := rand.New(rand.NewSource(now)) + return strconv.FormatInt(seed.Int63(), 16) +} + +func defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { + return dialer.DialContext +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts.go new file mode 100644 index 000000000..4ad222120 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts.go @@ -0,0 +1,296 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "time" + + "github.com/pkg/errors" + + "github.com/lacework/go-sdk/lwtime" +) + +// CloudAccountsService is the service that interacts with +// the CloudAccounts schema from the Lacework APIv2 Server +type CloudAccountsService struct { + client *Client +} + +// NewCloudAccount returns an instance of the CloudAccountRaw struct with the +// provided Cloud Account integration type, name and raw data as an interface{}. +// +// NOTE: This function must be used by any Cloud Account type. +// +// Basic usage: Initialize a new AwsIntegration struct, then use the new +// instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// awsCtSqs := api.NewCloudAccount("foo", +// api.AwsCtSqsCloudAccount, +// api.AwsCtSqsData{ +// QueueUrl: "https://sqs.us-west-2.amazonaws.com/123456789000/lw", +// Credentials: &api.AwsCtSqsCredentials { +// RoleArn: "arn:aws:XYZ", +// ExternalID: "1", +// }, +// }, +// ) +// +// client.V2.CloudAccount.Create(awsCtSqs) +func NewCloudAccount(name string, iType cloudAccountType, data interface{}) CloudAccountRaw { + return CloudAccountRaw{ + v2CommonIntegrationData: v2CommonIntegrationData{ + Name: name, + Type: iType.String(), + Enabled: 1, + }, + Data: data, + } +} + +// CloudAccount is an interface that helps us implement a few functions +// that any Cloud Account might use, there are some cases, like during +// Update, where we need to get the ID of the Cloud Account and its type, +// this will allow users to pass any Cloud Account that implements these +// methods +type CloudAccount interface { + ID() string + CloudAccountType() cloudAccountType +} + +type cloudAccountType int + +const ( + // type that defines a non-existing Cloud Account integration + NoneCloudAccount cloudAccountType = iota + AwsCfgCloudAccount + AwsCtSqsCloudAccount + AwsEksAuditCloudAccount + AwsSidekickCloudAccount + AwsSidekickOrgCloudAccount + AwsUsGovCfgCloudAccount + AwsUsGovCtSqsCloudAccount + AzureAdAlCloudAccount + AzureAlSeqCloudAccount + AzureCfgCloudAccount + GcpAtSesCloudAccount + GcpCfgCloudAccount + GcpGkeAuditCloudAccount + GcpSidekickCloudAccount + AzureSidekickCloudAccount + GcpAlPubSubCloudAccount + OciCfgCloudAccount +) + +// CloudAccountTypes is the list of available Cloud Account integration types +var CloudAccountTypes = map[cloudAccountType]string{ + NoneCloudAccount: "None", + AwsCfgCloudAccount: "AwsCfg", + AwsCtSqsCloudAccount: "AwsCtSqs", + AwsEksAuditCloudAccount: "AwsEksAudit", + AwsSidekickCloudAccount: "AwsSidekick", + AwsSidekickOrgCloudAccount: "AwsSidekickOrg", + AwsUsGovCfgCloudAccount: "AwsUsGovCfg", + AwsUsGovCtSqsCloudAccount: "AwsUsGovCtSqs", + AzureAdAlCloudAccount: "AzureAdAl", + AzureAlSeqCloudAccount: "AzureAlSeq", + AzureCfgCloudAccount: "AzureCfg", + GcpAtSesCloudAccount: "GcpAtSes", + GcpCfgCloudAccount: "GcpCfg", + GcpGkeAuditCloudAccount: "GcpGkeAudit", + GcpSidekickCloudAccount: "GcpSidekick", + AzureSidekickCloudAccount: "AzureSidekick", + GcpAlPubSubCloudAccount: "GcpAlPubSub", + OciCfgCloudAccount: "OciCfg", +} + +// String returns the string representation of a Cloud Account integration type +func (i cloudAccountType) String() string { + return CloudAccountTypes[i] +} + +// FindCloudAccountType looks up inside the list of available cloud account types +// the matching type from the provided string, if none, returns NoneCloudAccount +func FindCloudAccountType(cloudAccount string) (cloudAccountType, bool) { + for cType, cStr := range CloudAccountTypes { + if cStr == cloudAccount { + return cType, true + } + } + return NoneCloudAccount, false +} + +// List returns a list of Cloud Account integrations +func (svc *CloudAccountsService) List() (response CloudAccountsResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2CloudAccounts, nil, &response) + return +} + +// ListByType lists the cloud accounts from the provided type that are available +// on the Lacework Server +func (svc *CloudAccountsService) ListByType(caType cloudAccountType) (response CloudAccountsResponse, err error) { + err = svc.get(caType.String(), &response) + return +} + +// Create creates a single Cloud Account integration +func (svc *CloudAccountsService) Create(integration CloudAccountRaw) ( + response CloudAccountResponse, + err error, +) { + err = svc.create(integration, &response) + return +} + +// Delete deletes a Cloud Account integration that matches the provided guid +func (svc *CloudAccountsService) Delete(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2CloudAccountsWithParam, guid), + nil, + nil, + ) +} + +// Migrate marks a Cloud Account integration that matches the provided guid for migration +func (svc *CloudAccountsService) Migrate(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + data := MigrateRequestData{ + MigrateData{ + IntgGuid: guid, + Props: Props{ + Migrate: true, + MigrationTimestamp: time.Now(), + }, + }, + } + + return svc.client.RequestEncoderDecoder( + "PATCH", + apiV2MigrateGcpAtSes, + data, + nil, + ) +} + +// Get returns a raw response of the Cloud Account with the matching integration guid. +// +// To return a more specific Go struct of a Cloud Account integration, use the proper +// method such as GetAwsCtSqs() where the function name is composed by: +// +// Get(guid) +// +// Where is the Cloud Account integration type. +func (svc *CloudAccountsService) Get(guid string, response interface{}) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + return svc.get(guid, &response) +} + +type CloudAccountRaw struct { + v2CommonIntegrationData + Data interface{} `json:"data,omitempty"` +} + +func (cloud CloudAccountRaw) GetData() any { + return cloud.Data +} + +func (cloud CloudAccountRaw) GetCommon() v2CommonIntegrationData { + return cloud.v2CommonIntegrationData +} + +func (cloud CloudAccountRaw) CloudAccountType() cloudAccountType { + t, _ := FindCloudAccountType(cloud.Type) + return t +} + +type CloudAccountResponse struct { + Data CloudAccountRaw `json:"data"` +} + +type CloudAccountsResponse struct { + Data []CloudAccountRaw `json:"data"` +} + +type v2CommonIntegrationData struct { + IntgGuid string `json:"intgGuid,omitempty"` + Name string `json:"name"` + CreatedOrUpdatedTime string `json:"createdOrUpdatedTime,omitempty"` + CreatedOrUpdatedBy string `json:"createdOrUpdatedBy,omitempty"` + Type string `json:"type"` + Enabled int `json:"enabled"` + IsOrg int `json:"isOrg,omitempty"` + State *V2IntegrationState `json:"state,omitempty"` +} + +func (c v2CommonIntegrationData) ID() string { + return c.IntgGuid +} + +func (c v2CommonIntegrationData) Status() string { + if c.Enabled == 1 { + return "Enabled" + } + return "Disabled" +} + +func (c v2CommonIntegrationData) StateString() string { + if c.State != nil && c.State.Ok { + return "Ok" + } + return "Pending" +} + +type V2IntegrationState struct { + Ok bool `json:"ok"` + Details map[string]interface{} `json:"details"` + LastUpdatedTime lwtime.Epoch `json:"lastUpdatedTime"` + LastSuccessfulTime lwtime.Epoch `json:"lastSuccessfulTime"` +} + +func (svc *CloudAccountsService) create(data interface{}, response interface{}) error { + return svc.client.RequestEncoderDecoder("POST", apiV2CloudAccounts, data, response) +} + +func (svc *CloudAccountsService) get(param string, response interface{}) error { + apiPath := fmt.Sprintf(apiV2CloudAccountsWithParam, param) + return svc.client.RequestDecoder("GET", apiPath, nil, response) +} + +func (svc *CloudAccountsService) update(guid string, data interface{}, response interface{}) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + apiPath := fmt.Sprintf(apiV2CloudAccountsWithParam, guid) + return svc.client.RequestEncoderDecoder("PATCH", apiPath, data, response) +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_cfg.go new file mode 100644 index 000000000..e765e3bd8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_cfg.go @@ -0,0 +1,57 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAwsCfg gets a single AwsCfg integration matching the +// provided integration guid +func (svc *CloudAccountsService) GetAwsCfg(guid string) ( + response AwsCfgIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAwsCfg updates a single AwsCfg integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAwsCfg(data CloudAccount) ( + response AwsCfgIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsCfgIntegrationResponse struct { + Data AwsCfg `json:"data"` +} + +type AwsCfg struct { + v2CommonIntegrationData + Data AwsCfgData `json:"data"` +} + +type AwsCfgData struct { + Credentials AwsCfgCredentials `json:"crossAccountCredentials"` + AwsAccountID string `json:"awsAccountId,omitempty"` +} + +type AwsCfgCredentials struct { + RoleArn string `json:"roleArn"` + ExternalID string `json:"externalId"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_ct_sqs.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_ct_sqs.go new file mode 100644 index 000000000..c2f3ddabb --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_ct_sqs.go @@ -0,0 +1,92 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/base64" + "fmt" + "strings" +) + +// GetAwsCtSqs gets a single AwsCtSqs integration matching the +// provided integration guid +func (svc *CloudAccountsService) GetAwsCtSqs(guid string) ( + response AwsCtSqsIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAwsCtSqs updates a single AwsCtSqs integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAwsCtSqs(data CloudAccount) ( + response AwsCtSqsIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsCtSqsIntegrationResponse struct { + Data AwsCtSqsIntegration `json:"data"` +} + +type AwsCtSqsIntegration struct { + v2CommonIntegrationData + Data AwsCtSqsData `json:"data"` +} + +type AwsCtSqsData struct { + Credentials AwsCtSqsCredentials `json:"crossAccountCredentials"` + QueueUrl string `json:"queueUrl"` + AwsAccountID string `json:"awsAccountId,omitempty"` + + // This field must be a base64 encode with the following format: + // + // "data:application/json;name=i.json;base64,[ENCODING]" + // + // [ENCODING] is the the base64 encode, use EncodeAccountMappingFile() to encode a JSON mapping file + AccountMappingFile string `json:"accountMappingFile,omitempty"` +} + +type AwsCtSqsCredentials struct { + RoleArn string `json:"roleArn"` + ExternalID string `json:"externalId"` +} + +func (aws *AwsCtSqsData) EncodeAccountMappingFile(mapping []byte) { + encodedMappings := base64.StdEncoding.EncodeToString(mapping) + aws.AccountMappingFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedMappings) +} + +func (aws *AwsCtSqsData) DecodeAccountMappingFile() ([]byte, error) { + if len(aws.AccountMappingFile) == 0 { + return []byte{}, nil + } + + var ( + b64 = strings.Split(aws.AccountMappingFile, ",") + raw, err = base64.StdEncoding.DecodeString(b64[1]) + ) + if err != nil { + return []byte{}, err + } + + return raw, nil +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go new file mode 100644 index 000000000..a53cc6e7a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go @@ -0,0 +1,57 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAwsEksAudit gets a single AwsEksAudit integration matching the provided integration guid +func (svc *CloudAccountsService) GetAwsEksAudit(guid string) ( + response AwsEksAuditIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAwsEksAudit updates a single AwsEksAudit integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAwsEksAudit(data CloudAccount) ( + response AwsEksAuditIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsEksAuditIntegrationResponse struct { + Data AwsEksAuditIntegration `json:"data"` +} + +type AwsEksAuditIntegration struct { + v2CommonIntegrationData + Data AwsEksAuditData `json:"data"` +} + +type AwsEksAuditData struct { + Credentials AwsEksAuditCredentials `json:"crossAccountCredentials"` + SnsArn string `json:"snsArn"` + S3BucketArn string `json:"s3BucketArn,omitempty"` +} + +type AwsEksAuditCredentials struct { + RoleArn string `json:"roleArn"` + ExternalID string `json:"externalId"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_cfg.go new file mode 100644 index 000000000..17cb99e82 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_cfg.go @@ -0,0 +1,57 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAwsUsGovCfg gets a single AwsUsGovCfg integration matching the +// provided integration guid +func (svc *CloudAccountsService) GetAwsUsGovCfg(guid string) ( + response AwsUsGovCfgIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAwsUsGovCfg updates a single AwsUsGovCfg integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAwsUsGovCfg(data CloudAccount) ( + response AwsUsGovCfgIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsUsGovCfgIntegrationResponse struct { + Data AwsUsGovCfg `json:"data"` +} + +type AwsUsGovCfg struct { + v2CommonIntegrationData + Data AwsUsGovCfgData `json:"data"` +} + +type AwsUsGovCfgData struct { + Credentials AwsUsGovCfgCredentials `json:"accessKeyCredentials"` +} + +type AwsUsGovCfgCredentials struct { + AwsAccountID string `json:"accountId"` + AccessKeyID string `json:"accessKeyId"` + SecretAccessKey string `json:"secretAccessKey"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_ct.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_ct.go new file mode 100644 index 000000000..cd2dd1b9d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_ct.go @@ -0,0 +1,58 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAwsUsGovCtSqs gets a single AwsUsGovCtSqs integration matching the +// provided integration guid +func (svc *CloudAccountsService) GetAwsUsGovCtSqs(guid string) ( + response AwsUsGovCtSqsIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAwsUsGovCtSqs updates a single AwsUsGovCtSqs integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAwsUsGovCtSqs(data CloudAccount) ( + response AwsUsGovCtSqsIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsUsGovCtSqsIntegrationResponse struct { + Data AwsUsGovCtSqs `json:"data"` +} + +type AwsUsGovCtSqs struct { + v2CommonIntegrationData + Data AwsUsGovCtSqsData `json:"data"` +} + +type AwsUsGovCtSqsData struct { + Credentials AwsUsGovCtSqsCredentials `json:"accessKeyCredentials"` + QueueUrl string `json:"queueUrl"` +} + +type AwsUsGovCtSqsCredentials struct { + AwsAccountID string `json:"accountId"` + AccessKeyID string `json:"accessKeyId"` + SecretAccessKey string `json:"secretAccessKey"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick.go new file mode 100644 index 000000000..fbce51146 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick.go @@ -0,0 +1,84 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAwsSidekick gets a single AwsSidekick integration matching the provided integration guid +func (svc *CloudAccountsService) GetAwsSidekick(guid string) ( + response AwsSidekickResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// CreateAwsSidekick creates an AwsSidekick Cloud Account integration +func (svc *CloudAccountsService) CreateAwsSidekick(data CloudAccount) ( + response AwsSidekickResponse, + err error, +) { + err = svc.create(data, &response) + return +} + +// UpdateAwsSidekick updates a single AwsSidekick integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAwsSidekick(data CloudAccount) ( + response AwsSidekickResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsSidekickResponse struct { + Data AwsSidekick `json:"data"` +} + +type AwsSidekick struct { + v2CommonIntegrationData + awsSidekickToken `json:"serverToken"` + Data AwsSidekickData `json:"data"` +} + +type awsSidekickToken struct { + ServerToken string `json:"serverToken"` + Uri string `json:"uri"` +} + +type AwsSidekickData struct { + //QueryText represents an lql json string + QueryText string `json:"queryText,omitempty"` + + //ScanFrequency in hours, 24 == 24 hours + ScanFrequency int `json:"scanFrequency"` + + ScanContainers bool `json:"scanContainers"` + ScanHostVulnerabilities bool `json:"scanHostVulnerabilities"` + ScanMultiVolume bool `json:"scanMultiVolume"` + ScanStoppedInstances bool `json:"scanStoppedInstances"` + ScanShortLivedInstances bool `json:"scanShortLivedInstances"` + + AccountID string `json:"awsAccountId,omitempty"` + BucketArn string `json:"bucketArn,omitempty"` + CrossAccountCreds AwsSidekickCrossAccountCredentials `json:"crossAccountCredentials"` +} + +type AwsSidekickCrossAccountCredentials struct { + RoleArn string `json:"roleArn,omitempty"` + ExternalID string `json:"externalId,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick_org.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick_org.go new file mode 100644 index 000000000..3cb750ba5 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick_org.go @@ -0,0 +1,107 @@ +// +// Author:: Teddy Reed () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/base64" + "fmt" + "strings" +) + +// GetAwsSidekickOrg gets a single AwsSidekickOrg integration matching the provided integration guid +func (svc *CloudAccountsService) GetAwsSidekickOrg(guid string) ( + response AwsSidekickOrgResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// CreateAwsSidekickOrg creates an AwsSidekickOrg Cloud Account integration +func (svc *CloudAccountsService) CreateAwsSidekickOrg(data CloudAccount) ( + response AwsSidekickOrgResponse, + err error, +) { + err = svc.create(data, &response) + return +} + +// UpdateAwsSidekickOrg updates a single AwsSidekickOrg integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAwsSidekickOrg(data CloudAccount) ( + response AwsSidekickOrgResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsSidekickOrgResponse struct { + Data AwsSidekickOrg `json:"data"` +} + +type AwsSidekickOrg struct { + v2CommonIntegrationData + awsSidekickToken `json:"serverToken"` + Data AwsSidekickOrgData `json:"data"` +} + +type AwsSidekickOrgData struct { + //QueryText represents an lql json string + QueryText string `json:"queryText,omitempty"` + + //ScanFrequency in hours, 24 == 24 hours + ScanFrequency int `json:"scanFrequency"` + + ScanContainers bool `json:"scanContainers"` + ScanHostVulnerabilities bool `json:"scanHostVulnerabilities"` + ScanMultiVolume bool `json:"scanMultiVolume"` + ScanStoppedInstances bool `json:"scanStoppedInstances"` + ScanShortLivedInstances bool `json:"scanShortLivedInstances"` + + //Properties specific to the AWS organization integration type + ScanningAccount string `json:"scanningAccount"` + ManagementAccount string `json:"managementAccount,omitempty"` + MonitoredAccounts string `json:"monitoredAccounts"` + + AccountID string `json:"awsAccountId,omitempty"` + BucketArn string `json:"bucketArn,omitempty"` + CrossAccountCreds AwsSidekickCrossAccountCredentials `json:"crossAccountCredentials"` + AccountMappingFile string `json:"accountMappingFile,omitempty"` +} + +func (aws *AwsSidekickOrgData) EncodeAccountMappingFile(mapping []byte) { + encodedMappings := base64.StdEncoding.EncodeToString(mapping) + aws.AccountMappingFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedMappings) +} + +func (aws *AwsSidekickOrgData) DecodeAccountMappingFile() ([]byte, error) { + if len(aws.AccountMappingFile) == 0 { + return []byte{}, nil + } + + var ( + b64 = strings.Split(aws.AccountMappingFile, ",") + raw, err = base64.StdEncoding.DecodeString(b64[1]) + ) + if err != nil { + return []byte{}, err + } + + return raw, nil +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_al.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_al.go new file mode 100644 index 000000000..39fefefab --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_al.go @@ -0,0 +1,58 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAzureAlSeq gets a single AzureAlSeq integration matching the +// provided integration guid +func (svc *CloudAccountsService) GetAzureAlSeq(guid string) ( + response AzureAlSeqIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAzureAlSeq updates a single AzureAlSeq integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAzureAlSeq(data CloudAccount) ( + response AzureAlSeqIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AzureAlSeqIntegrationResponse struct { + Data AzureAlSeq `json:"data"` +} + +type AzureAlSeq struct { + v2CommonIntegrationData + Data AzureAlSeqData `json:"data"` +} + +type AzureAlSeqData struct { + Credentials AzureAlSeqCredentials `json:"credentials"` + TenantID string `json:"tenantId"` + QueueUrl string `json:"queueUrl"` +} + +type AzureAlSeqCredentials struct { + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_cfg.go new file mode 100644 index 000000000..4c23e03f3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_cfg.go @@ -0,0 +1,57 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAzureCfg gets a single AzureCfg integration matching the +// provided integration guid +func (svc *CloudAccountsService) GetAzureCfg(guid string) ( + response AzureCfgIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAzureCfg updates a single AzureCfg integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAzureCfg(data CloudAccount) ( + response AzureCfgIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AzureCfgIntegrationResponse struct { + Data AzureCfg `json:"data"` +} + +type AzureCfg struct { + v2CommonIntegrationData + Data AzureCfgData `json:"data"` +} + +type AzureCfgData struct { + Credentials AzureCfgCredentials `json:"credentials"` + TenantID string `json:"tenantId"` +} + +type AzureCfgCredentials struct { + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_ad_al.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_ad_al.go new file mode 100644 index 000000000..be008791c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_ad_al.go @@ -0,0 +1,59 @@ +// +// Author:: Rubinder Singh () +// Copyright:: Copyright 2024, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAzureAdAl gets a single AzureAdAl integration matching the +// provided integration guid +func (svc *CloudAccountsService) GetAzureAdAl(guid string) ( + response AzureAdAlIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAzureAdAl updates a single AzureAdAl integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAzureAdAl(data CloudAccount) ( + response AzureAdAlIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AzureAdAlIntegrationResponse struct { + Data AzureAdAl `json:"data"` +} + +type AzureAdAl struct { + v2CommonIntegrationData + Data AzureAdAlData `json:"data"` +} + +type AzureAdAlData struct { + Credentials AzureAdAlCredentials `json:"credentials"` + TenantID string `json:"tenantId"` + EventHubNamespace string `json:"eventHubNamespace"` + EventHubName string `json:"eventHubName"` +} + +type AzureAdAlCredentials struct { + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_sidekick.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_sidekick.go new file mode 100644 index 000000000..b82d7d4e5 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_sidekick.go @@ -0,0 +1,88 @@ +// +// Author:: Ao Zhang () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License.n +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +const ( + AzureSubscriptionIntegration string = "SUBSCRIPTION" + AzureTenantIntegration string = "TENANT" +) + +// GetAzureSidekick gets a single AzureSidekick integration matching the provided integration guid +func (svc *CloudAccountsService) GetAzureSidekick(guid string) ( + response AzureSidekickIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// CreateAzureSidekick creates an AzureSidekick Cloud Account integration +func (svc *CloudAccountsService) CreateAzureSidekick(data CloudAccount) ( + response AzureSidekickIntegrationResponse, + err error, +) { + err = svc.create(data, &response) + return +} + +// UpdateAzureSidekick updates a single AzureSidekick integration on the Lacework Server +func (svc *CloudAccountsService) UpdateAzureSidekick(data CloudAccount) ( + response AzureSidekickIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AzureSidekickIntegrationResponse struct { + Data V2AzureSidekickIntegration `json:"data"` +} + +type AzureSidekickToken struct { + ServerToken string `json:"serverToken"` + Uri string `json:"uri"` +} + +type V2AzureSidekickIntegration struct { + v2CommonIntegrationData + AzureSidekickToken `json:"serverToken"` + Data AzureSidekickData `json:"data"` +} + +type AzureSidekickData struct { + Credentials AzureSidekickCredentials `json:"credentials"` + IntegrationLevel string `json:"integrationLevel"` // SUBSCRIPTION or TENANT + ScanningSubscriptionId string `json:"scanningSubscriptionId"` + TenantId string `json:"tenantId"` + BlobContainerName string `json:"blobContainerName"` + ScanningResourceGroupName string `json:"scanningResourceGroupName"` + StorageAccountUrl string `json:"storageAccountUrl"` + SubscriptionsList string `json:"subscriptionsList,omitempty"` + QueryText string `json:"queryText,omitempty"` + ScanFrequency int `json:"scanFrequency"` // in hours + ScanContainers bool `json:"scanContainers"` + ScanHostVulnerabilities bool `json:"scanHostVulnerabilities"` + ScanMultiVolume bool `json:"scanMultiVolume"` + ScanStoppedInstances bool `json:"scanStoppedInstances"` +} + +type AzureSidekickCredentials struct { + ClientId string `json:"clientId"` + ClientSecret string `json:"clientSecret,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_al_pubsub.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_al_pubsub.go new file mode 100644 index 000000000..1013b052a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_al_pubsub.go @@ -0,0 +1,63 @@ +// +// Author:: David McTavish() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetGcpAlPubSub gets a single GcpAlPubSub integration matching the provided integration guid +func (svc *CloudAccountsService) GetGcpAlPubSub(guid string) ( + response GcpAlPubSubIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateGcpAtSes updates a single GcpAtSes integration on the Lacework Server +func (svc *CloudAccountsService) UpdateGcpAlPubSub(data CloudAccount) ( + response GcpAlPubSubIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GcpAlPubSubIntegrationResponse struct { + Data V2GcpAlPubSubIntegration `json:"data"` +} + +type V2GcpAlPubSubIntegration struct { + v2CommonIntegrationData + Data GcpAlPubSubSesData `json:"data"` +} + +type GcpAlPubSubSesData struct { + Credentials GcpAlPubSubCredentials `json:"credentials"` + IntegrationType string `json:"integrationType"` + // OrganizationId is optional for a project level integration, therefore we omit if empty + OrganizationID string `json:"organizationId,omitempty"` + ProjectID string `json:"projectId"` + SubscriptionName string `json:"subscriptionName"` + TopicID string `json:"topicId"` +} + +type GcpAlPubSubCredentials struct { + ClientID string `json:"clientId"` + ClientEmail string `json:"clientEmail"` + PrivateKeyID string `json:"privateKeyId"` + PrivateKey string `json:"privateKey,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_at.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_at.go new file mode 100644 index 000000000..5d326c18e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_at.go @@ -0,0 +1,77 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "time" + +// GetGcpAtSes gets a single GcpAtSes integration matching the provided integration guid +func (svc *CloudAccountsService) GetGcpAtSes(guid string) ( + response GcpAtSesIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateGcpAtSes updates a single GcpAtSes integration on the Lacework Server +func (svc *CloudAccountsService) UpdateGcpAtSes(data CloudAccount) ( + response GcpAtSesIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GcpAtSesIntegrationResponse struct { + Data V2GcpAtSesIntegration `json:"data"` +} + +type V2GcpAtSesIntegration struct { + v2CommonIntegrationData + Data GcpAtSesData `json:"data"` +} + +type GcpAtSesData struct { + Credentials GcpAtSesCredentials `json:"credentials"` + IDType string `json:"idType"` + // Either the org id or project id + ID string `json:"id"` + SubscriptionName string `json:"subscriptionName"` +} + +type GcpAtSesCredentials struct { + ClientID string `json:"clientId"` + ClientEmail string `json:"clientEmail"` + PrivateKeyID string `json:"privateKeyId,omitempty"` + PrivateKey string `json:"privateKey,omitempty"` +} + +type Props struct { + Migrate bool `json:"migrate"` + MigrationTimestamp time.Time `json:"migrationTimestamp"` +} + +type MigrateData struct { + IntgGuid string `json:"intgGuid"` + Props Props `json:"props"` +} + +type MigrateRequestData struct { + Data MigrateData `json:"data"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_cfg.go new file mode 100644 index 000000000..d1c3131c5 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_cfg.go @@ -0,0 +1,80 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// gcpResourceLevel determines Project or Organization level integration +type gcpResourceLevel int + +const ( + // Project level integration with GCP + GcpProjectIntegration gcpResourceLevel = iota + + // Organization level integration with GCP + GcpOrganizationIntegration +) + +var gcpResourceLevels = map[gcpResourceLevel]string{ + GcpProjectIntegration: "PROJECT", + GcpOrganizationIntegration: "ORGANIZATION", +} + +func (g gcpResourceLevel) String() string { + return gcpResourceLevels[g] +} + +// GetGcpCfg gets a single GcpCfg integration matching the provided integration guid +func (svc *CloudAccountsService) GetGcpCfg(guid string) ( + response GcpCfgIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateGcpCfg updates a single GcpCfg integration on the Lacework Server +func (svc *CloudAccountsService) UpdateGcpCfg(data CloudAccount) ( + response GcpCfgIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GcpCfgIntegrationResponse struct { + Data V2GcpCfgIntegration `json:"data"` +} + +type V2GcpCfgIntegration struct { + v2CommonIntegrationData + Data GcpCfgData `json:"data"` +} + +type GcpCfgData struct { + Credentials GcpCfgCredentials `json:"credentials"` + IDType string `json:"idType"` + // Either the org id or project id + ID string `json:"id"` +} + +type GcpCfgCredentials struct { + ClientID string `json:"clientId"` + ClientEmail string `json:"clientEmail"` + PrivateKeyID string `json:"privateKeyId,omitempty"` + PrivateKey string `json:"privateKey,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_gke_audit.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_gke_audit.go new file mode 100644 index 000000000..d5f3ab15c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_gke_audit.go @@ -0,0 +1,62 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetGcpGkeAudit gets a single GcpGkeAudit integration matching the provided integration guid +func (svc *CloudAccountsService) GetGcpGkeAudit(guid string) ( + response GcpGkeAuditIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateGcpGkeAudit updates a single GcpGkeAudit integration on the Lacework Server +func (svc *CloudAccountsService) UpdateGcpGkeAudit(data CloudAccount) ( + response GcpGkeAuditIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GcpGkeAuditIntegrationResponse struct { + Data GcpGkeAuditIntegration `json:"data"` +} + +type GcpGkeAuditIntegration struct { + v2CommonIntegrationData + Data GcpGkeAuditData `json:"data"` +} + +type GcpGkeAuditData struct { + Credentials GcpGkeAuditCredentials `json:"credentials"` + IntegrationType string `json:"integrationType"` + // OrganizationId is optional for a project level integration, therefore we omit if empty + OrganizationId string `json:"organizationId,omitempty"` + ProjectId string `json:"projectId"` + SubscriptionName string `json:"subscriptionName"` +} + +type GcpGkeAuditCredentials struct { + ClientId string `json:"clientId"` + ClientEmail string `json:"clientEmail"` + PrivateKeyId string `json:"privateKeyId"` + PrivateKey string `json:"privateKey"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_sidekick.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_sidekick.go new file mode 100644 index 000000000..fa5cd93ab --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_sidekick.go @@ -0,0 +1,115 @@ +// +// Author:: Ammar Ekbote() +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/base64" + "fmt" + "strings" +) + +// GetGcpSidekick gets a single GcpSidekick integration matching the provided integration guid +func (svc *CloudAccountsService) GetGcpSidekick(guid string) ( + response GcpSidekickIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// CreateGcpSidekick creates an GcpSidekick Cloud Account integration +func (svc *CloudAccountsService) CreateGcpSidekick(data CloudAccount) ( + response GcpSidekickIntegrationResponse, + err error, +) { + err = svc.create(data, &response) + return +} + +// UpdateGcpSidekick updates a single GcpSidekick integration on the Lacework Server +func (svc *CloudAccountsService) UpdateGcpSidekick(data CloudAccount) ( + response GcpSidekickIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GcpSidekickIntegrationResponse struct { + Data V2GcpSidekickIntegration `json:"data"` +} + +type GcpSidekickToken struct { + ServerToken string `json:"serverToken"` + Uri string `json:"uri"` +} + +type V2GcpSidekickIntegration struct { + v2CommonIntegrationData + GcpSidekickToken `json:"serverToken"` + Data GcpSidekickData `json:"data"` +} + +type GcpSidekickData struct { + Credentials GcpSidekickCredentials `json:"credentials"` + IDType string `json:"idType"` + // Either the org id or project id + ID string `json:"id"` + ScanningProjectId string `json:"scanningProjectId"` + SharedBucket string `json:"sharedBucketName"` + FilterList string `json:"filterList,omitempty"` + QueryText string `json:"queryText,omitempty"` + //ScanFrequency in hours, 24 == 24 hours + ScanFrequency int `json:"scanFrequency"` + ScanContainers bool `json:"scanContainers"` + ScanHostVulnerabilities bool `json:"scanHostVulnerabilities"` + ScanMultiVolume bool `json:"scanMultiVolume"` + ScanStoppedInstances bool `json:"scanStoppedInstances"` + + AccountMappingFile string `json:"accountMappingFile,omitempty"` +} + +type GcpSidekickCredentials struct { + ClientID string `json:"clientId"` + ClientEmail string `json:"clientEmail"` + PrivateKeyID string `json:"privateKeyId,omitempty"` + PrivateKey string `json:"privateKey,omitempty"` + TokenUri string `json:"tokenUri,omitempty"` +} + +func (gcp *GcpSidekickData) EncodeAccountMappingFile(mapping []byte) { + encodedMappings := base64.StdEncoding.EncodeToString(mapping) + gcp.AccountMappingFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedMappings) +} + +func (gcp *GcpSidekickData) DecodeAccountMappingFile() ([]byte, error) { + if len(gcp.AccountMappingFile) == 0 { + return []byte{}, nil + } + + var ( + b64 = strings.Split(gcp.AccountMappingFile, ",") + raw, err = base64.StdEncoding.DecodeString(b64[1]) + ) + if err != nil { + return []byte{}, err + } + + return raw, nil +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_oci_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_oci_cfg.go new file mode 100644 index 000000000..b42062d70 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_oci_cfg.go @@ -0,0 +1,60 @@ +// +// Author:: Kolbeinn Karlsson () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetOciCfg gets a single OciCfg integration matching the +// provided integration guid +func (svc *CloudAccountsService) GetOciCfg(guid string) ( + response OciCfgIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateOciCfg updates a single OciCfg integration on the Lacework Server +func (svc *CloudAccountsService) UpdateOciCfg(data CloudAccount) ( + response OciCfgIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type OciCfgIntegrationResponse struct { + Data OciCfg `json:"data"` +} + +type OciCfg struct { + v2CommonIntegrationData + Data OciCfgData `json:"data"` +} + +type OciCfgData struct { + Credentials OciCfgCredentials `json:"credentials"` + HomeRegion string `json:"homeRegion"` + TenantID string `json:"tenantId"` + TenantName string `json:"tenantName"` + UserOCID string `json:"userOcid"` +} + +type OciCfgCredentials struct { + Fingerprint string `json:"fingerprint"` + PrivateKey string `json:"privateKey,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/compliance_evaluations.go b/vendor/github.com/lacework/go-sdk/api/compliance_evaluations.go new file mode 100644 index 000000000..12f1e1aae --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/compliance_evaluations.go @@ -0,0 +1,68 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "time" + +type ComplianceEvaluationService struct { + client *Client +} + +type complianceEvaluationDataset string + +const AwsComplianceEvaluationDataset complianceEvaluationDataset = "AwsCompliance" + +// Search expects the response and the search filters +// +// e.g. +// +// var ( +// awsComplianceEvaluationSearchResponse api.ComplianceEvaluationAwsResponse +// filter = api.ComplianceEvaluationSearch{ +// SearchFilter: api.SearchFilter{ +// Filters: []api.Filter{{ +// Expression: "eq", +// Field: "resource", +// Value: arn:aws:s3:::my-bucket, +// }}, +// }, +// Dataset: api.AwsComplianceEvaluationDataset, +// } +// ) +// lacework.V2.ComplianceEvaluation.Search(&awsComplianceEvaluationSearchResponse, filters) +func (svc *ComplianceEvaluationService) Search(response interface{}, filters SearchableFilter) error { + return svc.client.RequestEncoderDecoder("POST", apiV2ComplianceEvaluationsSearch, filters, response) +} + +func (c *ComplianceEvaluationSearch) GetTimeFilter() *TimeFilter { + return c.TimeFilter +} + +func (c *ComplianceEvaluationSearch) SetStartTime(t *time.Time) { + c.TimeFilter.StartTime = t +} + +func (c *ComplianceEvaluationSearch) SetEndTime(t *time.Time) { + c.TimeFilter.EndTime = t +} + +type ComplianceEvaluationSearch struct { + SearchFilter + Dataset complianceEvaluationDataset `json:"dataset"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/compliance_evaluations_aws.go b/vendor/github.com/lacework/go-sdk/api/compliance_evaluations_aws.go new file mode 100644 index 000000000..f3106f2c1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/compliance_evaluations_aws.go @@ -0,0 +1,54 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "time" + +type ComplianceEvaluationAwsResponse struct { + Data []ComplianceEvaluationAws `json:"data"` + Paging V2Pagination `json:"paging"` +} + +func (r ComplianceEvaluationAwsResponse) GetDataLength() int { + return len(r.Data) +} + +func (r ComplianceEvaluationAwsResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *ComplianceEvaluationAwsResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +type ComplianceEvaluationAws struct { + Account struct { + AccountId string `json:"AccountId"` + AccountAlias string `json:"Account_Alias"` + } `json:"account"` + EvalType string `json:"evalType"` + Id string `json:"id"` + Reason string `json:"reason"` + Recommendation string `json:"recommendation"` + ReportTime time.Time `json:"reportTime"` + Resource string `json:"resource"` + Section string `json:"section"` + Severity string `json:"severity"` + Status string `json:"status"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/component_data.go b/vendor/github.com/lacework/go-sdk/api/component_data.go new file mode 100644 index 000000000..b434d69e5 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/component_data.go @@ -0,0 +1,208 @@ +package api + +import ( + "bytes" + "io" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/pkg/errors" +) + +type ComponentDataService struct { + client *Client +} + +const URL_TYPE_DEFAULT = "Default" +const URL_TYPE_SAST_TABLES = "SastTables" +const URL_TYPE_PROSAST = "ProSast" + +var URL_TYPES = []string{URL_TYPE_DEFAULT, URL_TYPE_SAST_TABLES, URL_TYPE_PROSAST} + +type ComponentDataInitialRequest struct { + Name string `json:"name"` + Tags []string `json:"tags"` + SupportedMethods []string `json:"supportedMethods"` + Documents []*DocumentSpec `json:"documents"` + UrlType string `json:"urlType"` +} + +type DocumentSpec struct { + Name string `json:"name"` + Size int64 `json:"size"` +} + +type ComponentDataInitialResponseRaw struct { + Data *ComponentDataInitialResponse `json:"data,omitempty"` +} + +type ComponentDataInitialResponse struct { + Guid string `json:"guid,omitempty"` + UploadMethods []*ComponentDataUploadMethod `json:"uploadMethods,omitempty"` +} + +type ComponentDataUploadMethod struct { + Method string `json:"method,omitempty"` + Info map[string]string `json:"info,omitempty"` +} + +type ComponentDataCompleteRequest struct { + UploadGuid string `json:"uploadGuid"` + UrlType string `json:"urlType"` +} + +type ComponentDataCompleteResponseRaw struct { + Data *ComponentDataCompleteResponse `json:"data,omitempty"` +} + +type ComponentDataCompleteResponse struct { + Guid string `json:"guid,omitempty"` +} + +func (svc *ComponentDataService) UploadFiles( + name string, tags []string, paths []string) (string, error) { + return svc.doUploadFiles(name, tags, paths, URL_TYPE_DEFAULT) +} + +func (svc *ComponentDataService) UploadSastTables( + name string, paths []string) (string, error) { + return svc.doUploadFiles(name, []string{"sast"}, paths, URL_TYPE_SAST_TABLES) +} + +func (svc *ComponentDataService) UploadProSast(name string, paths []string) (string, error) { + return svc.doUploadFiles(name, []string{"sast"}, paths, URL_TYPE_PROSAST) +} + +func (svc *ComponentDataService) doUploadFiles( + name string, tags []string, paths []string, urlType string) (string, error) { + var hasValidType = false + for _, validType := range URL_TYPES { + if urlType == validType { + hasValidType = true + break + } + } + if !hasValidType { + return "", errors.Errorf("Invalid URL type: (%s)", urlType) + } + initialRequest, err := buildComponentDataInitialRequest(name, tags, paths, urlType) + if err != nil { + return "", err + } + var initialResponse ComponentDataInitialResponseRaw + err = doWithExponentialBackoffWaiting(func() error { + return svc.client.RequestEncoderDecoder(http.MethodPost, + apiV2ComponentDataRequest, + initialRequest, + &initialResponse, + ) + }) + if err != nil { + return "", err + } + var chosenMethod *ComponentDataUploadMethod + for _, method := range initialResponse.Data.UploadMethods { + if method.Method == "AwsS3" { + chosenMethod = method + } + } + if chosenMethod == nil { + return "", errors.New("couldn't find a supported upload method in the upload request response") + } + for _, path := range paths { + err = doWithExponentialBackoffWaiting(func() error { + return svc.putFileToS3(path, chosenMethod.Info) + }) + if err != nil { + return "", err + } + } + completeRequest := ComponentDataCompleteRequest{ + UploadGuid: initialResponse.Data.Guid, + UrlType: urlType, + } + var completeResponse ComponentDataCompleteResponseRaw + err = doWithExponentialBackoffWaiting(func() error { + return svc.client.RequestEncoderDecoder(http.MethodPost, + apiV2ComponentDataComplete, + completeRequest, + &completeResponse, + ) + }) + if err != nil { + return "", err + } + if initialResponse.Data.Guid != completeResponse.Data.Guid { + return "", errors.New("expected the initial GUID and the one returned on completion to match") + } + return initialResponse.Data.Guid, nil +} + +func buildComponentDataInitialRequest( + name string, tags []string, paths []string, urlType string, +) (*ComponentDataInitialRequest, error) { + documents := make([]*DocumentSpec, 0, len(paths)) + for _, path := range paths { + info, err := os.Lstat(path) + if err != nil { + return nil, err + } + documents = append(documents, &DocumentSpec{ + Name: filepath.Base(path), + Size: info.Size(), + }) + } + return &ComponentDataInitialRequest{ + Name: name, + Tags: tags, + SupportedMethods: []string{"AwsS3"}, + Documents: documents, + UrlType: urlType, + }, nil +} + +func (svc *ComponentDataService) putFileToS3(path string, uploadUrls map[string]string) error { + contents, err := os.ReadFile(path) + if err != nil { + return err + } + req, err := http.NewRequest(http.MethodPut, uploadUrls[filepath.Base(path)], bytes.NewReader(contents)) + if err != nil { + return err + } + resp, err := svc.client.Do(req) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err == nil { + return errors.Errorf("Upload to S3 failed (%s): %s", resp.Status, body) + } + return errors.Errorf("Upload to S3 failed (%s)", resp.Status) + } + return nil +} + +func doWithExponentialBackoffWaiting(f func() error) error { + return DoWithExponentialBackoff(f, func(x int) { + time.Sleep(time.Duration(x) * time.Second) + }) +} + +func DoWithExponentialBackoff(f func() error, wait func(x int)) error { + err := f() + if err == nil { + return nil + } + for waitTime := 2; waitTime < 60; waitTime *= 2 { + wait(waitTime) + err := f() + if err == nil { + return nil + } + } + return err +} diff --git a/vendor/github.com/lacework/go-sdk/api/components.go b/vendor/github.com/lacework/go-sdk/api/components.go new file mode 100644 index 000000000..f80946bbd --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/components.go @@ -0,0 +1,81 @@ +package api + +import "fmt" + +type ComponentsService struct { + client *Client +} + +type ListComponentsResponse struct { + Data []LatestComponent `json:"data"` + Message string `json:"message"` +} + +type LatestComponent struct { + Components []LatestComponentVersion `json:"components"` +} + +type LatestComponentVersion struct { + Id int32 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Version string `json:"version"` + Size int64 `json:"size"` + ComponentType string `json:"type"` + Deprecated bool `json:"deprecated"` +} + +func (svc *ComponentsService) ListComponents(os string, arch string) (response ListComponentsResponse, err error) { + apiPath := fmt.Sprintf(apiV2Components, os, arch) + + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + + return +} + +type ListComponentVersionsResponse struct { + Data []ComponentVersions `json:"data"` +} + +type ComponentVersions struct { + Id int32 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Component_type string `json:"type"` + Deprecated bool `json:"deprecated"` + Versions []string `json:"versions"` +} + +func (svc *ComponentsService) ListComponentVersions(id int32, os string, arch string) ( + response ListComponentVersionsResponse, + err error) { + apiPath := fmt.Sprintf(apiV2ComponentsVersions, id, os, arch) + + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + + return +} + +type FetchComponentResponse struct { + Data []Artifact `json:"data"` +} + +type Artifact struct { + Id int32 `json:"id"` + Name string `json:"name"` + Version string `json:"version"` + Size int64 `json:"size"` + InstallMessage string `json:"installMessage"` + UpdateMessage string `json:"updateMessage"` + ArtifactUrl string `json:"artifact_url"` +} + +func (svc *ComponentsService) FetchComponentArtifact(id int32, os string, arch string, version string) ( + response FetchComponentResponse, + err error) { + apiPath := fmt.Sprintf(apiV2ComponentsFetch, id, os, arch, version) + + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries.go b/vendor/github.com/lacework/go-sdk/api/container_registries.go new file mode 100644 index 000000000..0e28c3e37 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries.go @@ -0,0 +1,275 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/fatih/structs" + "github.com/pkg/errors" +) + +// ContainerRegistriesService is the service that interacts with +// the ContainerRegistries schema from the Lacework APIv2 Server +type ContainerRegistriesService struct { + client *Client +} + +// NewContainerRegistry returns an instance of the ContainerRegistryRaw struct with the +// provided Container Registry integration type, name and raw data as an interface{}. +// +// NOTE: This function must be used by any Container Registry type. +// +// Basic usage: Initialize a new GhcrContainerRegistry integration struct, then +// +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// ghcr := api.NewContainerRegistry("foo", +// api.GhcrContainerRegistry, +// api.GhcrData{ +// Credentials: api.GhcrCredentials { +// Username: "bubu", +// Password: "supers3cret", +// Ssl: true, +// }, +// }, +// ) +// +// client.V2.ContainerRegistries.Create(ghcr) +func NewContainerRegistry(name string, regType containerRegistryType, data interface{}) ContainerRegistryRaw { + reg := ContainerRegistryRaw{ + v2CommonIntegrationData: v2CommonIntegrationData{ + Name: name, + Type: "ContVulnCfg", + Enabled: 1, + }, + } + + switch regType { + case GcpGarContainerRegistry: + reg.Data = verifyGcpGarContainerRegistry(data) + case GhcrContainerRegistry: + reg.Data = verifyGhcrContainerRegistry(data) + case InlineScannerContainerRegistry: + reg.Data = verifyInlineScannerContainerRegistry(data) + case ProxyScannerContainerRegistry: + reg.Data = verifyProxyScannerContainerRegistry(data) + case AwsEcrContainerRegistry: + reg.Data = verifyAwsEcrContainerRegistry(data) + case DockerhubContainerRegistry: + reg.Data = verifyDockerhubContainerRegistry(data) + case DockerhubV2ContainerRegistry: + reg.Data = verifyDockerhubV2ContainerRegistry(data) + case GcpGcrContainerRegistry: + reg.Data = verifyGcpGcrContainerRegistry(data) + default: + reg.Data = data + } + + return reg +} + +// ContainerRegistry is an interface that helps us implement a few functions +// that any Container Registry might use, there are some cases, like during +// Update, where we need to get the ID of the Container Registry and its type, +// this will allow users to pass any Container Registry that implements these +// methods +type ContainerRegistry interface { + ID() string + ContainerRegistryType() containerRegistryType +} + +type containerRegistryType int + +const ( + // type that defines a non-existing Container Registry integration + NoneContainerRegistry containerRegistryType = iota + GcpGarContainerRegistry + GhcrContainerRegistry + InlineScannerContainerRegistry + ProxyScannerContainerRegistry + AwsEcrContainerRegistry + DockerhubContainerRegistry + DockerhubV2ContainerRegistry + GcpGcrContainerRegistry +) + +// ContainerRegistryTypes is the list of available Container Registry integration types +var ContainerRegistryTypes = map[containerRegistryType]string{ + NoneContainerRegistry: "None", + GcpGarContainerRegistry: "GCP_GAR", + GhcrContainerRegistry: "GHCR", + InlineScannerContainerRegistry: "INLINE_SCANNER", + ProxyScannerContainerRegistry: "PROXY_SCANNER", + AwsEcrContainerRegistry: "AWS_ECR", + DockerhubContainerRegistry: "DOCKERHUB", + DockerhubV2ContainerRegistry: "V2_REGISTRY", + GcpGcrContainerRegistry: "GCP_GCR", +} + +// String returns the string representation of a Container Registry integration type +func (i containerRegistryType) String() string { + return ContainerRegistryTypes[i] +} + +// FindContainerRegistryType looks up inside the list of available container registry types +// the matching type from the provided string, if none, returns NoneContainerRegistry +func FindContainerRegistryType(containerRegistry string) (containerRegistryType, bool) { + for cType, cStr := range ContainerRegistryTypes { + if cStr == containerRegistry { + return cType, true + } + } + return NoneContainerRegistry, false +} + +// List returns a list of Container Registry integrations +func (svc *ContainerRegistriesService) List() (response ContainerRegistriesResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2ContainerRegistries, nil, &response) + return +} + +// Create creates a single Container Registry integration +func (svc *ContainerRegistriesService) Create(integration ContainerRegistryRaw) ( + response ContainerRegistryResponse, + err error, +) { + err = svc.create(integration, &response) + return +} + +// Delete deletes a Container Registry integration that matches the provided guid +func (svc *ContainerRegistriesService) Delete(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2ContainerRegistryFromGUID, guid), + nil, + nil, + ) +} + +// Get returns a raw response of the Container Registry with the matching integration guid. +// +// To return a more specific Go struct of a Container Registry integration, use the proper +// method such as GetGhcr() where the function name is composed by: +// +// Get(guid) +// +// Where is the Container Registry integration type. +func (svc *ContainerRegistriesService) Get(guid string, response interface{}) error { + return svc.get(guid, &response) +} + +type ContainerRegistryRaw struct { + v2CommonIntegrationData + Data interface{} `json:"data,omitempty"` + ServerToken *V2ServerToken `json:"serverToken,omitempty"` +} + +func (reg ContainerRegistryRaw) StateString() string { + switch reg.ContainerRegistryType() { + case InlineScannerContainerRegistry, ProxyScannerContainerRegistry: + return "Ok" + default: + return reg.v2CommonIntegrationData.StateString() + } +} + +type V2ServerToken struct { + ServerToken string `json:"serverToken"` + Uri string `json:"uri"` +} + +func (reg ContainerRegistryRaw) GetData() any { + return reg.Data +} + +func (reg ContainerRegistryRaw) GetCommon() v2CommonIntegrationData { + return reg.v2CommonIntegrationData +} + +func (reg ContainerRegistryRaw) ContainerRegistryType() containerRegistryType { + if casting, ok := reg.Data.(map[string]interface{}); ok { + if regType, exist := casting["registryType"]; exist { + t, _ := FindContainerRegistryType(regType.(string)) + return t + } + } + + m := structs.Map(reg.Data) + if regType, exist := m["RegistryType"]; exist { + t, _ := FindContainerRegistryType(regType.(string)) + return t + } + + return NoneContainerRegistry +} + +func (reg ContainerRegistryRaw) ContainerRegistryDomain() string { + if casting, ok := reg.Data.(map[string]interface{}); ok { + if domain, exist := casting["registryDomain"]; exist { + return domain.(string) + } + } + + if structs.IsStruct(reg.Data) { + m := structs.Map(reg.Data) + if domain, exist := m["RegistryDomain"]; exist { + return domain.(string) + } + } + return "" +} + +type ContainerRegistryResponse struct { + Data ContainerRegistryRaw `json:"data"` +} + +type ContainerRegistriesResponse struct { + Data []ContainerRegistryRaw `json:"data"` +} + +func (svc *ContainerRegistriesService) create(data interface{}, response interface{}) error { + return svc.client.RequestEncoderDecoder("POST", apiV2ContainerRegistries, data, response) +} + +func (svc *ContainerRegistriesService) get(guid string, response interface{}) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + apiPath := fmt.Sprintf(apiV2ContainerRegistryFromGUID, guid) + return svc.client.RequestDecoder("GET", apiPath, nil, response) +} + +func (svc *ContainerRegistriesService) update(guid string, data interface{}, response interface{}) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + apiPath := fmt.Sprintf(apiV2ContainerRegistryFromGUID, guid) + return svc.client.RequestEncoderDecoder("PATCH", apiPath, data, response) +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_access_key.go b/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_access_key.go new file mode 100644 index 000000000..bd392f080 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_access_key.go @@ -0,0 +1,98 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +type ecrAuthType int + +const ( + AwsEcrIAM ecrAuthType = iota + AwsEcrAccessKey +) + +// AwsEcrAuthTypes is the list of available ECR auth types +var AwsEcrAuthTypes = map[ecrAuthType]string{ + AwsEcrIAM: "AWS_IAM", + AwsEcrAccessKey: "AWS_ACCESS_KEY", +} + +// String returns the string representation of an ECR auth type +func (i ecrAuthType) String() string { + return AwsEcrAuthTypes[i] +} + +// GetAwsEcrAccessKey gets a single AwsEcrAccessKey integration with access key credentials matching the +// provided integration guid +func (svc *ContainerRegistriesService) GetAwsEcrAccessKey(guid string) ( + response AwsEcrAccessKeyIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAwsEcrAccessKey updates a single AwsEcrAccessKey integration with access key credential on the Lacework Server +func (svc *ContainerRegistriesService) UpdateAwsEcrAccessKey(data ContainerRegistry) ( + response AwsEcrAccessKeyIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsEcrAccessKeyIntegrationResponse struct { + Data AwsEcrIntegration `json:"data"` +} + +type AwsEcrIntegration struct { + v2CommonIntegrationData + Data AwsEcrAccessKeyData `json:"data"` +} + +type AwsEcrAccessKeyData struct { + AccessKeyCredentials AwsEcrAccessKeyCredentials `json:"accessKeyCredentials,omitempty"` + RegistryDomain string `json:"registryDomain"` + LimitByTag []string `json:"limitByTag,omitempty"` + LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` + LimitByRep []string `json:"limitByRep,omitempty"` + LimitNumImg int `json:"limitNumImg"` + NonOSPackageEval bool `json:"nonOsPackageEval"` + AwsAuthType string `json:"awsAuthType"` + RegistryType string `json:"registryType"` +} + +func verifyAwsEcrContainerRegistry(data interface{}) interface{} { + if ecr, ok := data.(AwsEcrAccessKeyData); ok { + ecr.RegistryType = AwsEcrContainerRegistry.String() + ecr.AwsAuthType = AwsEcrAccessKey.String() + return ecr + } + + if ecr, ok := data.(AwsEcrIamRoleData); ok { + ecr.RegistryType = AwsEcrContainerRegistry.String() + ecr.AwsAuthType = AwsEcrIAM.String() + return ecr + } + + return data +} + +type AwsEcrAccessKeyCredentials struct { + AccessKeyID string `json:"accessKeyId,omitempty"` + SecretAccessKey string `json:"secretAccessKey,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_iam_role.go b/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_iam_role.go new file mode 100644 index 000000000..e685e9abc --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_iam_role.go @@ -0,0 +1,69 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetAwsEcrIamRole gets a single AwsEcr with Iam Role credentials integration matching the +// provided integration guid +func (svc *ContainerRegistriesService) GetAwsEcrIamRole(guid string) ( + response AwsEcrIamRoleIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateAwsEcrIamRole updates a single AwsEcr with Iam Role credentials integration on the Lacework Server +func (svc *ContainerRegistriesService) UpdateAwsEcrIamRole(data ContainerRegistry) ( + response AwsEcrIamRoleIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type AwsEcrIamRoleIntegrationResponse struct { + Data AwsEcrIamRoleIntegration `json:"data"` +} + +type AwsEcrIamRoleIntegration struct { + v2CommonIntegrationData + Data AwsEcrIamRoleData `json:"data"` +} + +func (reg AwsEcrIamRoleIntegration) ContainerRegistryType() containerRegistryType { + t, _ := FindContainerRegistryType(reg.Data.RegistryType) + return t +} + +type AwsEcrIamRoleData struct { + CrossAccountCredentials AwsEcrCrossAccountCredentials `json:"crossAccountCredentials,omitempty"` + RegistryDomain string `json:"registryDomain"` + RegistryType string `json:"registryType"` + LimitByTag []string `json:"limitByTag,omitempty"` + LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` + LimitByRep []string `json:"limitByRep,omitempty"` + LimitNumImg int `json:"limitNumImg"` + NonOSPackageEval bool `json:"nonOsPackageEval"` + AwsAuthType string `json:"awsAuthType"` +} + +type AwsEcrCrossAccountCredentials struct { + RoleArn string `json:"roleArn,omitempty"` + ExternalID string `json:"externalId,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub.go b/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub.go new file mode 100644 index 000000000..7bf6c467d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub.go @@ -0,0 +1,77 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetDockerhub gets a single Dockerhub integration matching the +// provided integration guid +func (svc *ContainerRegistriesService) GetDockerhub(guid string) ( + response DockerhubIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateDockerhub updates a single Dockerhub integration on the Lacework Server +func (svc *ContainerRegistriesService) UpdateDockerhub(data ContainerRegistry) ( + response DockerhubIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type DockerhubIntegrationResponse struct { + Data DockerhubIntegration `json:"data"` +} + +type DockerhubIntegration struct { + v2CommonIntegrationData + Data DockerhubData `json:"data"` +} + +func (reg DockerhubIntegration) ContainerRegistryType() containerRegistryType { + t, _ := FindContainerRegistryType(reg.Data.RegistryType) + return t +} + +type DockerhubData struct { + Credentials DockerhubCredentials `json:"credentials"` + RegistryDomain string `json:"registryDomain"` // always "index.docker.io" + RegistryType string `json:"registryType"` // always "DOCKERHUB" + LimitByTag []string `json:"limitByTag,omitempty"` + LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` + LimitByRep []string `json:"limitByRep,omitempty"` + LimitNumImg int `json:"limitNumImg"` + NonOSPackageEval bool `json:"nonOsPackageEval"` +} + +func verifyDockerhubContainerRegistry(data interface{}) interface{} { + if hub, ok := data.(DockerhubData); ok { + hub.RegistryType = DockerhubContainerRegistry.String() + hub.RegistryDomain = "index.docker.io" + return hub + } + return data +} + +type DockerhubCredentials struct { + Username string `json:"username"` + Password string `json:"password"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub_v2.go b/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub_v2.go new file mode 100644 index 000000000..699bdd944 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub_v2.go @@ -0,0 +1,76 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetDockerhubV2 gets a single DockerhubV2 integration matching the +// provided integration guid +func (svc *ContainerRegistriesService) GetDockerhubV2(guid string) ( + response DockerhubV2IntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateDockerhubV2 updates a single DockerhubV2 integration on the Lacework Server +func (svc *ContainerRegistriesService) UpdateDockerhubV2(data ContainerRegistry) ( + response DockerhubV2IntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type DockerhubV2IntegrationResponse struct { + Data DockerhubV2Integration `json:"data"` +} + +type DockerhubV2Integration struct { + v2CommonIntegrationData + Data DockerhubV2Data `json:"data"` +} + +func (reg DockerhubV2Integration) ContainerRegistryType() containerRegistryType { + t, _ := FindContainerRegistryType(reg.Data.RegistryType) + return t +} + +type DockerhubV2Data struct { + Credentials DockerhubV2Credentials `json:"credentials"` + RegistryDomain string `json:"registryDomain"` + RegistryType string `json:"registryType"` + RegistryNotifications *bool `json:"registryNotifications,omitempty"` + LimitByTag []string `json:"limitByTag,omitempty"` + LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` + NonOSPackageEval bool `json:"nonOsPackageEval"` +} + +func verifyDockerhubV2ContainerRegistry(data interface{}) interface{} { + if ecr, ok := data.(DockerhubV2Data); ok { + ecr.RegistryType = DockerhubV2ContainerRegistry.String() + return ecr + } + return data +} + +type DockerhubV2Credentials struct { + Username string `json:"username"` + Password string `json:"password"` + SSL bool `json:"ssl"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gar.go b/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gar.go new file mode 100644 index 000000000..4ef93c6c8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gar.go @@ -0,0 +1,81 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetGcpGar gets a single GcpGar integration matching the +// provided integration guid +func (svc *ContainerRegistriesService) GetGcpGar(guid string) ( + response GcpGarIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateGcpGar updates a single GcpGar integration on the Lacework Server +func (svc *ContainerRegistriesService) UpdateGcpGar(data ContainerRegistry) ( + response GcpGarIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GcpGarIntegrationResponse struct { + Data GcpGarIntegration `json:"data"` +} + +type GcpGarIntegration struct { + v2CommonIntegrationData + Data GcpGarData `json:"data"` +} + +func (reg GcpGarIntegration) ContainerRegistryType() containerRegistryType { + t, _ := FindContainerRegistryType(reg.Data.RegistryType) + return t +} + +type GcpGarData struct { + Credentials GcpCredentialsV2 `json:"credentials"` + RegistryDomain string `json:"registryDomain"` + RegistryType string `json:"registryType"` // always "GCP_GAR" + LimitByTag []string `json:"limitByTag,omitempty"` + LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` + LimitByRep []string `json:"limitByRep,omitempty"` + LimitNumImg int `json:"limitNumImg"` + NonOSPackageEval bool `json:"nonOsPackageEval"` +} + +func verifyGcpGarContainerRegistry(data interface{}) interface{} { + if gar, ok := data.(GcpGarData); ok { + gar.RegistryType = GcpGarContainerRegistry.String() + return gar + } + return data +} + +// GcpCredentials is already defined in api/integrations_gcp.go:163 +// so we need to add a "V2" at the end to make it clear that this is +// the Google Credentials struct for API v2 +type GcpCredentialsV2 struct { + ClientEmail string `json:"clientEmail"` + ClientID string `json:"clientId"` + PrivateKeyID string `json:"privateKeyId"` + PrivateKey string `json:"privateKey,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gcr.go b/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gcr.go new file mode 100644 index 000000000..c964120db --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gcr.go @@ -0,0 +1,71 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetGcpGcr gets a single GcpGcr integration matching the +// provided integration guid +func (svc *ContainerRegistriesService) GetGcpGcr(guid string) ( + response GcpGcrIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateGcpGcr updates a single GcpGcr integration on the Lacework Server +func (svc *ContainerRegistriesService) UpdateGcpGcr(data ContainerRegistry) ( + response GcpGcrIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GcpGcrIntegrationResponse struct { + Data GcpGcrIntegration `json:"data"` +} + +type GcpGcrIntegration struct { + v2CommonIntegrationData + Data GcpGcrData `json:"data"` +} + +func (reg GcpGcrIntegration) ContainerRegistryType() containerRegistryType { + t, _ := FindContainerRegistryType(reg.Data.RegistryType) + return t +} + +type GcpGcrData struct { + Credentials GcpCredentialsV2 `json:"credentials"` + RegistryDomain string `json:"registryDomain"` + RegistryType string `json:"registryType"` + LimitByTag []string `json:"limitByTag,omitempty"` + LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` + LimitByRep []string `json:"limitByRep,omitempty"` + LimitNumImg int `json:"limitNumImg"` + NonOSPackageEval bool `json:"nonOsPackageEval"` +} + +func verifyGcpGcrContainerRegistry(data interface{}) interface{} { + if gar, ok := data.(GcpGcrData); ok { + gar.RegistryType = GcpGcrContainerRegistry.String() + return gar + } + return data +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_ghcr.go b/vendor/github.com/lacework/go-sdk/api/container_registries_ghcr.go new file mode 100644 index 000000000..fd5db59f8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries_ghcr.go @@ -0,0 +1,82 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetGhcr gets a single Ghcr integration matching the +// provided integration guid +func (svc *ContainerRegistriesService) GetGhcr(guid string) ( + response GhcrIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateGhcr updates a single Ghcr integration on the Lacework Server +func (svc *ContainerRegistriesService) UpdateGhcr(data ContainerRegistry) ( + response GhcrIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GhcrIntegrationResponse struct { + Data GhcrIntegration `json:"data"` +} + +type GhcrIntegration struct { + v2CommonIntegrationData + Data GhcrData `json:"data"` +} + +func (reg GhcrIntegration) ContainerRegistryType() containerRegistryType { + t, _ := FindContainerRegistryType(reg.Data.RegistryType) + return t +} + +type GhcrData struct { + Credentials GhcrCredentials `json:"credentials"` + RegistryNotifications bool `json:"registryNotifications"` + RegistryDomain string `json:"registryDomain"` // always "ghcr.io" + RegistryType string `json:"registryType"` // always "GHCR" + LimitByTag []string `json:"limitByTag,omitempty"` + LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` + LimitByRep []string `json:"limitByRep,omitempty"` + LimitNumImg int `json:"limitNumImg"` + NonOSPackageEval bool `json:"nonOsPackageEval"` +} + +func verifyGhcrContainerRegistry(data interface{}) interface{} { + if ghcr, ok := data.(GhcrData); ok { + ghcr.RegistryType = GhcrContainerRegistry.String() + ghcr.RegistryDomain = "ghcr.io" + return ghcr + } + return data +} + +// GcpCredentials is already defined in api/integrations_gcp.go:163 +// so we need to add a "V2" at the end to make it clear that this is +// the Google Credentials struct for API v2 +type GhcrCredentials struct { + Username string `json:"username"` + Password string `json:"password,omitempty"` + Ssl bool `json:"ssl"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_inline_scanner.go b/vendor/github.com/lacework/go-sdk/api/container_registries_inline_scanner.go new file mode 100644 index 000000000..eb889c45a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries_inline_scanner.go @@ -0,0 +1,67 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetInlineScanner gets a single InlineScanner integration matching the +// provided integration guid +func (svc *ContainerRegistriesService) GetInlineScanner(guid string) ( + response InlineScannerIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateInlineScanner updates a single InlineScanner integration on the Lacework Server +func (svc *ContainerRegistriesService) UpdateInlineScanner(data ContainerRegistry) ( + response InlineScannerIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type InlineScannerIntegrationResponse struct { + Data InlineScannerIntegration `json:"data"` +} + +type InlineScannerIntegration struct { + v2CommonIntegrationData + Data InlineScannerData `json:"data"` + ServerToken V2ServerToken `json:"serverToken"` +} + +func (reg InlineScannerIntegration) ContainerRegistryType() containerRegistryType { + t, _ := FindContainerRegistryType(reg.Data.RegistryType) + return t +} + +type InlineScannerData struct { + RegistryType string `json:"registryType"` // always "INLINE_SCANNER" + IdentifierTag []map[string]string `json:"identifierTag"` + LimitNumScan string `json:"limitNumScan,omitempty"` +} + +func verifyInlineScannerContainerRegistry(data interface{}) interface{} { + if inlineScanner, ok := data.(InlineScannerData); ok { + inlineScanner.RegistryType = InlineScannerContainerRegistry.String() + return inlineScanner + } + return data +} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_proxy_scanner.go b/vendor/github.com/lacework/go-sdk/api/container_registries_proxy_scanner.go new file mode 100644 index 000000000..8b29d185c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/container_registries_proxy_scanner.go @@ -0,0 +1,69 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GetProxyScanner gets a single ProxyScanner integration matching the +// provided integration guid +func (svc *ContainerRegistriesService) GetProxyScanner(guid string) ( + response ProxyScannerIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateProxyScanner updates a single ProxyScanner integration on the Lacework Server +func (svc *ContainerRegistriesService) UpdateProxyScanner(data ContainerRegistry) ( + response ProxyScannerIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type ProxyScannerIntegrationResponse struct { + Data ProxyScannerIntegration `json:"data"` +} + +type ProxyScannerIntegration struct { + v2CommonIntegrationData + Data ProxyScannerData `json:"data"` + ServerToken V2ServerToken `json:"serverToken"` +} + +func (reg ProxyScannerIntegration) ContainerRegistryType() containerRegistryType { + t, _ := FindContainerRegistryType(reg.Data.RegistryType) + return t +} + +type ProxyScannerData struct { + RegistryType string `json:"registryType"` // always "PROXY_SCANNER" + LimitByTag []string `json:"limitByTag"` + LimitByLabel []map[string]string `json:"limitByLabel"` + LimitByRep []string `json:"limitByRep"` + LimitNumImg int `json:"limitNumImg"` +} + +func verifyProxyScannerContainerRegistry(data interface{}) interface{} { + if proxyScanner, ok := data.(ProxyScannerData); ok { + proxyScanner.RegistryType = ProxyScannerContainerRegistry.String() + return proxyScanner + } + return data +} diff --git a/vendor/github.com/lacework/go-sdk/api/data_export_rules.go b/vendor/github.com/lacework/go-sdk/api/data_export_rules.go new file mode 100644 index 000000000..52c704f5f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/data_export_rules.go @@ -0,0 +1,130 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +// DataExportRulesService is a service that interacts with the DataExportRule +// endpoints from the Lacework Server +type DataExportRulesService struct { + client *Client +} + +type DataExportRulesResponse struct { + Data []DataExportRule `json:"data"` + Message string `json:"message"` +} + +type DataExportRuleResponse struct { + Data DataExportRule `json:"data"` + Message string `json:"message"` +} + +type DataExportRule struct { + ID string `json:"mcGuid,omitempty"` + Filter DataExportRuleFilter `json:"filters"` + Type string `json:"type"` + IDs []string `json:"intgGuidList"` +} + +type DataExportRuleFilter struct { + Name string `json:"name"` + Description string `json:"description"` + CreatedBy string `json:"createdOrUpdatedBy,omitempty"` + UpdatedTime string `json:"createdOrUpdatedTime,omitempty"` + Enabled int `json:"enabled"` + ProfileVersions []string `json:"profileVersions,omitempty"` +} + +// List returns a list of Data Export Rules +func (svc *DataExportRulesService) List() ( + response DataExportRulesResponse, + err error, +) { + err = svc.client.RequestDecoder("GET", apiV2DataExportRules, nil, &response) + return +} + +// Get returns a raw response of the Data Export Rule with the matching guid. +func (svc *DataExportRulesService) Get(id string) ( + response DataExportRuleResponse, + err error, +) { + if id == "" { + err = errors.New("data export rule ID must be provided") + return + } + apiPath := fmt.Sprintf(apiV2DataExportRulesFromGUID, id) + + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +// Create creates a single Data Export Rule +func (svc *DataExportRulesService) Create(rule DataExportRule) (response DataExportRuleResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("POST", apiV2DataExportRules, rule, &response) + return +} + +// Update updates a Data Export Rule that matches the provided guid +func (svc *DataExportRulesService) Update(rule DataExportRule) (response DataExportRuleResponse, + err error, +) { + if rule.ID == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2DataExportRulesFromGUID, rule.ID) + rule.ID = "" + rule.Filter.UpdatedTime = "" + rule.Filter.CreatedBy = "" + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, rule, &response) + return +} + +// Delete deletes a Data Export Rule that matches the provided guid +func (svc *DataExportRulesService) Delete(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2DataExportRulesFromGUID, guid), + nil, + nil, + ) +} + +// Search returns a list of Data Export Rules +func (svc *DataExportRulesService) Search(filters SearchFilter) ( + response DataExportRulesResponse, err error, +) { + err = svc.client.RequestEncoderDecoder( + "POST", apiV2DataExportRulesSearch, + filters, &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/datasources.go b/vendor/github.com/lacework/go-sdk/api/datasources.go new file mode 100644 index 000000000..80858a21c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/datasources.go @@ -0,0 +1,88 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "net/url" + + "github.com/pkg/errors" +) + +type DatasourcesResponse struct { + Data []Datasource `json:"data"` + Message string `json:"message"` +} + +type DatasourceResponse struct { + Data Datasource `json:"data"` + Message string `json:"message"` +} + +type Datasource struct { + Name string `json:"name"` + Description string `json:"description"` + ResultSchema []DatasourceSchema `json:"resultSchema"` + SourceRelationships []DatasourceRelationship `json:"sourceRelationships"` +} + +type DatasourceSchema struct { + Name string `json:"name"` + DataType string `json:"dataType"` + Description string `json:"description"` +} + +type DatasourceRelationship struct { + Name string `json:"name"` + Description string `json:"description"` + From string `json:"from"` + To string `json:"to"` + ToCardinality string `json:"toCardinality"` +} + +// DatasourcesService is a service that interacts with the Datasources +// endpoints from the Lacework Server +type DatasourcesService struct { + client *Client +} + +func (svc *DatasourcesService) List() ( + response DatasourcesResponse, + err error, +) { + err = svc.client.RequestDecoder("GET", apiV2Datasources, nil, &response) + return +} + +func (svc *DatasourcesService) Get(id string) ( + response DatasourceResponse, + err error, +) { + if id == "" { + err = errors.New("datasource ID must be provided") + return + } + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf("%s/%s", apiV2Datasources, url.QueryEscape(id)), + nil, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/entities.go b/vendor/github.com/lacework/go-sdk/api/entities.go new file mode 100644 index 000000000..d47eb6af9 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/entities.go @@ -0,0 +1,92 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +type EntitiesService struct { + client *Client +} + +type EntityType int + +const ( + NoneEntityType EntityType = iota + MachineDetailsEntityType + UsersEntityType + ImagesEntityType + ContainersEntityType + MachineEntityType +) + +// EntityTypes is the list of available entity types +var EntityTypes = map[EntityType]string{ + NoneEntityType: "None", + MachineDetailsEntityType: "MachineDetails", + MachineEntityType: "Machines", + UsersEntityType: "Users", + ImagesEntityType: "Images", + ContainersEntityType: "Containers", +} + +// Search expects the response and the search filters +// +// e.g. +// +// var ( +// response = &api.MachineDetailsEntityResponse{} +// now = time.Now().UTC() +// before = now.AddDate(0, 0, -7) // 7 days from ago +// filters = api.SearchFilter{ +// TimeFilter: &api.TimeFilter{ +// StartTime: &before, +// EndTime: &now, +// }, +// } +// ) +// lacework.V2.Entities.Search(response, filters) +func (svc *EntitiesService) Search(response interface{}, filters SearchFilter) error { + var apiPath string + + switch response.(type) { + case *MachineDetailsEntityResponse: + apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[MachineDetailsEntityType]) + + case *UsersEntityResponse: + apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[UsersEntityType]) + + case *ImagesEntityResponse: + apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[ImagesEntityType]) + + case *ContainersEntityResponse: + apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[ContainersEntityType]) + + case *MachinesEntityResponse: + apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[MachineEntityType]) + + default: + return errors.New("missing implementation for the provided entity response") + } + + return svc.client.RequestEncoderDecoder("POST", apiPath, filters, response) +} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_containers.go b/vendor/github.com/lacework/go-sdk/api/entities_containers.go new file mode 100644 index 000000000..78904e16b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/entities_containers.go @@ -0,0 +1,158 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "time" + + "github.com/lacework/go-sdk/internal/array" +) + +// ListContainers returns a list of Active Containers from the last 7 days +func (svc *EntitiesService) ListContainers() (response ContainersEntityResponse, err error) { + now := time.Now().UTC() + before := now.AddDate(0, 0, -7) // 7 days from ago + err = svc.Search(&response, + SearchFilter{ + TimeFilter: &TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + }, + ) + return +} + +// ListContainersWithFilters returns a list of Active Containers based on a user defined filter +func (svc *EntitiesService) ListContainersWithFilters(filters SearchFilter) ( + response ContainersEntityResponse, err error, +) { + err = svc.Search(&response, filters) + return +} + +// ListAllContainers iterates over all pages to return all active container information at once +func (svc *EntitiesService) ListAllContainers() (response ContainersEntityResponse, err error) { + response, err = svc.ListContainers() + if err != nil { + return + } + + var ( + all []ContainerEntity + pageOk bool + ) + for { + all = append(all, response.Data...) + + newResponse := ContainersEntityResponse{ + Paging: response.Paging, + } + pageOk, err = svc.client.NextPage(&newResponse) + if err == nil && pageOk { + response = newResponse + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +// ListAllContainersWithFilters iterates over all pages to return all active container +// information at once based on a user defined filter +func (svc *EntitiesService) ListAllContainersWithFilters(filters SearchFilter) ( + response ContainersEntityResponse, err error, +) { + response, err = svc.ListContainersWithFilters(filters) + if err != nil { + return + } + + var ( + all []ContainerEntity + pageOk bool + ) + + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +type ContainersEntityResponse struct { + Data []ContainerEntity `json:"data"` + Paging V2Pagination `json:"paging"` + + v2PageMetadata `json:"-"` +} + +// Fulfill Pageable interface (look at api/v2.go) +func (r ContainersEntityResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *ContainersEntityResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +// Total returns the total number of active containers +func (r *ContainersEntityResponse) Total() int { + uniqMIDs := []int{} + for _, container := range r.Data { + if !array.ContainsInt(uniqMIDs, container.Mid) { + uniqMIDs = append(uniqMIDs, container.Mid) + } + } + return len(uniqMIDs) +} + +// Count returns the number of active containers with the provided image ID +func (r *ContainersEntityResponse) Count(imageID string) int { + uniqMIDs := []int{} + for _, container := range r.Data { + if container.ImageID == imageID && + !array.ContainsInt(uniqMIDs, container.Mid) { + uniqMIDs = append(uniqMIDs, container.Mid) + } + } + return len(uniqMIDs) +} + +type ContainerEntity struct { + ContainerName string `json:"containerName"` + ImageID string `json:"imageId"` + Mid int `json:"mid"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + PodName string `json:"podName"` + PropsContainer map[string]interface{} `json:"propsContainer"` + Tags map[string]interface{} `json:"tags"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_images.go b/vendor/github.com/lacework/go-sdk/api/entities_images.go new file mode 100644 index 000000000..ac5093acf --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/entities_images.go @@ -0,0 +1,124 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "time" + +// ListImages returns a list of UserEntity from the last 7 days +func (svc *EntitiesService) ListImages() (response ImagesEntityResponse, err error) { + now := time.Now().UTC() + before := now.AddDate(0, 0, -7) // 7 days from ago + err = svc.Search(&response, + SearchFilter{ + TimeFilter: &TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + }, + ) + return +} + +// ListImagesWithFilters returns a list of UserEntity based on a user defined filter +func (svc *EntitiesService) ListImagesWithFilters(filters SearchFilter) (response ImagesEntityResponse, err error) { + err = svc.Search(&response, filters) + return +} + +// ListAllImages iterates over all pages to return all images information at once +func (svc *EntitiesService) ListAllImages() (response ImagesEntityResponse, err error) { + response, err = svc.ListImages() + if err != nil { + return + } + + var ( + all []ImageEntity + pageOk bool + ) + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +// ListAllImagesWithFilters iterates over all pages to return all images information +// at once based on a user defined filter +func (svc *EntitiesService) ListAllImagesWithFilters(filters SearchFilter) ( + response ImagesEntityResponse, err error, +) { + response, err = svc.ListImagesWithFilters(filters) + if err != nil { + return + } + + var ( + all []ImageEntity + pageOk bool + ) + + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +type ImagesEntityResponse struct { + Data []ImageEntity `json:"data"` + Paging V2Pagination `json:"paging"` + + v2PageMetadata `json:"-"` +} + +// Fulfill Pageable interface (look at api/v2.go) +func (r ImagesEntityResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *ImagesEntityResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +type ImageEntity struct { + ContainerType string `json:"containerType"` + CreatedTime time.Time `json:"createdTime"` + ImageID string `json:"imageId"` + Mid int `json:"mid"` + Repo string `json:"repo"` + Size int `json:"size"` + Tag string `json:"tag"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_machine_details.go b/vendor/github.com/lacework/go-sdk/api/entities_machine_details.go new file mode 100644 index 000000000..0371023bc --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/entities_machine_details.go @@ -0,0 +1,171 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "time" +) + +// ListMachineDetails returns a list of MachineDetailEntity from the last 7 days +func (svc *EntitiesService) ListMachineDetails() (response MachineDetailsEntityResponse, err error) { + now := time.Now().UTC() + before := now.AddDate(0, 0, -7) // 7 days from ago + err = svc.Search(&response, + SearchFilter{ + TimeFilter: &TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + }, + ) + return +} + +// ListMachineDetailsWithFilters returns a list of UserEntity based on a user defined filter +func (svc *EntitiesService) ListMachineDetailsWithFilters(filters SearchFilter) ( + response MachineDetailsEntityResponse, err error, +) { + err = svc.Search(&response, filters) + return +} + +// ListAllMachineDetails iterates over all pages to return all machine details at once +func (svc *EntitiesService) ListAllMachineDetails() (response MachineDetailsEntityResponse, err error) { + response, err = svc.ListMachineDetails() + if err != nil { + return + } + + var ( + all []MachineDetailEntity + pageOk bool + ) + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +// ListAllMachineDetailsWithFilters iterates over all pages to return all machine details +// at once based on a user defined filter +func (svc *EntitiesService) ListAllMachineDetailsWithFilters(filters SearchFilter) ( + response MachineDetailsEntityResponse, err error, +) { + response, err = svc.ListMachineDetailsWithFilters(filters) + if err != nil { + return + } + + var ( + all []MachineDetailEntity + pageOk bool + ) + + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +type MachineDetailsEntityResponse struct { + Data []MachineDetailEntity `json:"data"` + Paging V2Pagination `json:"paging"` + + v2PageMetadata `json:"-"` +} + +// Fulfill Pageable interface (look at api/v2.go) +func (r MachineDetailsEntityResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *MachineDetailsEntityResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +type MachineDetailEntity struct { + AwsInstanceID string `json:"awsInstanceId"` + AwsZone string `json:"awsZone"` + CreatedTime time.Time `json:"createdTime"` + Domain string `json:"domain"` + Hostname string `json:"hostname"` + Kernel string `json:"kernel"` + KernelRelease string `json:"kernelRelease"` + KernelVersion string `json:"kernelVersion"` + Mid int `json:"mid"` + Os string `json:"os"` + OsVersion string `json:"osVersion"` + Tags struct { + // Shared Tags + Arch string `json:"arch,omitempty"` + ExternalIP string `json:"ExternalIp,omitempty"` + Hostname string `json:"Hostname,omitempty"` + InstanceID string `json:"InstanceId,omitempty"` + InternalIP string `json:"InternalIp,omitempty"` + LwTokenShort string `json:"LwTokenShort,omitempty"` + Os string `json:"os,omitempty"` + VMInstanceType string `json:"VmInstanceType,omitempty"` + VMProvider string `json:"VmProvider,omitempty"` + Zone string `json:"Zone,omitempty"` + + // AWS Tags + Account string `json:"Account,omitempty"` + AmiID string `json:"AmiId,omitempty"` + Name string `json:"Name,omitempty"` + SubnetID string `json:"SubnetId,omitempty"` + VpcID string `json:"VpcId,omitempty"` + + // GCP Tags + Cluster string `json:"Cluster,omitempty"` + ClusterLocation string `json:"cluster-location,omitempty"` + ClusterName string `json:"cluster-name,omitempty"` + ClusterUID string `json:"cluster-uid,omitempty"` + CreatedBy string `json:"created-by,omitempty"` + EnableOSLogin string `json:"enable-oslogin,omitempty"` + Env string `json:"Env,omitempty"` + GCEtags string `json:"GCEtags,omitempty"` + GCIEnsureGKEDocker string `json:"gci-ensure-gke-docker,omitempty"` + GCIUpdateStrategy string `json:"gci-update-strategy,omitempty"` + GoogleComputeEnablePCID string `json:"google-compute-enable-pcid,omitempty"` + InstanceName string `json:"InstanceName,omitempty"` + InstanceTemplate string `json:"InstanceTemplate,omitempty"` + KubeLabels string `json:"kube-labels,omitempty"` + LWKubernetesCluster string `json:"lw_KubernetesCluster,omitempty"` + NumericProjectID string `json:"NumericProjectId,omitempty"` + ProjectID string `json:"ProjectId,omitempty"` + } `json:"tags"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_machines.go b/vendor/github.com/lacework/go-sdk/api/entities_machines.go new file mode 100644 index 000000000..53205c610 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/entities_machines.go @@ -0,0 +1,164 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "time" +) + +// ListMachines returns a list of MachineEntity from the last 7 days +func (svc *EntitiesService) ListMachines() (response MachinesEntityResponse, err error) { + now := time.Now().UTC() + before := now.AddDate(0, 0, -7) // 7 days from ago + err = svc.Search(&response, + SearchFilter{ + TimeFilter: &TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + }, + ) + return +} + +// ListMachinesWithFilters returns a list of UserEntity based on a user defined filter +func (svc *EntitiesService) ListMachinesWithFilters(filters SearchFilter) (response MachinesEntityResponse, err error) { + err = svc.Search(&response, filters) + return +} + +// ListAllMachines iterates over all pages to return all machine details at once +func (svc *EntitiesService) ListAllMachines() (response MachinesEntityResponse, err error) { + response, err = svc.ListMachines() + if err != nil { + return + } + + var ( + all []MachineEntity + pageOk bool + ) + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +// ListAllMachinesWithFilters iterates over all pages to return all machine details +// at once based on a user defined filter +func (svc *EntitiesService) ListAllMachinesWithFilters(filters SearchFilter) ( + response MachinesEntityResponse, err error, +) { + response, err = svc.ListMachinesWithFilters(filters) + if err != nil { + return + } + + var ( + all []MachineEntity + pageOk bool + ) + + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +type MachinesEntityResponse struct { + Data []MachineEntity `json:"data"` + Paging V2Pagination `json:"paging"` + + v2PageMetadata `json:"-"` +} + +// Fulfill Pageable interface (look at api/v2.go) +func (r MachinesEntityResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *MachinesEntityResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +type MachineEntity struct { + AwsInstanceID string `json:"awsInstanceId"` + Hostname string `json:"hostname"` + EntityType string `json:"entityType"` + EndTime time.Time `json:"endTime"` + Mid int `json:"mid"` + PrimaryIpAddr string `json:"primaryIpAddr"` + StartTime time.Time `json:"startTime"` + Tags struct { + // Shared Tags + Cluster string `json:"Cluster,omitempty"` + Env string `json:"Env,omitempty"` + Arch string `json:"arch,omitempty"` + ExternalIP string `json:"ExternalIp,omitempty"` + Hostname string `json:"Hostname,omitempty"` + InstanceID string `json:"InstanceId,omitempty"` + InternalIP string `json:"InternalIp,omitempty"` + LwTokenShort string `json:"LwTokenShort,omitempty"` + Os string `json:"os,omitempty"` + VMInstanceType string `json:"VmInstanceType,omitempty"` + VMProvider string `json:"VmProvider,omitempty"` + Zone string `json:"Zone,omitempty"` + ClusterLocation string `json:"cluster-location,omitempty"` + ClusterName string `json:"cluster-name,omitempty"` + ClusterUid string `json:"cluster-uid,omitempty"` + CreatedBy string `json:"created-by,omitempty"` + LwKubernetesCluster string `json:"lw_KubernetesCluster,omitempty"` + KubeLabels string `json:"kube-labels,omitempty"` + + // AWS Tags + Account string `json:"Account,omitempty"` + AmiId string `json:"AmiId,omitempty"` + SubnetId string `json:"SubnetId,omitempty"` + VpcId string `json:"VpcId,omitempty"` + + // GCP Tags + GCEtags string `json:"GCEtags,omitempty"` + InstanceName string `json:"InstanceName,omitempty"` + NumericProjectId string `json:"NumericProjectId,omitempty"` + ProjectId string `json:"ProjectId,omitempty"` + EnableOslogin string `json:"enable-oslogin,omitempty"` + GciEnsureGkeDocker string `json:"gci-ensure-gke-docker,omitempty"` + GciUpdateStrategy string `json:"gci-update-strategy,omitempty"` + GoogleComputeEnablePcid string `json:"google-compute-enable-pcid,omitempty"` + InstanceTemplate string `json:"instance-template,omitempty"` + } `json:"machineTags"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_users.go b/vendor/github.com/lacework/go-sdk/api/entities_users.go new file mode 100644 index 000000000..155d2c062 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/entities_users.go @@ -0,0 +1,87 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "time" + +// ListUsers returns a list of UserEntity from the last 7 days +func (svc *EntitiesService) ListUsers() (response UsersEntityResponse, err error) { + now := time.Now().UTC() + before := now.AddDate(0, 0, -7) // 7 days from ago + err = svc.Search(&response, + SearchFilter{ + TimeFilter: &TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + }, + ) + return +} + +// ListAllUsers iterates over all pages to return all user information at once +func (svc *EntitiesService) ListAllUsers() (response UsersEntityResponse, err error) { + response, err = svc.ListUsers() + if err != nil { + return + } + + var ( + all []UserEntity + pageOk bool + ) + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +type UsersEntityResponse struct { + Data []UserEntity `json:"data"` + Paging V2Pagination `json:"paging"` + + v2PageMetadata `json:"-"` +} + +// Fulfill Pagination interface (look at api/v2.go) +func (r UsersEntityResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *UsersEntityResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +type UserEntity struct { + CreatedTime time.Time `json:"createdTime"` + Mid int `json:"mid"` + OtherGroupNames []string `json:"otherGroupNames"` + PrimaryGroupName string `json:"primaryGroupName"` + UID int `json:"uid"` + Username string `json:"username"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/errors.go b/vendor/github.com/lacework/go-sdk/api/errors.go new file mode 100644 index 000000000..329fdcac4 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/errors.go @@ -0,0 +1,113 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/json" + "fmt" + "io" + "net/http" +) + +// errorResponse handles errors caused by a Lacework API request +type errorResponse struct { + Response *http.Response + Message string +} + +type apiErrorResponse struct { + Ok bool `json:"ok"` + V2Message string `json:"message"` + Data struct { + Message string `json:"message"` + StatusMessage string `json:"statusMessage"` + ErrorMsg string `json:"ErrorMsg"` + } `json:"data"` +} + +// Message extracts the message from an api error response +func (r *apiErrorResponse) Message() string { + if r != nil { + if r.Data.ErrorMsg != "" { + return r.Data.ErrorMsg + } + if r.Data.Message != "" { + return r.Data.Message + } + if r.V2Message != "" && r.V2Message != "SUCCESS" { + return r.V2Message + } + if r.Data.StatusMessage != "" { + return r.Data.StatusMessage + } + } + return "" +} + +// Error fulfills the built-in error interface function +func (r *errorResponse) Error() string { + return fmt.Sprintf("\n [%v] %v\n [%d] %s", + r.Response.Request.Method, + r.Response.Request.URL, + r.Response.StatusCode, + r.Message, + ) +} + +// checkResponse checks the provided response and generates an Error +func checkErrorInResponse(r *http.Response) error { + if c := r.StatusCode; c >= 200 && c <= 299 { + return nil + } + + var ( + errRes = &errorResponse{Response: r} + data, err = io.ReadAll(r.Body) + ) + if err == nil && len(data) > 0 { + // try to unmarshal the api error response + apiErrRes := &apiErrorResponse{} + if err := json.Unmarshal(data, apiErrRes); err != nil { + errRes.Message = string(data) + return errRes + } + + var ( + apiErrResMessage = apiErrRes.Message() + statusText = http.StatusText(r.StatusCode) + ) + + // try our best to parse the error message + if apiErrResMessage != "" { + errRes.Message = apiErrResMessage + return errRes + } + + // if it is empty, try to display the Status Code in pretty Text + if statusText != "" { + errRes.Message = statusText + return errRes + } + + // if we couldn't even decode the StatusCode... well, lets just be transparent + errRes.Message = "Unknown" + } + + return errRes +} diff --git a/vendor/github.com/lacework/go-sdk/api/feature_flags.go b/vendor/github.com/lacework/go-sdk/api/feature_flags.go new file mode 100644 index 000000000..8fa897c6e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/feature_flags.go @@ -0,0 +1,27 @@ +package api + +import ( + "fmt" +) + +type FeatureFlagsService struct { + client *Client +} + +type FeatureFlag string + +type FeatureFlags struct { + Flags []FeatureFlag `json:"flags,omitempty"` +} + +type FeatureFlagsResponse struct { + Data FeatureFlags `json:"data"` +} + +func (svc *FeatureFlagsService) GetFeatureFlagsMatchingPrefix(prefix string) ( + response FeatureFlagsResponse, err error, +) { + apiPath := fmt.Sprintf("%s/%s", apiV2FeatureFlags, prefix) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/http.go b/vendor/github.com/lacework/go-sdk/api/http.go new file mode 100644 index 000000000..37a590b24 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/http.go @@ -0,0 +1,258 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/cenkalti/backoff/v4" + "go.uber.org/zap" +) + +// NewRequest generates a new http request +func (c *Client) NewRequest(method string, apiURL string, body io.Reader) (*http.Request, error) { + apiPath, err := url.Parse(c.apiPath(apiURL)) + if err != nil { + return nil, err + } + + u := c.baseURL.ResolveReference(apiPath) + request, err := http.NewRequest(method, u.String(), body) + if err != nil { + return nil, err + } + + // set all necessary headers + headers := map[string]string{ + "Method": request.Method, + "Accept": "application/json", + } + + // handle the special case that we are requesting an access token + if apiURL == apiTokens { + headers["X-LW-UAKS"] = c.auth.secret + } else { + // verify that the client has a token or token is not expired, + // if not, try to generate one + if c.auth.token == "" || c.TokenExpired() { + // run token expired callback + if c.callbacks.TokenExpiredCallback != nil && c.TokenExpired() { + if err := c.callbacks.TokenExpiredCallback(); err != nil { + c.log.Info("token expired callback failure", zap.String("error", err.Error())) + } + } + if _, err = c.GenerateToken(); err != nil { + return nil, err + } + } + headers["Authorization"] = c.auth.token + } + + if body != nil { + // @afiune we should detect the content-type from the body + // instead of hard-coding it here + headers["Content-Type"] = "application/json" + } + + for k, v := range headers { + request.Header.Set(k, v) + } + + // add all global headers that the client has configured, + // by default we set only the User-Agent header + for k, v := range c.headers { + request.Header.Set(k, v) + } + + // parse and encode query string values + values := request.URL.Query() + request.URL.RawQuery = values.Encode() + + c.log.Debug("request", + zap.String("method", request.Method), + zap.String("url", c.baseURL.String()), + zap.String("endpoint", apiPath.String()), + zap.Reflect("headers", c.httpHeadersSniffer(request.Header)), + zap.String("body", c.httpRequestBodySniffer(request)), + ) + + return request, nil +} + +// DoDecoder is used to execute (aka Do) the http request and +// decode it into the provided interface, all at once +func (c *Client) DoDecoder(req *http.Request, v interface{}) (*http.Response, error) { + res, err := c.Do(req) + if err != nil { + return nil, err + } + + // optimized return for HTTP 204 as there's nothing to decode onto v + if res.StatusCode == http.StatusNoContent { + return res, nil + } + + err = checkErrorInResponse(res) + if err != nil { + return res, err + } + + if v != nil { + var ( + resBuf bytes.Buffer + + // by using a TeeReader for capturing the reader’s data we avoid + // interfering with the consumer of the reader + resTee = io.TeeReader(res.Body, &resBuf) + ) + if w, ok := v.(io.Writer); ok { + _, err = io.Copy(w, resTee) + return res, err + } + dcoder := json.NewDecoder(resTee) + dcoder.UseNumber() + err = dcoder.Decode(v) + } + + return res, err +} + +// RequestDecoder performs an http request on an endpoint, and +// decodes the response into the provided interface, all at once +func (c *Client) RequestDecoder(method, path string, body io.Reader, v interface{}) error { + request, err := c.NewRequest(method, path, body) + if err != nil { + return err + } + + var res *http.Response + if c.retries != nil { + err = backoff.Retry(func() error { + res, err = c.DoDecoder(request, v) + return err + }, c.retries) + } else { + res, err = c.DoDecoder(request, v) + } + if err != nil { + return err + } + defer res.Body.Close() + + return err +} + +// RequestEncoderDecoder leverages RequestDecoder and performs an http request that first +// encodes the provider 'data' as a JSON Reader and passes it as the body to the request +func (c *Client) RequestEncoderDecoder(method, path string, data, v interface{}) error { + body, err := jsonReader(data) + if err != nil { + return err + } + return c.RequestDecoder(method, path, body, v) +} + +// Do calls request.Do() directly +func (c *Client) Do(req *http.Request) (*http.Response, error) { + response, err := c.c.Do(req) + if err == nil { + c.log.Info("response", + zap.String("from_req_url", req.URL.String()), + zap.Int("code", response.StatusCode), + zap.String("proto", response.Proto), + zap.Reflect("headers", c.httpHeadersSniffer(response.Header)), + zap.String("body", c.httpResponseBodySniffer(response)), + ) + } + + // run request callback + if call := c.callbacks.RequestCallback; call != nil && response != nil { + if err := call(response.StatusCode, response.Header); err != nil { + c.log.Info("request callback failure", zap.String("error", err.Error())) + } + } + + return response, err +} + +// httpHeadersSniffer is only useful to avoid logging out the headers of a request +// or response when the log level is set to INFO +func (c *Client) httpHeadersSniffer(headers interface{}) interface{} { + if !c.debugMode() { + // prevents headers to be displayed if we are not in DEBUG mode + return "suppressed" + } + return headers +} + +// httpRequestBodySniffer a request sniffer, it reads the body from the +// provided request without closing it (use only for debugging purposes) +func (c *Client) httpRequestBodySniffer(r *http.Request) string { + if !c.debugMode() { + // prevents sniffing the request if we are not in DEBUG mode + return "suppressed" + } + + if r.Body == nil || r.Body == http.NoBody { + // No need to sniff + return "" + } + + var stringBody string + r.Body, stringBody = sniffBody(r.Body) + + return stringBody +} + +// httpResponseBodySniffer a response sniffer, it reads the body from the +// provided response without closing it (use only for debugging purposes) +func (c *Client) httpResponseBodySniffer(r *http.Response) string { + if !c.debugMode() { + // prevents sniffing the response if we are not in DEBUG mode + return "suppressed" + } + + if r.Body == nil || r.ContentLength == 0 { + // No need to sniff + return "" + } + + var stringBody string + r.Body, stringBody = sniffBody(r.Body) + + return stringBody +} + +// a very simple body sniffer (use only for debugging purposes) +func sniffBody(body io.ReadCloser) (io.ReadCloser, string) { + bodyBytes, err := io.ReadAll(body) + if err != nil { + return nil, "" + } + + if err := body.Close(); err != nil { + return nil, "" + } + + return io.NopCloser(bytes.NewBuffer(bodyBytes)), string(bodyBytes) +} diff --git a/vendor/github.com/lacework/go-sdk/api/inventory.go b/vendor/github.com/lacework/go-sdk/api/inventory.go new file mode 100644 index 000000000..c3d80e04f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/inventory.go @@ -0,0 +1,90 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "time" +) + +type InventoryService struct { + client *Client +} + +type inventoryType string +type inventoryDataset string + +const AwsInventoryType inventoryType = "AWS" +const AzureInventoryType inventoryType = "Azure" +const GcpInventoryType inventoryType = "GCP" +const AwsInventoryDataset inventoryDataset = "AwsCompliance" + +// Search expects the response and the search filters +// +// e.g. +// +// var ( +// awsInventorySearchResponse api.InventoryAwsResponse +// filter = api.InventorySearch{ +// SearchFilter: api.SearchFilter{ +// Filters: []api.Filter{{ +// Expression: "eq", +// Field: "urn", +// Value: arn:aws:s3:::my-bucket, +// }}, +// }, +// Dataset: api.AwsComplianceEvaluationDataset, +// } +// ) +// lacework.V2.Inventory.Search(&awsInventorySearchResponse, filters) +func (svc *InventoryService) Search(response interface{}, filters SearchableFilter) error { + return svc.client.RequestEncoderDecoder("POST", apiV2InventorySearch, filters, response) +} + +// Scan triggers a resource inventory scan +func (svc *InventoryService) Scan(cloud inventoryType) (response InventoryScanResponse, err error) { + url := fmt.Sprintf(apiV2InventoryScanCsp, cloud) + err = svc.client.RequestEncoderDecoder("POST", url, nil, &response) + return +} + +type InventorySearch struct { + SearchFilter + Csp inventoryType `json:"csp"` + Dataset inventoryDataset `json:"dataset"` +} + +func (i InventorySearch) GetTimeFilter() *TimeFilter { + return i.TimeFilter +} + +func (i InventorySearch) SetStartTime(time *time.Time) { + i.TimeFilter.StartTime = time +} + +func (i InventorySearch) SetEndTime(time *time.Time) { + i.TimeFilter.EndTime = time +} + +type InventoryScanResponse struct { + Data struct { + Status string `json:"status"` + Details string `json:"details"` + } `json:"data"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/inventory_aws.go b/vendor/github.com/lacework/go-sdk/api/inventory_aws.go new file mode 100644 index 000000000..1440124cd --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/inventory_aws.go @@ -0,0 +1,62 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +type InventoryAwsResponse struct { + Data []InventoryAws `json:"data"` + Paging V2Pagination `json:"paging"` +} + +func (r InventoryAwsResponse) GetDataLength() int { + return len(r.Data) +} + +func (r InventoryAwsResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *InventoryAwsResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +type InventoryAws struct { + ApiKey string `json:"apiKey"` + Csp string `json:"csp"` + EndTime string `json:"endTime"` + StartTime string `json:"startTime"` + ResourceId string `json:"resourceId"` + ResourceRegion string `json:"resourceRegion"` + ResourceTags any `json:"resourceTags"` + ResourceType string `json:"resourceType"` + Service string `json:"service"` + Urn string `json:"urn"` + CloudDetails struct { + AccountAlias string `json:"accountAlias"` + AccountID string `json:"accountID"` + } `json:"cloudDetails"` + Status struct { + FormatVersion int `json:"formatVersion"` + Props any `json:"props"` + Status string `json:"status"` + // Error status + ErrorMessage string `json:"errorMessage,omitempty"` + ErrorType string `json:"errorType,omitempty"` + } `json:"status"` + ResourceConfig any `json:"resourceConfig"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/logging.go b/vendor/github.com/lacework/go-sdk/api/logging.go new file mode 100644 index 000000000..56dfe2d1c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/logging.go @@ -0,0 +1,156 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "io" + "os" + "syscall" + + "github.com/pkg/errors" + "go.uber.org/zap" + + "github.com/lacework/go-sdk/lwlogger" +) + +// WithLogLevel sets the log level of the client, available: info, debug, or error +func WithLogLevel(level string) Option { + return clientFunc(func(c *Client) error { + // do not re initialize our logger if the log level + // is the same as the desired one + if level == c.log.Level().CapitalString() { + return nil + } + + if !lwlogger.ValidLevel(level) { + return fmt.Errorf("invalid log level '%s'", level) + } + + c.log.Debug("setting up client", zap.String("log_level", level)) + c.initLogger(level) + return nil + }) +} + +// WithLogLevelAndWriter sets the log level of the client +// and writes the log messages to the provided io.Writer +func WithLogLevelAndWriter(level string, w io.Writer) Option { + return clientFunc(func(c *Client) error { + if !lwlogger.ValidLevel(level) { + return fmt.Errorf("invalid log level '%s'", level) + } + + c.log.Debug("setting up client", zap.String("log_level", level)) + c.initLoggerWithWriterAndLevel(level, w) + return nil + }) +} + +// WithLogWriter configures the client to log messages to the provided io.Writer +func WithLogWriter(w io.Writer) Option { + return clientFunc(func(c *Client) error { + c.initLoggerWithWriter(w) + return nil + }) +} + +// WithLogLevelAndFile sets the log level of the client +// and writes the log messages to the provided file +func WithLogLevelAndFile(level string, filename string) Option { + return clientFunc(func(c *Client) error { + if !lwlogger.ValidLevel(level) { + return fmt.Errorf("invalid log level '%s'", level) + } + + logWriter, err := os.OpenFile(filename, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) + if err != nil { + return errors.Wrap(err, "unable to open file to initialize api logger ") + } + + c.initLoggerWithWriterAndLevel(level, logWriter) + return nil + }) +} + +// WithLogFile configures the client to write messages to the provided file +func WithLogFile(filename string) Option { + return clientFunc(func(c *Client) error { + logWriter, err := os.OpenFile(filename, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) + if err != nil { + return errors.Wrap(err, "unable to open file to initialize api logger ") + } + + c.log.Debug("setting up client redirect logger", zap.String("file", filename)) + c.initLoggerWithWriter(logWriter) + return nil + }) +} + +// initLogger initializes the logger with a set of default fields +func (c *Client) initLogger(level string) { + if c.log != nil { + _ = c.log.Sync() + } + c.log = lwlogger.New(level, + zap.Fields( + zap.Field(zap.String("id", c.id)), + zap.Field(zap.String("account", c.account)), + ), + ) + + // verify if the log level has been configure through environment variable + if envLevel := lwlogger.LogLevelFromEnvironment(); envLevel != "" { + c.log.Debug("setting up client, override log level", + zap.String("before", level), + zap.String("after", envLevel), + ) + } +} + +// initLoggerWithWriter initializes a new logger with a set +// of default fields and configues the provided io.Writer +func (c *Client) initLoggerWithWriter(w io.Writer) { + c.initLoggerWithWriterAndLevel("", w) +} + +func (c *Client) initLoggerWithWriterAndLevel(level string, w io.Writer) { + if c.log != nil { + _ = c.log.Sync() + } + c.log = lwlogger.NewWithWriter(level, w, + zap.Fields( + zap.Field(zap.String("id", c.id)), + zap.Field(zap.String("account", c.account)), + ), + ) + + // verify if the log level has been configure through environment variable + if envLevel := lwlogger.LogLevelFromEnvironment(); envLevel != "" { + c.log.Debug("setting up client, override log level", + zap.String("before", level), + zap.String("after", envLevel), + ) + } +} + +// debugMode returns true if the client is configured to display debug level logs +func (c *Client) debugMode() bool { + return c.log.Level() == zap.DebugLevel +} diff --git a/vendor/github.com/lacework/go-sdk/api/lql.go b/vendor/github.com/lacework/go-sdk/api/lql.go new file mode 100644 index 000000000..ae476d4db --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/lql.go @@ -0,0 +1,133 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "reflect" + + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +type NewQuery struct { + QueryID string `json:"queryId" yaml:"queryId"` + QueryText string `json:"queryText" yaml:"queryText"` +} + +func ParseNewQuery(s string) (NewQuery, error) { + var ( + query NewQuery + err error + ) + + // valid json + if err = json.Unmarshal([]byte(s), &query); err == nil { + return query, err + } + // valid yaml + query = NewQuery{} + err = yaml.Unmarshal([]byte(s), &query) + if err == nil && !reflect.DeepEqual(query, NewQuery{}) { // empty string unmarshals w/o error + return query, nil + } + // invalid query + return query, errors.New("unable to parse query") +} + +type UpdateQuery struct { + QueryText string `json:"queryText"` +} + +type Query struct { + QueryID string `json:"queryId" yaml:"queryId"` + QueryText string `json:"queryText" yaml:"queryText"` + Owner string `json:"owner"` + LastUpdateTime string `json:"lastUpdateTime"` + LastUpdateUser string `json:"lastUpdateUser"` + ResultSchema []map[string]interface{} `json:"resultSchema"` +} + +type QueryResponse struct { + Data Query `json:"data"` + Message string `json:"message"` +} + +type QueriesResponse struct { + Data []Query `json:"data"` + Message string `json:"message"` +} + +// QueryService is a service that interacts with the Queries +// endpoints from the Lacework Server +type QueryService struct { + client *Client +} + +func (svc *QueryService) Create(nq NewQuery) ( + response QueryResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("POST", apiV2Queries, nq, &response) + return +} + +func (svc *QueryService) Update(id string, uq UpdateQuery) ( + response QueryResponse, + err error, +) { + if id == "" { + err = errors.New("query ID must be provided") + return + } + err = svc.client.RequestEncoderDecoder( + "PATCH", + fmt.Sprintf("%s/%s", apiV2Queries, url.QueryEscape(id)), + uq, + &response, + ) + return +} + +func (svc *QueryService) List() ( + response QueriesResponse, + err error, +) { + err = svc.client.RequestDecoder("GET", apiV2Queries, nil, &response) + return +} + +func (svc *QueryService) Get(id string) ( + response QueryResponse, + err error, +) { + if id == "" { + err = errors.New("query ID must be provided") + return + } + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf("%s/%s", apiV2Queries, url.QueryEscape(id)), + nil, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/lql_delete.go b/vendor/github.com/lacework/go-sdk/api/lql_delete.go new file mode 100644 index 000000000..845fea295 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/lql_delete.go @@ -0,0 +1,47 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "net/url" + + "github.com/pkg/errors" +) + +type QueryDeleteResponse struct { + Message string `json:"message"` +} + +func (svc *QueryService) Delete(id string) ( + response QueryDeleteResponse, + err error, +) { + if id == "" { + err = errors.New("query ID must be provided") + return + } + err = svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf("%s/%s", apiV2Queries, url.QueryEscape(id)), + nil, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/lql_execute.go b/vendor/github.com/lacework/go-sdk/api/lql_execute.go new file mode 100644 index 000000000..178bdb980 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/lql_execute.go @@ -0,0 +1,159 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" + + "github.com/lacework/go-sdk/lwtime" + "github.com/pkg/errors" +) + +type ExecuteQuery struct { + QueryText string `json:"queryText"` +} + +type ExecuteQueryArgumentName string + +const ( + QueryStartTimeRange ExecuteQueryArgumentName = "StartTimeRange" + QueryEndTimeRange ExecuteQueryArgumentName = "EndTimeRange" +) + +type ExecuteQueryOptions struct { + Limit *int `json:"limit,omitempty"` +} + +type ExecuteQueryArgument struct { + Name ExecuteQueryArgumentName `json:"name"` + Value string `json:"value"` +} + +type ExecuteQueryRequest struct { + Query ExecuteQuery `json:"query"` + Options ExecuteQueryOptions `json:"options"` + Arguments []ExecuteQueryArgument `json:"arguments"` +} + +type ExecuteQueryByIDRequest struct { + QueryID string `json:"queryId,omitempty"` + Options ExecuteQueryOptions `json:"options"` + Arguments []ExecuteQueryArgument `json:"arguments"` +} + +type ExecuteQueryData []interface{} + +func (d *ExecuteQueryData) UnmarshalJSON(data []byte) error { + type Alias ExecuteQueryData + + temp := (*Alias)(d) + reader := bytes.NewReader(data) + decoder := json.NewDecoder(reader) + decoder.UseNumber() + return decoder.Decode(temp) +} + +type ExecuteQueryResponse struct { + Data ExecuteQueryData `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +func validateQueryArguments(args []ExecuteQueryArgument) error { + var ( + hasStart, hasEnd bool + start, end time.Time + err error + ) + + for _, arg := range args { + if arg.Name == QueryStartTimeRange { + hasStart = true + start, err = validateQueryTimeString(arg.Value) + } + if err != nil { + return errors.Wrap(err, "invalid StartTimeRange argument") + } + + if arg.Name == QueryEndTimeRange { + hasEnd = true + end, err = validateQueryTimeString(arg.Value) + } + if err != nil { + return errors.Wrap(err, "invalid EndTimeRange argument") + } + } + + if hasStart && hasEnd { + return validateQueryRange(start, end) + } + return nil +} + +// StartTimeRange and EndTimeRange should be +func validateQueryTimeString(s string) (time.Time, error) { + return time.Parse(lwtime.RFC3339Milli, s) +} + +func validateQueryRange(start, end time.Time) (err error) { + // validate range + if start.After(end) { + err = errors.New("date range should have a start time before the end time") + return + } + return nil +} + +func (svc *QueryService) Execute(request ExecuteQueryRequest) ( + response ExecuteQueryResponse, + err error, +) { + if err = validateQueryArguments(request.Arguments); err != nil { + return + } + err = svc.client.RequestEncoderDecoder("POST", apiV2QueriesExecute, request, &response) + return +} + +func (svc *QueryService) ExecuteByID(request ExecuteQueryByIDRequest) ( + response ExecuteQueryResponse, + err error, +) { + if request.QueryID == "" { + err = errors.New("query ID must be provided") + return + } + queryID := request.QueryID + request.QueryID = "" // omit for POST + + if err = validateQueryArguments(request.Arguments); err != nil { + return + } + err = svc.client.RequestEncoderDecoder( + "POST", + fmt.Sprintf("%s/%s/execute", apiV2Queries, url.QueryEscape(queryID)), + request, + &response, + ) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/lql_validate.go b/vendor/github.com/lacework/go-sdk/api/lql_validate.go new file mode 100644 index 000000000..1349242bd --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/lql_validate.go @@ -0,0 +1,32 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +type ValidateQuery struct { + QueryText string `json:"queryText"` +} + +func (svc *QueryService) Validate(vq ValidateQuery) ( + response QueryResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder( + "POST", apiV2QueriesValidate, vq, &response) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/metrics.go b/vendor/github.com/lacework/go-sdk/api/metrics.go new file mode 100644 index 000000000..dbc7f7f3b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/metrics.go @@ -0,0 +1,110 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2024, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "os" + "runtime" +) + +const DisableTelemetry = "LW_TELEMETRY_DISABLE" + +// MetricsService is a service that sends events to Lacework APIv2 Server metrics endpoint +type MetricsService struct { + client *Client +} + +func (svc *MetricsService) Send(event Honeyvent) (response HoneyEventResponse, err error) { + if disabled := os.Getenv(DisableTelemetry); disabled != "" { + return HoneyEventResponse{Data: []Honeyvent{{TraceID: "Telemetry Disabled"}}}, nil + } + + event.setAccountDetails(*svc.client) + err = svc.client.RequestEncoderDecoder("POST", apiV2HoneyMetrics, event, &response) + return +} + +func NewHoneyvent(version, feature, dataset string) Honeyvent { + event := Honeyvent{ + Os: runtime.GOOS, + Arch: runtime.GOARCH, + TraceID: newID(), + Version: version, + Dataset: dataset, + Feature: feature, + } + + return event +} + +func (h *Honeyvent) setAccountDetails(client Client) { + if h.Account == "" { + h.Account = client.account + } + if h.Subaccount == "" { + h.Subaccount = client.subaccount + } +} + +// Honeyvent defines what a Honeycomb event looks like for the Lacework CLI +type Honeyvent struct { + Version string `json:"version"` + CfgVersion int `json:"config_version"` + Os string `json:"os"` + Arch string `json:"arch"` + Command string `json:"command,omitempty"` + Args []string `json:"args,omitempty"` + Flags []string `json:"flags,omitempty"` + Account string `json:"account,omitempty"` + Subaccount string `json:"subaccount,omitempty"` + Profile string `json:"profile,omitempty"` + ApiKey string `json:"api_key,omitempty"` + Feature string `json:"feature,omitempty"` + FeatureData interface{} `json:"feature.data,omitempty"` + DurationMs int64 `json:"duration_ms,omitempty"` + Error string `json:"error,omitempty"` + InstallMethod string `json:"install_method,omitempty"` + Component string `json:"component,omitempty"` + Dataset string `json:"dataset,omitempty"` + + // tracing data for multiple events, this is useful for specific features + // within the Lacework CLI such as daily version check, polling mechanism, etc. + TraceID string `json:"trace.trace_id,omitempty"` + SpanID string `json:"trace.span_id,omitempty"` + ParentID string `json:"trace.parent_id,omitempty"` + ContextID string `json:"trace.context_id,omitempty"` +} + +type HoneyEventResponse struct { + Data []Honeyvent `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +func (e *Honeyvent) AddFeatureField(key string, value interface{}) { + if e.FeatureData == nil { + e.FeatureData = map[string]interface{}{key: value} + return + } + + if v, ok := e.FeatureData.(map[string]interface{}); ok { + v[key] = value + e.FeatureData = v + } +} diff --git a/vendor/github.com/lacework/go-sdk/api/organization_info.go b/vendor/github.com/lacework/go-sdk/api/organization_info.go new file mode 100644 index 000000000..c15f77521 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/organization_info.go @@ -0,0 +1,53 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "github.com/lacework/go-sdk/lwdomain" + +// OrganizationInfoService is the service that interacts with +// the OrganizationInfo schema from the Lacework APIv2 Server +type OrganizationInfoService struct { + client *Client +} + +func (svc *OrganizationInfoService) Get() ( + response OrganizationInfoResponse, + err error, +) { + err = svc.client.RequestDecoder("GET", + apiV2OrganizationInfo, + nil, + &response, + ) + return +} + +type OrganizationInfoResponse struct { + Data []OrganizationInfo `json:"data"` +} + +type OrganizationInfo struct { + OrgAccount bool `json:"orgAccount"` + OrgAccountURL string `json:"orgAccountUrl,omitempty"` +} + +func (r OrganizationInfo) AccountName() string { + d, _ := lwdomain.New(r.OrgAccountURL) + return d.String() +} diff --git a/vendor/github.com/lacework/go-sdk/api/policy.go b/vendor/github.com/lacework/go-sdk/api/policy.go new file mode 100644 index 000000000..e5a832332 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/policy.go @@ -0,0 +1,364 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "reflect" + "time" + + "github.com/lacework/go-sdk/internal/array" + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +// PolicyService is a service that interacts with the Custom Policies +// endpoints from the Lacework Server +type PolicyService struct { + client *Client + Exceptions *policyExceptionsService +} + +func NewV2PolicyService(c *Client) *PolicyService { + return &PolicyService{c, + &policyExceptionsService{c}, + } +} + +type policyType int +type policyTypes map[policyType]string + +const ( + PolicyTypeCompliance policyType = iota + PolicyTypeManual + PolicyTypeViolation +) + +var ValidPolicyTypes = policyTypes{ + PolicyTypeCompliance: "Compliance", + PolicyTypeManual: "Manual", + PolicyTypeViolation: "Violation", +} + +func (p policyType) String() string { + return ValidPolicyTypes[p] +} + +func (pt policyTypes) String() (types []string) { + for _, v := range pt { + types = append(types, v) + } + return +} + +// ValidPolicySeverities is a list of all valid policy severities +var ValidPolicySeverities = []string{"critical", "high", "medium", "low", "info"} + +type NewPolicy struct { + PolicyID string `json:"policyId,omitempty" yaml:"policyId,omitempty" ` + PolicyType string `json:"policyType" yaml:"policyType"` + QueryID string `json:"queryId" yaml:"queryId"` + Title string `json:"title" yaml:"title"` + Enabled bool `json:"enabled" yaml:"enabled"` + Description string `json:"description" yaml:"description"` + Remediation string `json:"remediation" yaml:"remediation"` + Severity string `json:"severity" yaml:"severity"` + Limit int `json:"limit,omitempty" yaml:"limit,omitempty"` + EvalFrequency string `json:"evalFrequency,omitempty" yaml:"evalFrequency,omitempty"` + AlertEnabled bool `json:"alertEnabled" yaml:"alertEnabled"` + AlertProfile string `json:"alertProfile,omitempty" yaml:"alertProfile,omitempty"` + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` +} + +type newPoliciesYAML struct { + Policies []NewPolicy `yaml:"policies"` +} + +func ParseNewPolicy(s string) (NewPolicy, error) { + var policy NewPolicy + var err error + + // valid json + if err = json.Unmarshal([]byte(s), &policy); err == nil { + return policy, err + } + // nested yaml + var policies newPoliciesYAML + + if err = yaml.Unmarshal([]byte(s), &policies); err == nil { + if len(policies.Policies) > 0 { + return policies.Policies[0], err + } + } + // straight yaml + policy = NewPolicy{} + err = yaml.Unmarshal([]byte(s), &policy) + if err == nil && !reflect.DeepEqual(policy, NewPolicy{}) { // empty string unmarshals w/o error + return policy, nil + } + // invalid policy + return policy, errors.New("policy must be valid JSON or YAML") +} + +/* + In order to properly PATCH we need to omit items that aren't specified. + +For booleans and integers Golang will omit zero values false and 0 respectively. +This would prevent someone from toggling something to disabled or 0 respectively. +As such we are using pointers instead of primitives for booleans and integers in this struct +*/ +type UpdatePolicy struct { + PolicyID string `json:"policyId,omitempty" yaml:"policyId,omitempty"` + PolicyType string `json:"policyType,omitempty" yaml:"policyType,omitempty"` + QueryID string `json:"queryId,omitempty" yaml:"queryId,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Remediation string `json:"remediation,omitempty" yaml:"remediation,omitempty"` + Severity string `json:"severity,omitempty" yaml:"severity,omitempty"` + Limit *int `json:"limit,omitempty" yaml:"limit,omitempty"` + EvalFrequency string `json:"evalFrequency,omitempty" yaml:"evalFrequency,omitempty"` + AlertEnabled *bool `json:"alertEnabled,omitempty" yaml:"alertEnabled,omitempty"` + AlertProfile string `json:"alertProfile,omitempty" yaml:"alertProfile,omitempty"` + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` +} + +type updatePoliciesYAML struct { + Policies []UpdatePolicy `yaml:"policies"` +} + +func ParseUpdatePolicy(s string) (UpdatePolicy, error) { + var policy UpdatePolicy + var err error + + // valid json + if err = json.Unmarshal([]byte(s), &policy); err == nil { + return policy, err + } + // nested yaml + var policies updatePoliciesYAML + + if err = yaml.Unmarshal([]byte(s), &policies); err == nil { + if len(policies.Policies) > 0 { + return policies.Policies[0], err + } + } + // straight yaml + policy = UpdatePolicy{} + err = yaml.Unmarshal([]byte(s), &policy) + if err == nil && !reflect.DeepEqual(policy, UpdatePolicy{}) { // empty string unmarshals w/o error + return policy, nil + } + // invalid policy + return policy, errors.New("policy must be valid JSON or YAML") +} + +type ExceptionConfigMap map[string][]PolicyExceptionConfigurationConstraints +type Policy struct { + PolicyID string `json:"policyId" yaml:"policyId"` + PolicyType string `json:"policyType" yaml:"-"` + QueryID string `json:"queryId" yaml:"queryId"` + Title string `json:"title" yaml:"title"` + Enabled bool `json:"enabled" yaml:"enabled"` + Description string `json:"description" yaml:"description"` + Remediation string `json:"remediation" yaml:"remediation"` + Severity string `json:"severity" yaml:"severity"` + Limit int `json:"limit" yaml:"limit"` + EvalFrequency string `json:"evalFrequency" yaml:"evalFrequency"` + AlertEnabled bool `json:"alertEnabled" yaml:"alertEnabled"` + AlertProfile string `json:"alertProfile" yaml:"alertProfile"` + Tags []string `json:"tags" yaml:"tags"` + Owner string `json:"owner" yaml:"-"` + LastUpdateTime string `json:"lastUpdateTime" yaml:"-"` + LastUpdateUser string `json:"lastUpdateUser" yaml:"-"` + ExceptionConfiguration ExceptionConfigMap `json:"exceptionConfiguration" yaml:"-"` +} + +type PolicyExceptionConfigurationConstraints struct { + DataType string `json:"dataType" yaml:"dataType"` + FieldKey string `json:"fieldKey" yaml:"fieldKey"` + MultiValue bool `json:"multiValue" yaml:"multiValue"` +} + +func (p *Policy) HasTag(t string) bool { + return array.ContainsStr(p.Tags, t) +} + +type PolicyResponse struct { + Data Policy `json:"data"` + Message string `json:"message"` +} + +type PolicyTagsResponse struct { + Data []string `json:"data"` + Message string `json:"message"` +} + +type PoliciesResponse struct { + Data []Policy `json:"data"` + Message string `json:"message"` +} + +func (svc *PolicyService) Create(np NewPolicy) ( + response PolicyResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("POST", apiV2Policies, np, &response) + return +} + +func (svc *PolicyService) List() ( + response PoliciesResponse, + err error, +) { + err = svc.client.RequestDecoder("GET", apiV2Policies, nil, &response) + return +} + +func (svc *PolicyService) ListTags() ( + response PolicyTagsResponse, + err error, +) { + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf("%s/Tags", apiV2Policies), + nil, + &response, + ) + return +} + +func (svc *PolicyService) Get(policyID string) ( + response PolicyResponse, + err error, +) { + if policyID == "" { + err = errors.New("policy ID must be provided") + return + } + err = svc.client.RequestDecoder( + "GET", + fmt.Sprintf("%s/%s", apiV2Policies, url.QueryEscape(policyID)), + nil, + &response, + ) + return +} + +func (svc *PolicyService) Update(up UpdatePolicy) ( + response PolicyResponse, + err error, +) { + if up.PolicyID == "" { + err = errors.New("policy ID must be provided") + return + } + var policyID = up.PolicyID + up.PolicyID = "" // omit this for PATCH + + err = svc.client.RequestEncoderDecoder( + "PATCH", + fmt.Sprintf("%s/%s", apiV2Policies, url.QueryEscape(policyID)), + up, + &response, + ) + return +} + +func (svc *PolicyService) Delete(policyID string) ( + response PolicyResponse, + err error, +) { + if policyID == "" { + err = errors.New("policy ID must be provided") + return + } + err = svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf("%s/%s", apiV2Policies, url.QueryEscape(policyID)), + nil, + &response, + ) + return +} + +type BulkUpdatePolicy struct { + PolicyID string `json:"policyId,omitempty" yaml:"policyId,omitempty"` + Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + Severity string `json:"severity,omitempty" yaml:"severity,omitempty"` +} + +type BulkUpdatePolicies []BulkUpdatePolicy + +// UpdateMany supports updating the state(enabled/disabled) and severity of more than one +// policy using the policy bulk update api +func (svc *PolicyService) UpdateMany(policies BulkUpdatePolicies) ( + response BulkPolicyUpdateResponse, + err error, +) { + if len(policies) == 0 { + err = errors.New("a list of policies must be provided") + return + } + + err = svc.client.RequestEncoderDecoder( + "PATCH", + apiV2Policies, + policies, + &response, + ) + return +} + +type BulkPolicyUpdateResponse struct { + Data []BulkPolicyUpdateResponseData `json:"data"` +} + +type BulkPolicyUpdateResponseData struct { + EvaluatorId string `json:"evaluatorId,omitempty"` + PolicyId string `json:"policyId"` + PolicyType string `json:"policyType"` + QueryId string `json:"queryId,omitempty"` + QueryText string `json:"queryText,omitempty"` + Title string `json:"title"` + Enabled bool `json:"enabled,omitempty"` + Description string `json:"description"` + Remediation string `json:"remediation"` + Severity string `json:"severity"` + Limit int `json:"limit,omitempty"` + EvalFrequency string `json:"evalFrequency,omitempty"` + AlertEnabled bool `json:"alertEnabled,omitempty"` + AlertProfile string `json:"alertProfile,omitempty"` + Owner string `json:"owner"` + LastUpdateTime time.Time `json:"lastUpdateTime"` + LastUpdateUser string `json:"lastUpdateUser"` + Tags []string `json:"tags"` + InfoLink string `json:"infoLink,omitempty"` + ExceptionConfiguration struct { + ConstraintFields []struct { + FieldKey string `json:"fieldKey"` + DataType string `json:"dataType"` + MultiValue bool `json:"multiValue"` + } `json:"constraintFields"` + } `json:"exceptionConfiguration,omitempty"` + References []string `json:"references,omitempty"` + AdditionalInformation string `json:"additionalInformation,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/policy_exceptions.go b/vendor/github.com/lacework/go-sdk/api/policy_exceptions.go new file mode 100644 index 000000000..ba83e3825 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/policy_exceptions.go @@ -0,0 +1,111 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "errors" + "fmt" +) + +// policyExceptionsService is the service that interacts with +// the Exceptions schema from the Lacework APIv2 Server +type policyExceptionsService struct { + client *Client +} + +// List returns a list of the Policy Exceptions for a policy ID. +func (svc policyExceptionsService) List(policyID string) (response PolicyExceptionsResponse, err error) { + if policyID == "" { + return response, errors.New("specify a policy ID") + } + err = svc.client.RequestDecoder("GET", fmt.Sprintf(apiV2PolicyExceptions, policyID), nil, &response) + return +} + +// Get returns a raw response of the Policy Exception with the matching policy ID and exception ID. +func (svc policyExceptionsService) Get(policyID string, exceptionID string, response interface{}) error { + if exceptionID == "" || policyID == "" { + return errors.New("specify exception and policy IDs") + } + apiPath := fmt.Sprintf(apiV2PolicyExceptionsFromExceptionID, exceptionID, policyID) + return svc.client.RequestDecoder("GET", apiPath, nil, &response) +} + +func (svc policyExceptionsService) Delete(policyID string, exceptionID string) error { + if exceptionID == "" || policyID == "" { + return errors.New("specify exception and policy IDs") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2PolicyExceptionsFromExceptionID, exceptionID, policyID), + nil, + nil, + ) +} + +// Create creates a single Policy Exception +func (svc *policyExceptionsService) Create(policyID string, policy PolicyException) ( + response PolicyExceptionResponse, + err error, +) { + if policyID == "" { + return response, errors.New("specify a policy ID") + } + err = svc.client.RequestEncoderDecoder("POST", fmt.Sprintf(apiV2PolicyExceptions, policyID), + policy, &response) + return +} + +// Update updates a single Policy Exception +func (svc policyExceptionsService) Update(policyID string, exception PolicyException) ( + response PolicyExceptionResponse, err error, +) { + if exception.ExceptionID == "" || policyID == "" { + return response, errors.New("specify exception and policy IDs") + } + apiPath := fmt.Sprintf(apiV2PolicyExceptionsFromExceptionID, exception.ExceptionID, policyID) + // Request is invalid if it contains the ExceptionID, LastUpdatedTime or LastUpdatedUser fields. + exception.ExceptionID = "" + exception.LastUpdateUser = "" + exception.LastUpdateTime = "" + + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, exception, &response) + return +} + +type PolicyExceptionResponse struct { + Data PolicyException `json:"data"` +} + +type PolicyExceptionsResponse struct { + Data []PolicyException `json:"data"` +} +type PolicyException struct { + ExceptionID string `json:"exceptionId,omitempty"` + Description string `json:"description"` + Constraints []PolicyExceptionConstraint `json:"constraints"` + LastUpdateTime string `json:"lastUpdateTime,omitempty"` + LastUpdateUser string `json:"lastUpdateUser,omitempty"` +} + +type PolicyExceptionConstraint struct { + FieldKey string `json:"fieldKey"` + FieldValues []any `json:"fieldValues"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/reader.go b/vendor/github.com/lacework/go-sdk/api/reader.go new file mode 100644 index 000000000..931264872 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/reader.go @@ -0,0 +1,33 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "bytes" + "encoding/json" + "io" +) + +// jsonReader takes any arbitrary type and synthesizes a streaming encoder +func jsonReader(v interface{}) (r io.Reader, err error) { + buf := new(bytes.Buffer) + err = json.NewEncoder(buf).Encode(v) + r = bytes.NewReader(buf.Bytes()) + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/report_rule_notification_types.go b/vendor/github.com/lacework/go-sdk/api/report_rule_notification_types.go new file mode 100644 index 000000000..77a29d756 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/report_rule_notification_types.go @@ -0,0 +1,269 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/json" + "fmt" + + "github.com/fatih/structs" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +type reportRuleNotification interface { + ToMap() map[string]bool +} + +type ReportRuleNotifications []reportRuleNotification + +// Enable all Gcp report rules +var AllGcpReportRuleNotifications = new(GcpReportRuleNotifications).allNotifications() + +// Enable all Aws report rules +var AllAwsReportRuleNotifications = new(AwsReportRuleNotifications).allNotifications() + +// Enable all Azure report rules +var AllAzureReportRuleNotifications = new(AzureReportRuleNotifications).allNotifications() + +// Enable all Daily report rules +var AllDailyReportRuleNotifications = new(DailyEventsReportRuleNotifications).allNotifications() + +// Enable all Weekly report rules +var AllWeeklyReportRuleNotifications = new(WeeklyEventsReportRuleNotifications).allNotifications() + +// Enable all report rules +var AllReportRuleNotifications = new(ReportRuleNotificationTypes).allNotifications() + +func TransformReportRuleNotification(notificationsMap map[string]bool, notificationType reportRuleNotification) error { + jsonMap, err := json.Marshal(notificationsMap) + if err != nil { + return err + } + + err = json.Unmarshal(jsonMap, ¬ificationType) + if err != nil { + return err + } + + return nil +} + +func NewReportRuleNotificationTypes(types []reportRuleNotification) (ReportRuleNotificationTypes, error) { + notificationsTypes := ReportRuleNotificationTypes{} + notificationsMap := make(map[string]bool) + + for _, notificationType := range types { + m := structs.Map(notificationType) + for k, v := range m { + if _, ok := notificationsMap[k]; ok { + return ReportRuleNotificationTypes{}, errors.New(fmt.Sprintf("notification types contains a duplicate type: %s", k)) + } + notificationsMap[k] = v.(bool) + } + } + + err := mapstructure.Decode(notificationsMap, ¬ificationsTypes) + if err != nil { + return ReportRuleNotificationTypes{}, errors.New("unable to set report rule notification types") + } + + return notificationsTypes, nil +} + +func reportRuleNotificationToMap(notificationType reportRuleNotification) map[string]bool { + notificationsMap := make(map[string]bool) + m := structs.Map(notificationType) + for k, v := range m { + if v.(bool) { + notificationsMap[k] = true + } else { + notificationsMap[k] = false + } + } + return notificationsMap +} + +type GcpReportRuleNotifications struct { + GcpCis bool `json:"gcpCis"` + GcpHipaa bool `json:"gcpHipaa"` + GcpHipaaRev2 bool `json:"gcpHipaaRev2"` + GcpIso27001 bool `json:"gcpIso27001"` + GcpCis12 bool `json:"gcpCis12"` + GcpK8s bool `json:"gcpK8s"` + GcpPci bool `json:"gcpPci"` + GcpPciRev2 bool `json:"gcpPciRev2"` + GcpSoc bool `json:"gcpSoc"` + GcpSocRev2 bool `json:"gcpSocRev2"` +} + +func (gcp GcpReportRuleNotifications) allNotifications() GcpReportRuleNotifications { + return GcpReportRuleNotifications{ + GcpCis: true, + GcpHipaa: true, + GcpHipaaRev2: true, + GcpIso27001: true, + GcpCis12: true, + GcpK8s: true, + GcpPci: true, + GcpPciRev2: true, + GcpSoc: true, + GcpSocRev2: true, + } +} + +func (gcp GcpReportRuleNotifications) ToMap() map[string]bool { + return reportRuleNotificationToMap(gcp) +} + +type AwsReportRuleNotifications struct { + AwsCisS3 bool `json:"awsCisS3"` + AwsHipaa bool `json:"hipaa"` + AwsIso2700 bool `json:"iso2700"` + AwsNist80053Rev4 bool `json:"nist800-53Rev4"` + AwsNist800171Rev2 bool `json:"nist800-171Rev2"` + AwsPci bool `json:"pci"` + AwsSoc bool `json:"soc"` + AwsSocRev2 bool `json:"awsSocRev2"` +} + +func (aws AwsReportRuleNotifications) allNotifications() AwsReportRuleNotifications { + return AwsReportRuleNotifications{ + AwsCisS3: true, + AwsHipaa: true, + AwsIso2700: true, + AwsNist80053Rev4: true, + AwsNist800171Rev2: true, + AwsPci: true, + AwsSoc: true, + AwsSocRev2: true, + } +} + +func (aws AwsReportRuleNotifications) ToMap() map[string]bool { + return reportRuleNotificationToMap(aws) +} + +type AzureReportRuleNotifications struct { + AzureCis bool `json:"azureCis"` + AzureCis131 bool `json:"azureCis131"` + AzurePci bool `json:"azurePci"` + AzureSoc bool `json:"azureSoc"` +} + +func (az AzureReportRuleNotifications) allNotifications() AzureReportRuleNotifications { + return AzureReportRuleNotifications{ + AzureCis: true, + AzureCis131: true, + AzurePci: true, + AzureSoc: true, + } +} + +func (az AzureReportRuleNotifications) ToMap() map[string]bool { + return reportRuleNotificationToMap(az) +} + +type DailyEventsReportRuleNotifications struct { + AgentEvents bool `json:"agentEvents"` + OpenShiftCompliance bool `json:"openShiftCompliance"` + OpenShiftComplianceEvents bool `json:"openShiftComplianceEvents"` + PlatformEvents bool `json:"platformEvents"` + AwsCloudtrailEvents bool `json:"awsCloudtrailEvents"` + AwsComplianceEvents bool `json:"awsComplianceEvents"` + AzureComplianceEvents bool `json:"azureComplianceEvents"` + AzureActivityLogEvents bool `json:"azureActivityLogEvents"` + GcpAuditTrailEvents bool `json:"gcpAuditTrailEvents"` + GcpComplianceEvents bool `json:"gcpComplianceEvents"` +} + +func (daily DailyEventsReportRuleNotifications) allNotifications() DailyEventsReportRuleNotifications { + return DailyEventsReportRuleNotifications{ + AgentEvents: true, + OpenShiftCompliance: true, + OpenShiftComplianceEvents: true, + PlatformEvents: true, + AwsCloudtrailEvents: true, + AwsComplianceEvents: true, + AzureComplianceEvents: true, + AzureActivityLogEvents: true, + GcpAuditTrailEvents: true, + GcpComplianceEvents: true, + } +} + +func (daily DailyEventsReportRuleNotifications) ToMap() map[string]bool { + return reportRuleNotificationToMap(daily) +} + +type WeeklyEventsReportRuleNotifications struct { + TrendReport bool `json:"trendReport"` +} + +func (weekly WeeklyEventsReportRuleNotifications) allNotifications() WeeklyEventsReportRuleNotifications { + return WeeklyEventsReportRuleNotifications{ + TrendReport: true, + } +} + +func (weekly WeeklyEventsReportRuleNotifications) ToMap() map[string]bool { + return reportRuleNotificationToMap(weekly) +} + +func (all ReportRuleNotificationTypes) allNotifications() ReportRuleNotificationTypes { + return ReportRuleNotificationTypes{ + AgentEvents: true, + AwsCisS3: true, + AwsCloudtrailEvents: true, + AwsComplianceEvents: true, + AwsHipaa: true, + AwsIso2700: true, + AwsNist80053Rev4: true, + AwsNist800171Rev2: true, + AwsPci: true, + AwsSoc: true, + AwsSocRev2: true, + AzureActivityLogEvents: true, + AzureCis: true, + AzureCis131: true, + AzureComplianceEvents: true, + AzurePci: true, + AzureSoc: true, + GcpAuditTrailEvents: true, + GcpCis: true, + GcpComplianceEvents: true, + GcpHipaa: true, + GcpHipaaRev2: true, + GcpIso27001: true, + GcpCis12: true, + GcpK8s: true, + GcpPci: true, + GcpPciRev2: true, + GcpSoc: true, + GcpSocRev2: true, + OpenShiftCompliance: true, + OpenShiftComplianceEvents: true, + PlatformEvents: true, + TrendReport: true, + } +} + +func (all ReportRuleNotificationTypes) ToMap() map[string]bool { + return reportRuleNotificationToMap(all) +} diff --git a/vendor/github.com/lacework/go-sdk/api/report_rules.go b/vendor/github.com/lacework/go-sdk/api/report_rules.go new file mode 100644 index 000000000..6f784849c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/report_rules.go @@ -0,0 +1,305 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" +) + +// ReportRulesService is the service that interacts with +// the ReportRules schema from the Lacework APIv2 Server +type ReportRulesService struct { + client *Client +} + +type reportRuleSeverity int + +type ReportRuleSeverities []reportRuleSeverity + +const ReportRuleEventType = "Report" + +func (sevs ReportRuleSeverities) toInt() []int { + var res []int + for _, i := range sevs { + res = append(res, int(i)) + } + return res +} + +func (sevs ReportRuleSeverities) ToStringSlice() []string { + var res []string + for _, i := range sevs { + switch i { + case ReportRuleSeverityCritical: + res = append(res, "Critical") + case ReportRuleSeverityHigh: + res = append(res, "High") + case ReportRuleSeverityMedium: + res = append(res, "Medium") + case ReportRuleSeverityLow: + res = append(res, "Low") + case ReportRuleSeverityInfo: + res = append(res, "Info") + default: + continue + } + } + return res +} + +func NewReportRuleSeverities(sevSlice []string) ReportRuleSeverities { + var res ReportRuleSeverities + for _, i := range sevSlice { + sev := convertReportRuleSeverity(i) + if sev != ReportRuleSeverityUnknown { + res = append(res, sev) + } + } + return res +} + +func NewReportRuleSeveritiesFromIntSlice(sevSlice []int) ReportRuleSeverities { + var res ReportRuleSeverities + for _, i := range sevSlice { + sev := convertReportRuleSeverityInt(i) + if sev != ReportRuleSeverityUnknown { + res = append(res, sev) + } + } + return res +} + +func convertReportRuleSeverity(sev string) reportRuleSeverity { + switch strings.ToLower(sev) { + case "critical": + return ReportRuleSeverityCritical + case "high": + return ReportRuleSeverityHigh + case "medium": + return ReportRuleSeverityMedium + case "low": + return ReportRuleSeverityLow + case "info": + return ReportRuleSeverityInfo + default: + return ReportRuleSeverityUnknown + } +} + +func convertReportRuleSeverityInt(sev int) reportRuleSeverity { + switch sev { + case 1: + return ReportRuleSeverityCritical + case 2: + return ReportRuleSeverityHigh + case 3: + return ReportRuleSeverityMedium + case 4: + return ReportRuleSeverityLow + case 5: + return ReportRuleSeverityInfo + default: + return ReportRuleSeverityUnknown + } +} + +const ( + ReportRuleSeverityCritical reportRuleSeverity = 1 + ReportRuleSeverityHigh reportRuleSeverity = 2 + ReportRuleSeverityMedium reportRuleSeverity = 3 + ReportRuleSeverityLow reportRuleSeverity = 4 + ReportRuleSeverityInfo reportRuleSeverity = 5 + ReportRuleSeverityUnknown reportRuleSeverity = 0 +) + +// NewReportRule returns an instance of the ReportRule struct +// +// Basic usage: Initialize a new ReportRule struct, then +// +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// reportRule := api.NewReportRule( +// "Foo", +// api.ReportRuleConfig{ +// Description: "My Report Rule" +// Severities: api.ReportRuleSeverities{api.ReportRuleSeverityHigh, +// EmailAlertChannels: []string{"TECHALLY_000000000000AAAAAAAAAAAAAAAAAAAA"}, +// ResourceGroups: []string{"TECHALLY_111111111111AAAAAAAAAAAAAAAAAAAA"} +// ReportNotificationTypes: api.WeeklyEventsReportRuleNotifications{TrendReport: true}, +// }, +// }, +// ) +// +// client.V2.ReportRules.Create(reportRule) +func NewReportRule(name string, rule ReportRuleConfig) (ReportRule, error) { + notifications, err := NewReportRuleNotificationTypes(rule.NotificationTypes) + if err != nil { + return ReportRule{}, err + } + + return ReportRule{ + EmailAlertChannels: rule.EmailAlertChannels, + Type: ReportRuleEventType, + Filter: ReportRuleFilter{ + Name: name, + Enabled: 1, + Description: rule.Description, + Severity: rule.Severities.toInt(), + ResourceGroups: rule.ResourceGroups, + }, + ReportNotificationTypes: notifications, + }, nil +} + +func (rule ReportRuleFilter) Status() string { + if rule.Enabled == 1 { + return "Enabled" + } + return "Disabled" +} + +// List returns a list of Report Rules +func (svc *ReportRulesService) List() (response ReportRulesResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2ReportRules, nil, &response) + return +} + +// Create creates a single Report Rule +func (svc *ReportRulesService) Create(rule ReportRule) ( + response ReportRuleResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("POST", apiV2ReportRules, rule, &response) + return +} + +// Delete deletes a Report Rule that matches the provided guid +func (svc *ReportRulesService) Delete(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2ReportRuleFromGUID, guid), + nil, + nil, + ) +} + +// Update updates a single Report Rule of the provided guid. +func (svc *ReportRulesService) Update(data ReportRule) ( + response ReportRuleResponse, + err error, +) { + if data.Guid == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2ReportRuleFromGUID, data.Guid) + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) + return +} + +// Get returns a raw response of the Report Rule with the matching guid. +func (svc *ReportRulesService) Get(guid string, response interface{}) error { + if guid == "" { + return errors.New("specify a Guid") + } + apiPath := fmt.Sprintf(apiV2ReportRuleFromGUID, guid) + return svc.client.RequestDecoder("GET", apiPath, nil, &response) +} + +type ReportRuleConfig struct { + EmailAlertChannels []string + Description string + Severities ReportRuleSeverities + NotificationTypes []reportRuleNotification + ResourceGroups []string +} + +type ReportRule struct { + Guid string `json:"mcGuid,omitempty"` + Type string `json:"type"` + EmailAlertChannels []string `json:"intgGuidList"` + Filter ReportRuleFilter `json:"filters"` + ReportNotificationTypes ReportRuleNotificationTypes `json:"reportNotificationTypes"` +} + +type ReportRuleNotificationTypes struct { + AgentEvents bool `json:"agentEvents"` + AwsCisS3 bool `json:"awsCisS3"` + AwsCloudtrailEvents bool `json:"awsCloudtrailEvents"` + AwsComplianceEvents bool `json:"awsComplianceEvents"` + AwsHipaa bool `json:"hipaa"` + AwsIso2700 bool `json:"iso2700"` + AwsNist80053Rev4 bool `json:"nist800-53Rev4"` + AwsNist800171Rev2 bool `json:"nist800-171Rev2"` + AwsPci bool `json:"pci"` + AwsSoc bool `json:"soc"` + AwsSocRev2 bool `json:"awsSocRev2"` + AzureActivityLogEvents bool `json:"azureActivityLogEvents"` + AzureCis bool `json:"azureCis"` + AzureCis131 bool `json:"azureCis131"` + AzureComplianceEvents bool `json:"azureComplianceEvents"` + AzurePci bool `json:"azurePci"` + AzureSoc bool `json:"azureSoc"` + GcpAuditTrailEvents bool `json:"gcpAuditTrailEvents"` + GcpCis bool `json:"gcpCis"` + GcpComplianceEvents bool `json:"gcpComplianceEvents"` + GcpHipaa bool `json:"gcpHipaa"` + GcpHipaaRev2 bool `json:"gcpHipaaRev2"` + GcpIso27001 bool `json:"gcpIso27001"` + GcpCis12 bool `json:"gcpCis12"` + GcpK8s bool `json:"gcpK8s"` + GcpPci bool `json:"gcpPci"` + GcpPciRev2 bool `json:"gcpPciRev2"` + GcpSoc bool `json:"gcpSoc"` + GcpSocRev2 bool `json:"gcpSocRev2"` + OpenShiftCompliance bool `json:"openShiftCompliance"` + OpenShiftComplianceEvents bool `json:"openShiftComplianceEvents"` + PlatformEvents bool `json:"platformEvents"` + TrendReport bool `json:"trendReport"` +} + +type ReportRuleFilter struct { + Name string `json:"name"` + Enabled int `json:"enabled"` + Description string `json:"description,omitempty"` + Severity []int `json:"severity"` + ResourceGroups []string `json:"resourceGroups,omitempty"` + CreatedOrUpdatedTime string `json:"createdOrUpdatedTime,omitempty"` + CreatedOrUpdatedBy string `json:"createdOrUpdatedBy,omitempty"` +} + +type ReportRuleResponse struct { + Data ReportRule `json:"data"` +} + +type ReportRulesResponse struct { + Data []ReportRule `json:"data"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/reports.go b/vendor/github.com/lacework/go-sdk/api/reports.go new file mode 100644 index 000000000..5fbc130ba --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/reports.go @@ -0,0 +1,123 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// ReportsService is a service that interacts with the Reports +// endpoints from the Lacework APIv2 Server +type ReportsService struct { + client *Client + Aws *awsReportsService + Azure *azureReportsService + Gcp *gcpReportsService +} + +func NewReportsService(c *Client) *ReportsService { + return &ReportsService{c, + &awsReportsService{c}, + &azureReportsService{c}, + &gcpReportsService{c}, + } +} + +// The method by which a report can be retrieved from v2/Reports/ api +// can be 'reportName' or 'reportType' +type reportFilter int + +const ( + ReportFilterType reportFilter = iota + ReportFilterName +) + +// reportFilterTypes is the list of available report filter types +var reportFilterTypes = map[reportFilter]string{ + ReportFilterType: "reportType", + ReportFilterName: "reportName", +} + +func (r reportFilter) String() string { + return reportFilterTypes[r] +} + +type ReportSummary struct { + NumRecommendations int `json:"NUM_RECOMMENDATIONS"` + NumSeverity2NonCompliance int `json:"NUM_SEVERITY_2_NON_COMPLIANCE"` + NumSeverity4NonCompliance int `json:"NUM_SEVERITY_4_NON_COMPLIANCE"` + NumSeverity1NonCompliance int `json:"NUM_SEVERITY_1_NON_COMPLIANCE"` + NumCompliant int `json:"NUM_COMPLIANT"` + NumSeverity3NonCompliance int `json:"NUM_SEVERITY_3_NON_COMPLIANCE"` + AssessedResourceCount int `json:"ASSESSED_RESOURCE_COUNT"` + NumSuppressed int `json:"NUM_SUPPRESSED"` + NumSeverity5NonCompliance int `json:"NUM_SEVERITY_5_NON_COMPLIANCE"` + NumNotComplinace int `json:"NUM_NOT_COMPLIANT"` + ViolatedResourceCount int `json:"VIOLATED_RESOURCE_COUNT"` + SuppressedResourceCount int `json:"SUPPRESSED_RESOURCE_COUNT"` +} + +type RecommendationV2 struct { + AccountID string `json:"ACCOUNT_ID"` + AccountAlias string `json:"ACCOUNT_ALIAS"` + Service string `json:"SERVICE"` + StartTime int64 `json:"START_TIME"` + Suppressions []string `json:"SUPPRESSIONS"` + InfoLink string `json:"INFO_LINK"` + AssessedResourceCount int `json:"ASSESSED_RESOURCE_COUNT"` + Status string `json:"STATUS"` + RecID string `json:"REC_ID"` + Category string `json:"CATEGORY"` + Title string `json:"TITLE"` + Violations []ComplianceViolationV2 `json:"VIOLATIONS"` + ResourceCount int `json:"RESOURCE_COUNT"` + Severity int `json:"SEVERITY"` +} + +type ComplianceViolationV2 struct { + Region string `json:"region"` + Resource string `json:"resource"` + Reasons []string `json:"reasons"` +} + +func (r *RecommendationV2) SeverityString() string { + switch r.Severity { + case 1: + return "Critical" + case 2: + return "High" + case 3: + return "Medium" + case 4: + return "Low" + case 5: + return "Info" + default: + return "Unknown" + } +} + +type CloudComplianceReportV2 interface { + GetComplianceRecommendation(recommendationID string) (*RecommendationV2, bool) +} + +// ValidComplianceStatus is a list of all valid compliance status +var ValidComplianceStatus = []string{ + "non-compliant", + "requires-manual-assessment", + "suppressed", + "compliant", + "could-not-assess", +} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_aws.go b/vendor/github.com/lacework/go-sdk/api/reports_aws.go new file mode 100644 index 000000000..f793a3db1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/reports_aws.go @@ -0,0 +1,174 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "io" + "os" + "sort" + "time" + + "github.com/pkg/errors" +) + +// v2AwsReportsService is a service that interacts with the APIv2 +type awsReportsService struct { + client *Client +} + +const ComplianceReportDefaultAws = "CIS Amazon Web Services Foundations Benchmark v1.4.0" + +type AwsReportConfig struct { + AccountID string + Value string + Parameter reportFilter +} + +type AwsReportType int + +func (report AwsReportType) String() string { + return awsReportTypes[report] +} + +func NewAwsReportType(report string) (AwsReportType, error) { + for k, v := range awsReportTypes { + if v == report { + return k, nil + } + } + return NONE_AWS_REPORT, errors.Errorf("no report type found for %s", report) +} + +func AwsReportTypes() []string { + reportTypes := make([]string, 0, len(awsReportTypes)) + + for _, report := range awsReportTypes { + reportTypes = append(reportTypes, report) + } + + sort.Strings(reportTypes) + return reportTypes +} + +var awsReportTypes = map[AwsReportType]string{AWS_CIS_S3: "AWS_CIS_S3", NIST_800_53_Rev4: "NIST_800-53_Rev4", + NIST_800_171_Rev2: "NIST_800-171_Rev2", ISO_2700: "ISO_2700", HIPAA: "HIPAA", SOC: "SOC", + AWS_SOC_Rev2: "AWS_SOC_Rev2", PCI: "PCI", AWS_CIS_14: "AWS_CIS_14", AWS_CMMC_1_02: "AWS_CMMC_1.02", + AWS_ISO_27001_2013: "AWS_ISO_27001:2013", AWS_NIST_CSF: "AWS_NIST_CSF", AWS_HIPAA: "AWS_HIPAA", + AWS_NIST_800_53_rev5: "AWS_NIST_800-53_rev5", AWS_NIST_800_171_rev2: "AWS_NIST_800-171_rev2", + AWS_PCI_DSS_3_2_1: "AWS_PCI_DSS_3.2.1", AWS_SOC_2: "AWS_SOC_2", LW_AWS_SEC_ADD_1_0: "LW_AWS_SEC_ADD_1_0", + AWS_CIS_1_4_ISO_IEC_27002_2022: "AWS_CIS_1_4_ISO_IEC_27002_2022", AWS_CYBER_ESSENTIALS_2_2: "AWS_Cyber_Essentials_2_2", + AWS_CSA_CCM_4_0_5: "AWS_CSA_CCM_4_0_5"} + +const ( + NONE_AWS_REPORT AwsReportType = iota + AWS_CIS_S3 + NIST_800_53_Rev4 + NIST_800_171_Rev2 + ISO_2700 + HIPAA + SOC + AWS_SOC_Rev2 + PCI + AWS_CIS_14 + AWS_CMMC_1_02 + AWS_HIPAA + AWS_ISO_27001_2013 + AWS_NIST_CSF + AWS_NIST_800_171_rev2 + AWS_NIST_800_53_rev5 + AWS_PCI_DSS_3_2_1 + AWS_SOC_2 + LW_AWS_SEC_ADD_1_0 + AWS_CIS_1_4_ISO_IEC_27002_2022 + AWS_CYBER_ESSENTIALS_2_2 + AWS_CSA_CCM_4_0_5 +) + +// Get returns an AwsReportResponse +func (svc *awsReportsService) Get(reportCfg AwsReportConfig) (response AwsReportResponse, err error) { + if reportCfg.AccountID == "" { + return AwsReportResponse{}, errors.New("specify an account id") + } + + apiPath := fmt.Sprintf(apiV2Reports, reportCfg.AccountID, "json", reportCfg.Parameter.String(), reportCfg.Value) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +func (svc *awsReportsService) DownloadPDF(filepath string, config AwsReportConfig) error { + if config.AccountID == "" { + return errors.New("account id is required") + } + + // if name is set in config, fetch report by case + apiPath := fmt.Sprintf(apiV2Reports, config.AccountID, "pdf", config.Parameter.String(), config.Value) + + request, err := svc.client.NewRequest("GET", apiPath, nil) + if err != nil { + return err + } + + response, err := svc.client.Do(request) + if err != nil { + return err + } + defer response.Body.Close() + + err = checkErrorInResponse(response) + if err != nil { + return err + } + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, response.Body) + return err +} + +func (aws AwsReport) GetComplianceRecommendation(recommendationID string) (*RecommendationV2, bool) { + for _, r := range aws.Recommendations { + if r.RecID == recommendationID { + return &r, true + } + } + return nil, false +} + +type AwsReportResponse struct { + Data []AwsReport `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +type AwsReport struct { + ReportType string `json:"reportType"` + ReportTitle string `json:"reportTitle"` + Recommendations []RecommendationV2 `json:"recommendations"` + Summary []ReportSummary `json:"summary"` + AccountID string `json:"accountId"` + AccountAlias string `json:"accountAlias"` + ReportTime time.Time `json:"reportTime"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_azure.go b/vendor/github.com/lacework/go-sdk/api/reports_azure.go new file mode 100644 index 000000000..b58a08e86 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/reports_azure.go @@ -0,0 +1,183 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "io" + "os" + "sort" + "time" + + "github.com/pkg/errors" +) + +// v2AzureReportsService is a service that interacts with the APIv2 +type azureReportsService struct { + client *Client +} + +const ComplianceReportDefaultAzure = "CIS Microsoft Azure Foundations Benchmark v1.5.0" + +type AzureReportConfig struct { + TenantID string + SubscriptionID string + Value string + Parameter reportFilter +} + +type AzureReportType int + +func (report AzureReportType) String() string { + return azureReportTypes[report] +} + +func NewAzureReportType(report string) (AzureReportType, error) { + for k, v := range azureReportTypes { + if v == report { + return k, nil + } + } + return NONE_AZURE_REPORT, errors.Errorf("no report type found for %s", report) +} + +func AzureReportTypes() []string { + reportTypes := make([]string, 0, len(azureReportTypes)) + + for _, report := range azureReportTypes { + reportTypes = append(reportTypes, report) + } + + sort.Strings(reportTypes) + return reportTypes +} + +var azureReportTypes = map[AzureReportType]string{ + AZURE_CIS: "AZURE_CIS", + AZURE_CIS_131: "AZURE_CIS_131", + AZURE_SOC: "AZURE_SOC", + AZURE_SOC_Rev2: "AZURE_SOC_Rev2", + AZURE_PCI: "AZURE_PCI", + AZURE_PCI_Rev2: "AZURE_PCI_Rev2", + AZURE_ISO_27001: "AZURE_ISO_27001", + AZURE_NIST_CSF: "AZURE_NIST_CSF", + AZURE_NIST_800_53_REV5: "AZURE_NIST_800_53_REV5", + AZURE_NIST_800_171_REV2: "AZURE_NIST_800_171_REV2", + AZURE_HIPAA: "AZURE_HIPAA", +} + +const ( + NONE_AZURE_REPORT AzureReportType = iota + AZURE_CIS + AZURE_CIS_131 + AZURE_SOC + AZURE_SOC_Rev2 + AZURE_PCI + AZURE_PCI_Rev2 + AZURE_ISO_27001 + AZURE_NIST_CSF + AZURE_NIST_800_53_REV5 + AZURE_NIST_800_171_REV2 + AZURE_HIPAA +) + +// Get returns an AzureReportResponse +func (svc *azureReportsService) Get(reportCfg AzureReportConfig) (response AzureReportResponse, err error) { + if reportCfg.SubscriptionID == "" { + return AzureReportResponse{}, errors.New("specify an account id") + } + + apiPath := fmt.Sprintf(apiV2ReportsSecondaryQuery, + reportCfg.TenantID, + reportCfg.SubscriptionID, + "json", + reportCfg.Parameter.String(), + reportCfg.Value, + ) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +func (svc *azureReportsService) DownloadPDF(filepath string, config AzureReportConfig) error { + if config.TenantID == "" || config.SubscriptionID == "" { + return errors.New("tenant_id and subscription_id are required") + } + + apiPath := fmt.Sprintf(apiV2ReportsSecondaryQuery, + config.TenantID, + config.SubscriptionID, + "pdf", + config.Parameter.String(), + config.Value, + ) + + request, err := svc.client.NewRequest("GET", apiPath, nil) + if err != nil { + return err + } + + response, err := svc.client.Do(request) + if err != nil { + return err + } + defer response.Body.Close() + + err = checkErrorInResponse(response) + if err != nil { + return err + } + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, response.Body) + return err +} + +func (azure AzureReport) GetComplianceRecommendation(recommendationID string) (*RecommendationV2, bool) { + for _, r := range azure.Recommendations { + if r.RecID == recommendationID { + return &r, true + } + } + return nil, false +} + +type AzureReportResponse struct { + Data []AzureReport `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +type AzureReport struct { + ReportType string `json:"reportType"` + ReportTitle string `json:"reportTitle"` + Recommendations []RecommendationV2 `json:"recommendations"` + Summary []ReportSummary `json:"summary"` + ReportTime time.Time `json:"reportTime"` + SubscriptionName string `json:"subscriptionName"` + SubscriptionID string `json:"SubscriptionID"` + TenantName string `json:"tenantName"` + TenantID string `json:"tenantId"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_definitions.go b/vendor/github.com/lacework/go-sdk/api/reports_definitions.go new file mode 100644 index 000000000..b15466bbc --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/reports_definitions.go @@ -0,0 +1,237 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "time" + + "github.com/pkg/errors" +) + +// ReportDefinitionsService is a service that interacts with the ReportDefinitions +// endpoints from the Lacework APIv2 Server +type ReportDefinitionsService struct { + client *Client +} + +// The report definition type. At present "COMPLIANCE" is the only supported type for custom report definitions +type reportDefinitionType int + +const ( + ReportDefinitionTypeCompliance reportDefinitionType = iota +) + +func (reportType reportDefinitionType) String() string { + return reportDefinitionTypes[reportType] +} + +var reportDefinitionTypes = map[reportDefinitionType]string{ + ReportDefinitionTypeCompliance: "COMPLIANCE", +} + +// The report definition subtype. Supported values are "AWS", "Azure", "GCP" +type reportDefinitionSubType int + +const ( + ReportDefinitionSubTypeAws reportDefinitionSubType = iota + ReportDefinitionSubTypeGcp + ReportDefinitionSubTypeAzure +) + +func (subType reportDefinitionSubType) String() string { + return reportDefinitionSubTypes[subType] +} + +func ReportDefinitionSubTypes() (values []string) { + for _, v := range reportDefinitionSubTypes { + values = append(values, v) + } + return +} + +var reportDefinitionSubTypes = map[reportDefinitionSubType]string{ + ReportDefinitionSubTypeAws: "AWS", + ReportDefinitionSubTypeGcp: "GCP", + ReportDefinitionSubTypeAzure: "Azure", +} + +// List returns a ReportDefinitionResponse +func (svc *ReportDefinitionsService) List() (response ReportDefinitionsResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2ReportDefinitions, nil, &response) + return +} + +// Get returns a ReportDefinitionResponse +func (svc *ReportDefinitionsService) Get(guid string) (response ReportDefinitionResponse, err error) { + if guid == "" { + return ReportDefinitionResponse{}, errors.New("specify a report definition guid") + } + apiPath := fmt.Sprintf(apiV2ReportDefinitionsFromGUID, guid) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +// GetVersions returns a list of all versions of a reportDefinition +func (svc *ReportDefinitionsService) GetVersions(guid string) (response ReportDefinitionsResponse, err error) { + if guid == "" { + return ReportDefinitionsResponse{}, errors.New("specify a report definition guid") + } + apiPath := fmt.Sprintf(apiV2ReportDefinitionsVersions, guid) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +// Delete a ReportDefinition +func (svc *ReportDefinitionsService) Delete(guid string) error { + if guid == "" { + return errors.New("specify a report definition guid") + } + + return svc.client.RequestDecoder("DELETE", fmt.Sprintf(apiV2ReportDefinitionsFromGUID, guid), nil, nil) +} + +func (svc *ReportDefinitionsService) Create(report ReportDefinition) (response ReportDefinitionResponse, err error) { + err = svc.client.RequestEncoderDecoder("POST", apiV2ReportDefinitions, report, &response) + return +} + +func (svc *ReportDefinitionsService) Update(guid string, report ReportDefinitionUpdate) ( + response ReportDefinitionResponse, err error, +) { + if guid == "" { + return response, errors.New("specify a report definition guid") + } + + err = svc.client.RequestEncoderDecoder("PATCH", fmt.Sprintf(apiV2ReportDefinitionsFromGUID, guid), report, &response) + return +} + +func (svc *ReportDefinitionsService) Revert(guid string, version int) (response ReportDefinitionResponse, err error) { + if guid == "" { + return response, errors.New("specify a report definition guid") + } + + apiPath := fmt.Sprintf(apiV2ReportDefinitionsRevert, guid, version) + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, "", &response) + return +} + +// NewReportDefinition creates a new report definition for Create function +func NewReportDefinition(cfg ReportDefinitionConfig) ReportDefinition { + return ReportDefinition{ + ReportName: cfg.ReportName, + DisplayName: cfg.DisplayName, + ReportType: cfg.ReportType, + SubReportType: cfg.SubReportType, + ReportDefinitionDetails: ReportDefinitionDetails{Sections: cfg.Sections}, + } +} + +// NewReportDefinitionUpdate creates a new report definition for Update function +func NewReportDefinitionUpdate(cfg ReportDefinitionConfig) ReportDefinitionUpdate { + return ReportDefinitionUpdate{ + ReportName: cfg.ReportName, + DisplayName: cfg.DisplayName, + ReportDefinitionDetails: &ReportDefinitionDetails{Sections: cfg.Sections}, + } +} + +var ReportDefinitionSubtypes = []string{"AWS", "Azure", "GCP"} + +type ReportDefinitionConfig struct { + ReportName string `json:"reportName" yaml:"reportName"` + DisplayName string `json:"displayName" yaml:"displayName"` + ReportType string `json:"reportType" yaml:"reportType"` + SubReportType string `json:"subReportType" yaml:"subReportType"` + Sections []ReportDefinitionSection `json:"sections,omitempty" yaml:"sections,omitempty"` +} + +// ReportDefinitionUpdate represents fields allowed for update request +type ReportDefinitionUpdate struct { + ReportName string `json:"reportName,omitempty" yaml:"reportName,omitempty"` + DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty"` + ReportDefinitionDetails *ReportDefinitionDetails `json:"reportDefinition,omitempty" yaml:"reportDefinition,omitempty"` +} + +type ReportDefinitionsResponse struct { + Data []ReportDefinition `json:"data"` +} + +type ReportDefinitionResponse struct { + Data ReportDefinition `json:"data"` +} + +type ReportDefinition struct { + ReportDefinitionGuid string `json:"reportDefinitionGuid,omitempty" yaml:"reportDefinitionGuid,omitempty"` + ReportName string `json:"reportName" yaml:"reportName"` + DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty"` + ReportType string `json:"reportType" yaml:"reportType"` + ReportNotificationType string `json:"reportNotificationType,omitempty" yaml:"reportNotificationType,omitempty"` + SubReportType string `json:"subReportType" yaml:"subReportType"` + + ReportDefinitionDetails ReportDefinitionDetails `json:"reportDefinition" yaml:"reportDefinition"` + Props *ReportDefinitionProps `json:"props,omitempty" yaml:"props,omitempty"` + DistributionType string `json:"distributionType,omitempty" yaml:"distributionType,omitempty"` + AlertChannels []string `json:"alertChannels,omitempty" yaml:"alertChannels,omitempty"` + Frequency string `json:"frequency,omitempty" yaml:"frequency,omitempty"` + Version int `json:"version,omitempty" yaml:"version,omitempty"` + UpdateType string `json:"updateType,omitempty" yaml:"updateType,omitempty"` + CreatedBy string `json:"createdBy,omitempty" yaml:"createdBy,omitempty"` + CreatedTime *time.Time `json:"createdTime,omitempty" yaml:"createdTime,omitempty"` + Enabled int `json:"enabled,omitempty" yaml:"enabled,omitempty"` +} + +// IsCustom returns true if report definition is user created, not created by SYSTEM +func (report ReportDefinition) IsCustom() bool { + return report.CreatedBy != "SYSTEM" +} + +func (report ReportDefinition) Config() ReportDefinitionConfig { + return ReportDefinitionConfig{ + ReportName: report.ReportName, + ReportType: report.ReportType, + DisplayName: report.DisplayName, + SubReportType: report.SubReportType, + Sections: report.ReportDefinitionDetails.Sections, + } +} + +type ReportDefinitionDetails struct { + Sections []ReportDefinitionSection `json:"sections"` + Overrides []ReportDefinitionOverrides `json:"overrides,omitempty" yaml:"overrides,omitempty"` +} + +type ReportDefinitionOverrides struct { + Policy string `json:"policy" yaml:"policy"` + Title string `json:"title" yaml:"title"` +} + +type ReportDefinitionSection struct { + Category string `json:"category" yaml:"category"` + Title string `json:"title" yaml:"title"` + Policies []string `json:"policies" yaml:"policies"` +} + +type ReportDefinitionProps struct { + Engine string `json:"engine,omitempty" yaml:"engine,omitempty"` + ReleaseLabel string `json:"releaseLabel,omitempty" yaml:"releaseLabel,omitempty"` + ResourceGroups []string `json:"resourceGroups,omitempty" yaml:"resourceGroups,omitempty"` + Integrations []string `json:"integrations,omitempty" yaml:"integrations,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_distributions.go b/vendor/github.com/lacework/go-sdk/api/reports_distributions.go new file mode 100644 index 000000000..294738444 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/reports_distributions.go @@ -0,0 +1,205 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +// ReportDistributionsService is a service that interacts with the ReportDistributions +// endpoints from the Lacework APIv2 Server +type ReportDistributionsService struct { + client *Client +} + +// The report distribution frequency type +type reportDistributionFrequency int + +const ( + ReportDistributionFrequencyDaily reportDistributionFrequency = iota + ReportDistributionFrequencyWeekly + ReportDistributionFrequencyBiweekly + ReportDistributionFrequencyMonthly +) + +func (frequency reportDistributionFrequency) String() string { + return reportDistributionTypes[frequency] +} + +var reportDistributionTypes = map[reportDistributionFrequency]string{ + ReportDistributionFrequencyDaily: "daily", + ReportDistributionFrequencyWeekly: "weekly", + ReportDistributionFrequencyBiweekly: "biweekly", + ReportDistributionFrequencyMonthly: "monthly", +} + +func ReportDistributionFrequencies() (frequencies []string) { + for _, v := range reportDistributionTypes { + frequencies = append(frequencies, v) + } + return +} + +// The report distribution violation type +type reportDistributionViolation int + +const ( + ReportDistributionViolationCompliant reportDistributionViolation = iota + ReportDistributionViolationNonCompliant + ReportDistributionViolationSuppressed + ReportDistributionViolationCouldNotAssess + ReportDistributionViolationManual +) + +func (subType reportDistributionViolation) String() string { + return reportDistributionSubTypes[subType] +} + +func ReportDistributionViolations() (values []string) { + for _, v := range reportDistributionSubTypes { + values = append(values, v) + } + return +} + +var reportDistributionSubTypes = map[reportDistributionViolation]string{ + ReportDistributionViolationCompliant: "Compliant", + ReportDistributionViolationNonCompliant: "NonCompliant", + ReportDistributionViolationSuppressed: "Suppressed", + ReportDistributionViolationCouldNotAssess: "CouldNotAssess", + ReportDistributionViolationManual: "Manual", +} + +// The report distribution scope type +type reportDistributionScope int + +const ( + ReportDistributionScopeResourceGroup reportDistributionScope = iota + ReportDistributionScopeCloudIntegration +) + +func (scope reportDistributionScope) String() string { + return reportDistributionScopeTypes[scope] +} + +func ReportDistributionScopes() (values []string) { + for _, v := range reportDistributionScopeTypes { + values = append(values, v) + } + return +} + +var reportDistributionScopeTypes = map[reportDistributionScope]string{ + ReportDistributionScopeResourceGroup: "Resource Group", + ReportDistributionScopeCloudIntegration: "Cloud Account Integration", +} + +// List returns a ReportDistributionResponse +func (svc *ReportDistributionsService) List() (response ReportDistributionsResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2ReportDistributions, nil, &response) + return +} + +// Get returns a ReportDistributionResponse +func (svc *ReportDistributionsService) Get(guid string) (response ReportDistributionResponse, err error) { + if guid == "" { + return ReportDistributionResponse{}, errors.New("specify a report distribution guid") + } + apiPath := fmt.Sprintf(apiV2ReportDistributionsFromGUID, guid) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +// Delete a ReportDistribution +func (svc *ReportDistributionsService) Delete(guid string) error { + if guid == "" { + return errors.New("specify a report distribution guid") + } + + return svc.client.RequestDecoder("DELETE", fmt.Sprintf(apiV2ReportDistributionsFromGUID, guid), nil, nil) +} + +func (svc *ReportDistributionsService) Create(report ReportDistribution) ( + response ReportDistributionResponse, err error, +) { + err = svc.client.RequestEncoderDecoder("POST", apiV2ReportDistributions, report, &response) + return +} + +func (svc *ReportDistributionsService) Update(guid string, report ReportDistributionUpdate) ( + response ReportDistributionResponse, err error, +) { + if guid == "" { + return response, errors.New("specify a report distribution guid") + } + + err = svc.client.RequestEncoderDecoder("PATCH", + fmt.Sprintf(apiV2ReportDistributionsFromGUID, guid), report, &response) + return +} + +func (distribution *ReportDistribution) UpdateConfig() ReportDistributionUpdate { + return ReportDistributionUpdate{ + DistributionName: distribution.DistributionName, + Data: distribution.Data, + AlertChannels: distribution.AlertChannels, + Frequency: distribution.Frequency, + } +} + +type ReportDistributionsResponse struct { + Data []ReportDistribution `json:"data"` +} + +type ReportDistributionResponse struct { + Data ReportDistribution `json:"data"` +} + +type ReportDistribution struct { + ReportDistributionGuid string `json:"reportDistributionGuid,omitempty"` + ReportDefinitionGuid string `json:"reportDefinitionGuid"` + DistributionName string `json:"distributionName"` + Data ReportDistributionData `json:"data"` + AlertChannels []string `json:"alertChannels"` + Frequency string `json:"frequency"` +} + +type ReportDistributionData struct { + Severities []string `json:"severities"` + Violations []string `json:"violations"` + ResourceGroups []string `json:"resourceGroups"` + Integrations []ReportDistributionIntegration `json:"integrations"` +} + +type ReportDistributionIntegration struct { + TenantID string `json:"tenantId,omitempty"` + SubscriptionID string `json:"subscriptionId,omitempty"` + AccountID string `json:"accountId,omitempty"` + OrganizationID string `json:"organizationId,omitempty"` + ProjectID string `json:"projectId,omitempty"` +} + +type ReportDistributionUpdate struct { + DistributionName string `json:"distributionName,omitempty"` + Data ReportDistributionData `json:"data,omitempty"` + AlertChannels []string `json:"alertChannels,omitempty"` + Frequency string `json:"frequency,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_gcp.go b/vendor/github.com/lacework/go-sdk/api/reports_gcp.go new file mode 100644 index 000000000..681e15db3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/reports_gcp.go @@ -0,0 +1,204 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "io" + "os" + "sort" + "time" + + "github.com/pkg/errors" +) + +// v2GcpReportsService is a service that interacts with the APIv2 +type gcpReportsService struct { + client *Client +} + +const ComplianceReportDefaultGcp = "GCP CIS Benchmark 1.3" + +type GcpReportConfig struct { + OrganizationID string + ProjectID string + Value string + Parameter reportFilter +} + +type GcpReportType int + +func (report GcpReportType) String() string { + return gcpReportTypes[report] +} + +func NewGcpReportType(report string) (GcpReportType, error) { + for k, v := range gcpReportTypes { + if v == report { + return k, nil + } + } + return NONE_GCP_REPORT, errors.Errorf("no report type found for %s", report) +} + +func GcpReportTypes() []string { + reportTypes := make([]string, 0, len(gcpReportTypes)) + + for _, report := range gcpReportTypes { + reportTypes = append(reportTypes, report) + } + sort.Strings(reportTypes) + return reportTypes +} + +var gcpReportTypes = map[GcpReportType]string{ + GCP_HIPAA: "GCP_HIPAA", + GCP_CIS: "GCP_CIS", + GCP_SOC: "GCP_SOC", + GCP_CIS12: "GCP_CIS12", + GCP_K8S: "GCP_K8S", + GCP_PCI_Rev2: "GCP_PCI_Rev2", + GCP_SOC_Rev2: "GCP_SOC_Rev2", + GCP_HIPAA_Rev2: "GCP_HIPAA_Rev2", + GCP_ISO_27001: "GCP_ISO_27001", + GCP_NIST_CSF: "GCP_NIST_CSF", + GCP_NIST_800_53_REV4: "GCP_NIST_800_53_REV4", + GCP_NIST_800_171_REV2: "GCP_NIST_800_171_REV2", + GCP_PCI: "GCP_PCI", + GCP_CIS13: "GCP_CIS13", + GCP_CIS_1_3_0_NIST_800_171_rev2: "GCP_CIS_1_3_0_NIST_800_171_rev2", + GCP_CIS_1_3_0_NIST_800_53_rev5: "GCP_CIS_1_3_0_NIST_800_53_rev5", + GCP_CIS_1_3_0_NIST_CSF: "GCP_CIS_1_3_0_NIST_CSF", + GCP_PCI_DSS_3_2_1: "GCP_PCI_DSS_3_2_1", + GCP_HIPAA_2013: "GCP_HIPAA_2013", + GCP_ISO_27001_2013: "GCP_ISO_27001_2013", + GCP_CMMC_1_02: "GCP_CMMC_1_02", + GCP_SOC_2: "GCP_SOC_2", +} + +const ( + NONE_GCP_REPORT GcpReportType = iota + GCP_HIPAA + GCP_CIS + GCP_SOC + GCP_CIS12 + GCP_K8S + GCP_PCI_Rev2 + GCP_SOC_Rev2 + GCP_HIPAA_Rev2 + GCP_ISO_27001 + GCP_NIST_CSF + GCP_NIST_800_53_REV4 + GCP_NIST_800_171_REV2 + GCP_PCI + GCP_CIS13 + GCP_CIS_1_3_0_NIST_800_171_rev2 + GCP_CIS_1_3_0_NIST_800_53_rev5 + GCP_CIS_1_3_0_NIST_CSF + GCP_PCI_DSS_3_2_1 + GCP_HIPAA_2013 + GCP_ISO_27001_2013 + GCP_CMMC_1_02 + GCP_SOC_2 +) + +// Get returns a GcpReportResponse +func (svc *gcpReportsService) Get(reportCfg GcpReportConfig) (response GcpReportResponse, err error) { + if reportCfg.ProjectID == "" || reportCfg.OrganizationID == "" { + return GcpReportResponse{}, errors.New("project id and org id are required") + } + + apiPath := fmt.Sprintf(apiV2ReportsSecondaryQuery, + reportCfg.OrganizationID, + reportCfg.ProjectID, + "json", + reportCfg.Parameter.String(), + reportCfg.Value, + ) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +func (svc *gcpReportsService) DownloadPDF(filepath string, config GcpReportConfig) error { + if config.ProjectID == "" || config.OrganizationID == "" { + return errors.New("project id and org id are required") + } + + apiPath := fmt.Sprintf(apiV2ReportsSecondaryQuery, + config.OrganizationID, + config.ProjectID, + "pdf", + config.Parameter.String(), + config.Value, + ) + + request, err := svc.client.NewRequest("GET", apiPath, nil) + if err != nil { + return err + } + + response, err := svc.client.Do(request) + if err != nil { + return err + } + defer response.Body.Close() + + err = checkErrorInResponse(response) + if err != nil { + return err + } + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, response.Body) + return err +} + +func (gcp GcpReport) GetComplianceRecommendation(recommendationID string) (*RecommendationV2, bool) { + for _, r := range gcp.Recommendations { + if r.RecID == recommendationID { + return &r, true + } + } + return nil, false +} + +type GcpReportResponse struct { + Data []GcpReport `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +type GcpReport struct { + ReportType string `json:"reportType"` + ReportTitle string `json:"reportTitle"` + Recommendations []RecommendationV2 `json:"recommendations"` + Summary []ReportSummary `json:"summary"` + ReportTime time.Time `json:"reportTime"` + OrganizationName string `json:"organizationName"` + OrganizationID string `json:"organizationId"` + ProjectName string `json:"projectName"` + ProjectID string `json:"projectId"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/resource_groups.go b/vendor/github.com/lacework/go-sdk/api/resource_groups.go new file mode 100644 index 000000000..35b15aa9a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/resource_groups.go @@ -0,0 +1,262 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + _ "embed" + "encoding/json" + "fmt" + "time" + + "github.com/pkg/errors" +) + +type resourceGroupType int + +const ( + // type that defines a non-existing Resource Group + NoneResourceGroup resourceGroupType = iota + AwsResourceGroup + AzureResourceGroup + ContainerResourceGroup + GcpResourceGroup + MachineResourceGroup + OciResourceGroup + KubernetesResourceGroup +) + +// query templates +var ( + NoneResourceGroupQueryTemplate string = "" + //go:embed _templates/resource_groups/aws.json + AwsResourceGroupQueryTemplate string + //go:embed _templates/resource_groups/azure.json + AzureResourceGroupQueryTemplate string + //go:embed _templates/resource_groups/container.json + ContainerResourceGroupQueryTemplate string + //go:embed _templates/resource_groups/gcp.json + GcpResourceGroupQueryTemplate string + //go:embed _templates/resource_groups/machine.json + MachineResourceGroupQueryTemplate string + //go:embed _templates/resource_groups/oci.json + OciResourceGroupQueryTemplate string + //go:embed _templates/resource_groups/kubernetes.json + KubernetesResourceGroupQueryTemplate string +) + +type resourceGroupContext struct { + resourceGroupType string + queryTemplate string +} + +// ResourceGroupTypes is the list of available Resource Group types +var ResourceGroupTypes = map[resourceGroupType]resourceGroupContext{ + NoneResourceGroup: {resourceGroupType: "None", queryTemplate: NoneResourceGroupQueryTemplate}, + AwsResourceGroup: {resourceGroupType: "AWS", queryTemplate: AwsResourceGroupQueryTemplate}, + AzureResourceGroup: {resourceGroupType: "AZURE", queryTemplate: AzureResourceGroupQueryTemplate}, + ContainerResourceGroup: {resourceGroupType: "CONTAINER", queryTemplate: ContainerResourceGroupQueryTemplate}, + GcpResourceGroup: {resourceGroupType: "GCP", queryTemplate: GcpResourceGroupQueryTemplate}, + MachineResourceGroup: {resourceGroupType: "MACHINE", queryTemplate: MachineResourceGroupQueryTemplate}, + OciResourceGroup: {resourceGroupType: "OCI", queryTemplate: OciResourceGroupQueryTemplate}, + KubernetesResourceGroup: {resourceGroupType: "KUBERNETES", queryTemplate: KubernetesResourceGroupQueryTemplate}, +} + +func NewResourceGroup(name string, iType resourceGroupType, + description string, query *RGQuery) ResourceGroupData { + return ResourceGroupData{ + Name: name, + Type: iType.String(), + Enabled: 1, + Query: query, + Description: description, + } +} + +func (svc *ResourceGroupsService) List() (response ResourceGroupsResponse, err error) { + var rawResponse ResourceGroupsResponse + err = svc.client.RequestDecoder("GET", apiV2ResourceGroups, nil, &rawResponse) + if err != nil { + return rawResponse, err + } + + return rawResponse, nil +} + +func (svc *ResourceGroupsService) Create(group ResourceGroupData) ( + response ResourceGroupResponse, + err error, +) { + err = svc.create(group, &response) + return +} + +func (svc *ResourceGroupsService) Update(data *ResourceGroupData) ( + response ResourceGroupResponse, + err error, +) { + if data == nil { + err = errors.New("resource group must not be empty") + return + } + guid := data.ID() + data.ResetResourceGUID() + + err = svc.update(guid, data, &response) + if err != nil { + return + } + + return +} + +func (group *ResourceGroupData) ResetResourceGUID() { + group.ResourceGroupGuid = "" + group.UpdatedBy = "" + group.UpdatedTime = nil + group.CreatedBy = "" + group.CreatedTime = nil + group.IsDefaultBoolean = nil +} + +func (svc *ResourceGroupsService) Delete(guid string) error { + if guid == "" { + return errors.New("specify a resourceGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2ResourceGroupsFromGUID, guid), + nil, + nil, + ) +} + +func (svc *ResourceGroupsService) Get(guid string, response interface{}) error { + var rawResponse ResourceGroupResponse + err := svc.get(guid, &rawResponse) + if err != nil { + return err + } + + j, err := json.Marshal(rawResponse) + if err != nil { + return err + } + + err = json.Unmarshal(j, &response) + if err != nil { + return err + } + return nil +} + +func (svc *ResourceGroupsService) create(data interface{}, response interface{}) error { + return svc.client.RequestEncoderDecoder("POST", apiV2ResourceGroups, data, response) +} + +func (svc *ResourceGroupsService) get(guid string, response interface{}) error { + if guid == "" { + return errors.New("specify an resource group guid") + } + apiPath := fmt.Sprintf(apiV2ResourceGroupsFromGUID, guid) + return svc.client.RequestDecoder("GET", apiPath, nil, response) +} + +func (svc *ResourceGroupsService) update(guid string, data interface{}, response interface{}) error { + if guid == "" { + return errors.New("specify a resource group guid") + } + + apiPath := fmt.Sprintf(apiV2ResourceGroupsFromGUID, guid) + return svc.client.RequestEncoderDecoder("PATCH", apiPath, data, response) +} + +type ResourceGroupsService struct { + client *Client +} + +type RGExpression struct { + Operator string `json:"operator"` + Children []*RGChild `json:"children"` +} + +type RGChild struct { + Operator string `json:"operator,omitempty"` + FilterName string `json:"filterName,omitempty"` + Children []*RGChild `json:"children,omitempty"` +} + +type RGFilter struct { + Field string `json:"field"` + Operation string `json:"operation"` + Values []string `json:"values"` + Key string `json:"key,omitempty"` +} + +type RGQuery struct { + Filters map[string]*RGFilter `json:"filters"` + Expression *RGExpression `json:"expression"` +} + +// String returns the string representation of a Resource Group type +func (i resourceGroupType) String() string { + return ResourceGroupTypes[i].resourceGroupType +} + +// QueryTemplate returns the resource group type's query template +func (i resourceGroupType) QueryTemplate() string { + return ResourceGroupTypes[i].queryTemplate +} + +// FindResourceGroupType looks up inside the list of available resource group types +// the matching type from the provided string, if none, returns NoneResourceGroup +func FindResourceGroupType(typ string) (resourceGroupType, bool) { + for i, ctx := range ResourceGroupTypes { + if typ == ctx.resourceGroupType { + return i, true + } + } + return NoneResourceGroup, false +} + +func (group *ResourceGroupData) ID() string { + return group.ResourceGroupGuid +} + +type ResourceGroupResponse struct { + Data ResourceGroupData `json:"data"` +} + +type ResourceGroupsResponse struct { + Data []ResourceGroupData `json:"data"` +} + +type ResourceGroupData struct { + Name string `json:"name,omitempty"` + Query *RGQuery `json:"query,omitempty"` + Description string `json:"description,omitempty"` + ResourceGroupGuid string `json:"resourceGroupGuid,omitempty"` + CreatedTime *time.Time `json:"createdTime,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + UpdatedTime *time.Time `json:"updatedTime,omitempty"` + UpdatedBy string `json:"updatedBy,omitempty"` + IsDefaultBoolean *bool `json:"isDefaultBoolean,omitempty"` + Type string `json:"resourceType"` + Enabled int `json:"enabled"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/schemas.go b/vendor/github.com/lacework/go-sdk/api/schemas.go new file mode 100644 index 000000000..59b98114c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/schemas.go @@ -0,0 +1,44 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// SchemasService is the service that retrieves schemas for v2 +type SchemasService struct { + client *Client + Services map[integrationSchema]V2Service +} + +type integrationSchema int + +const ( + None integrationSchema = iota + AlertChannels + AlertProfiles + AlertRules + ContainerRegistries + CloudAccounts + ResourceGroups + ReportRules + TeamMembers + VulnerabilityExceptions +) + +func (svc *SchemasService) GetService(schemaName integrationSchema) V2Service { + return svc.Services[schemaName] +} diff --git a/vendor/github.com/lacework/go-sdk/api/team_members.go b/vendor/github.com/lacework/go-sdk/api/team_members.go new file mode 100644 index 000000000..067f94fc1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/team_members.go @@ -0,0 +1,295 @@ +// +// Author:: Vatasha White () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +type TeamMembersService struct { + client *Client +} + +// NewTeamMember returns an instance of the Team Member struct +// +// Basic usage: Initialize a new TeamMember struct and then use the new instance to perform CRUD operations. +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// teamMember := api.NewTeamMember( +// "FooBar", +// api.TeamMemberProps{ +// Company: "ACME Inc", +// FirstName: "Foo", +// LastName: "Bar" +// }, +// }, +// +// ) +// +// client.V2.TeamMembers.Create(teamMember) +func NewTeamMember(username string, props TeamMemberProps) TeamMember { + return TeamMember{ + Props: props, + UserEnabled: 1, + UserName: username, + } +} + +// NewTeamMemberOrg returns an instance of the team member org struct +// +// Basic usage: Initialize a new TeamMemberOrg struct and then use the new instance to perform CRUD operations. +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// teamMember := api.NewTeamMemberOrg( +// "FooBar", +// api.TeamMemberProps{ +// Company: "ACME Inc", +// FirstName: "Foo", +// LastName: "Bar" +// }, +// }, +// +// ) +// +// client.V2.TeamMembers.CreateOrg(teamMember) +func NewTeamMemberOrg(username string, props TeamMemberProps) TeamMemberOrg { + return TeamMemberOrg{ + Props: props, + UserEnabled: 1, + UserName: username, + OrgAdmin: false, + OrgUser: true, + AdminRoleAccounts: []string{}, + UserRoleAccounts: []string{}, + } +} + +// List returns a list of team members +func (svc *TeamMembersService) List() (res TeamMembersResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2TeamMembers, nil, &res) + return +} + +// Create creates a single team member +func (svc *TeamMembersService) Create(tm TeamMember) (res TeamMemberResponse, err error) { + if svc.client.OrgAccess() { + return res, errors.New("client configured to manage org-level datasets, use CreateOrg()") + } + err = svc.client.RequestEncoderDecoder("POST", apiV2TeamMembers, tm, &res) + return +} + +// CreateOrg creates a single team member at the org level +// TODO Move all ORG stuff into a different file +func (svc *TeamMembersService) CreateOrg(tm TeamMemberOrg) (res TeamMemberOrgResponse, err error) { + if !svc.client.OrgAccess() { + return res, errors.New("client configured to manage account-level datasets, use Create()") + } + err = svc.client.RequestEncoderDecoder("POST", apiV2TeamMembers, tm, &res) + return +} + +// Delete deletes a single team member at the account level with the corresponding guid +func (svc *TeamMembersService) Delete(guid string) error { + if svc.client.OrgAccess() { + return errors.New("client configured to manage org-level datasets, use DeleteOrg()") + } + if guid == "" { + return errors.New("please specify a guid") + } + return svc.client.RequestDecoder("DELETE", fmt.Sprintf(apiV2TeamMembersFromGUID, guid), nil, nil) +} + +// DeleteOrg deletes a single team member at the org level with the corresponding guid +func (svc *TeamMembersService) DeleteOrg(guid string) error { + if !svc.client.OrgAccess() { + return errors.New("client configured to manage account-level datasets, use Delete()") + } + if guid == "" { + return errors.New("please specify a guid") + } + return svc.client.RequestDecoder("DELETE", fmt.Sprintf(apiV2TeamMembersFromGUID, guid), nil, nil) +} + +// Update updates a single team member at the account-level with the corresponding guid +func (svc *TeamMembersService) Update(tm TeamMember) (res TeamMemberResponse, err error) { + if svc.client.OrgAccess() { + return res, errors.New("client configured to manage org-level datasets, use UpdateOrg()") + } + if tm.UserGuid == "" { + err = errors.New("please specify a guid") + return + } + userGuid := tm.UserGuid + // Omit userGuid for patch requests + tm.UserGuid = "" + // Omit userName for patch requests as it cannot be modified + tm.UserName = "" + err = svc.client.RequestEncoderDecoder("PATCH", fmt.Sprintf(apiV2TeamMembersFromGUID, userGuid), tm, &res) + return +} + +// UpdateOrg updates a single team member at the org-level with the corresponding username +func (svc *TeamMembersService) UpdateOrg(tm TeamMemberOrg) (res TeamMemberOrgResponse, err error) { + if !svc.client.OrgAccess() { + err = errors.New("client configured to manage account-level datasets, use Update()") + return + } + if tm.UserName == "" { + err = errors.New("please specify a username") + return + } + tms, errSearch := svc.SearchUsername(tm.UserName) + if errSearch != nil || len(tms.Data) == 0 { + err = errors.Wrap(err, "unable to find user with specified username") + return + } + tm.UserGuid = tms.Data[0].UserGuid + return svc.UpdateOrgById(tm) +} + +// UpdateOrgById updates a single team member at the org-level with the corresponding guid +func (svc *TeamMembersService) UpdateOrgById(tm TeamMemberOrg) (res TeamMemberOrgResponse, err error) { + if !svc.client.OrgAccess() { + err = errors.New("client configured to manage account-level datasets, use Update()") + return + } + if tm.UserGuid == "" { + err = errors.New("please specify a user guid") + return + } + userGuid := tm.UserGuid + // Omit UserGuid from the patch body as it cannot be modified + tm.UserGuid = "" + // Omit userEnabled field from the patch body as it cannot be modified + tm.UserEnabled = 0 + // Omit userName field from the patch body as it cannot be modified + tm.UserName = "" + // Omit Company field from the patch body as it cannot be modified + tm.Props.Company = "" + + err = svc.client.RequestEncoderDecoder("PATCH", fmt.Sprintf(apiV2TeamMembersFromGUID, userGuid), tm, &res) + return +} + +// Get returns a response of the team member +func (svc *TeamMembersService) Get(guid string, res interface{}) error { + if guid == "" { + return errors.New("please specify a guid") + } + return svc.client.RequestDecoder("GET", fmt.Sprintf(apiV2TeamMembersFromGUID, guid), nil, &res) + +} + +func (svc *TeamMembersService) SearchUsername(username string) (res TeamMembersResponse, err error) { + if username == "" { + err = errors.New("specify a username to search for a team member") + return + } + err = svc.client.RequestEncoderDecoder("POST", + apiV2TeamMembersSearch, + SearchFilter{ + Filters: []Filter{ + { + Field: "userName", + Expression: "eq", + Value: username, + }, + }, + }, + &res, + ) + return +} + +type TeamMemberProps struct { + AccountAdmin bool `json:"accountAdmin,omitempty"` + //Company is empty for patch requests on updateOrg as it cannot be modified + Company string `json:"company,omitempty"` + CreatedTime string `json:"createdTime,omitempty"` + FirstName string `json:"firstName"` + JitCreated bool `json:"jitCreated,omitempty"` + LastLoginTime interface{} `json:"lastLoginTime,omitempty"` + LastName string `json:"lastName"` + LastSessionCreatedTime interface{} `json:"lastSessionCreatedTime,omitempty"` + OrgAdmin bool `json:"orgAdmin,omitempty"` + OrgUser bool `json:"orgUser,omitempty"` + UpdatedBy string `json:"updatedBy,omitempty"` + UpdatedTime interface{} `json:"updatedTime,omitempty"` +} + +// TeamMember is for a standalone team member without org access +type TeamMember struct { + CustGuid string `json:"custGuid,omitempty"` + Props TeamMemberProps `json:"props"` + UserEnabled int `json:"userEnabled"` + UserGuid string `json:"userGuid,omitempty"` + UserName string `json:"userName,omitempty"` +} + +type TeamMemberResponse struct { + Data TeamMember `json:"data"` +} + +type TeamMembersResponse struct { + Data []TeamMember `json:"data"` +} + +// TeamMemberOrg is for an organizational team member +type TeamMemberOrg struct { + AdminRoleAccounts []string `json:"adminRoleAccounts"` + OrgAdmin bool `json:"orgAdmin"` + OrgUser bool `json:"orgUser"` + Props TeamMemberProps `json:"props"` + UserEnabled int `json:"userEnabled,omitempty"` + UserGuid string `json:"userGuid,omitempty"` + UserName string `json:"userName,omitempty"` + UserRoleAccounts []string `json:"userRoleAccounts"` +} + +type TeamMemberAccount struct { + AccountName string `json:"accountName"` + Admin bool `json:"admin"` + CustGuid string `json:"custGuid"` + UserEnabled int `json:"userEnabled"` + UserGuid string `json:"userGuid"` +} + +type TeamMemberOrgData struct { + Accounts []TeamMemberAccount `json:"accounts"` + OrgAccount bool `json:"orgAccount"` + OrgAdmin bool `json:"orgAdmin"` + OrgUser bool `json:"orgUser"` + Url string `json:"url"` + UserName string `json:"userName"` +} + +type TeamMemberOrgResponse struct { + Data TeamMemberOrgData `json:"data"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/user_profile.go b/vendor/github.com/lacework/go-sdk/api/user_profile.go new file mode 100644 index 000000000..cba37c3bf --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/user_profile.go @@ -0,0 +1,84 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "strings" + + "github.com/lacework/go-sdk/lwdomain" +) + +// UserProfileService is the service that interacts with the UserProfile +// schema from the Lacework APIv2 Server +type UserProfileService struct { + client *Client +} + +func (svc *UserProfileService) Get() (response UserProfileResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2UserProfile, nil, &response) + return +} + +type UserProfileResponse struct { + Data []UserProfile `json:"data"` +} + +type UserProfile struct { + Username string `json:"username"` + OrgAccount bool `json:"orgAccount"` + URL string `json:"url"` + OrgAdmin bool `json:"orgAdmin"` + OrgUser bool `json:"orgUser"` + Accounts []Account `json:"accounts"` +} + +func (p *UserProfile) OrgAccountName() string { + d, err := lwdomain.New(p.URL) + if err != nil { + return p.URL + } + return d.Account +} + +func (p *UserProfile) SubAccountNames() []string { + names := make([]string, 0) + orgAccountName := p.OrgAccountName() + for _, acc := range p.Accounts { + accName := strings.ToLower(acc.AccountName) + if accName == orgAccountName { + continue + } + if acc.Enabled() { + names = append(names, accName) + } + } + return names +} + +type Account struct { + Admin bool `json:"admin"` + AccountName string `json:"accountName"` + CustGUID string `json:"custGuid"` + UserGUID string `json:"userGuid"` + UserEnabled int `json:"userEnabled"` +} + +func (a *Account) Enabled() bool { + return a.UserEnabled == 1 +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2.go b/vendor/github.com/lacework/go-sdk/api/v2.go new file mode 100644 index 000000000..cb44328f1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2.go @@ -0,0 +1,265 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "net/url" + + "github.com/pkg/errors" + "go.uber.org/zap" +) + +// V2Endpoints groups all APIv2 endpoints available, they are grouped by +// schema which matches with our service architecture +type V2Endpoints struct { + client *Client + + // Every schema must have its own service + UserProfile *UserProfileService + AlertChannels *AlertChannelsService + Alert *v2alertProfilesService + AlertRules *AlertRulesService + ReportRules *ReportRulesService + CloudAccounts *CloudAccountsService + Components *ComponentsService + ComponentData *ComponentDataService + ContainerRegistries *ContainerRegistriesService + Configs *v2ConfigService + FeatureFlags *FeatureFlagsService + ResourceGroups *ResourceGroupsService + AgentAccessTokens *AgentAccessTokensService + AgentInfo *AgentInfoService + Inventory *InventoryService + ComplianceEvaluations *ComplianceEvaluationService + Query *QueryService + OrganizationInfo *OrganizationInfoService + Policy *PolicyService + Reports *ReportsService + ReportDefinitions *ReportDefinitionsService + Metrics *MetricsService + ReportDistributions *ReportDistributionsService + Entities *EntitiesService + Schemas *SchemasService + Datasources *DatasourcesService + DataExportRules *DataExportRulesService + TeamMembers *TeamMembersService + VulnerabilityExceptions *VulnerabilityExceptionsService + Vulnerabilities *v2VulnerabilitiesService + Alerts *AlertsService + Suppressions *SuppressionsServiceV2 + Recommendations *RecommendationsServiceV2 +} + +func NewV2Endpoints(c *Client) *V2Endpoints { + v2 := &V2Endpoints{c, + &UserProfileService{c}, + &AlertChannelsService{c}, + NewV2AlertProfilesService(c), + &AlertRulesService{c}, + &ReportRulesService{c}, + &CloudAccountsService{c}, + &ComponentsService{c}, + &ComponentDataService{c}, + &ContainerRegistriesService{c}, + NewV2ConfigService(c), + &FeatureFlagsService{c}, + &ResourceGroupsService{c}, + &AgentAccessTokensService{c}, + &AgentInfoService{c}, + &InventoryService{c}, + &ComplianceEvaluationService{c}, + &QueryService{c}, + &OrganizationInfoService{c}, + NewV2PolicyService(c), + NewReportsService(c), + &ReportDefinitionsService{c}, + &MetricsService{c}, + &ReportDistributionsService{c}, + &EntitiesService{c}, + &SchemasService{c, map[integrationSchema]V2Service{}}, + &DatasourcesService{c}, + &DataExportRulesService{c}, + &TeamMembersService{c}, + &VulnerabilityExceptionsService{c}, + NewV2VulnerabilitiesService(c), + &AlertsService{c}, + &SuppressionsServiceV2{c, + &AwsSuppressionsV2{c}, + &AzureSuppressionsV2{c}, + &GcpSuppressionsV2{c}, + }, + &RecommendationsServiceV2{c, + &AwsRecommendationsV2{c}, + &AzureRecommendationsV2{c}, + &GcpRecommendationsV2{c}, + }, + } + + v2.Schemas.Services = map[integrationSchema]V2Service{ + AlertChannels: &AlertChannelsService{c}, + AlertRules: &AlertRulesService{c}, + CloudAccounts: &CloudAccountsService{c}, + ContainerRegistries: &ContainerRegistriesService{c}, + ResourceGroups: &ResourceGroupsService{c}, + TeamMembers: &TeamMembersService{c}, + ReportRules: &ReportRulesService{c}, + VulnerabilityExceptions: &VulnerabilityExceptionsService{c}, + } + return v2 +} + +type V2Service interface { + Get(string, interface{}) error + Delete(string) error +} + +type V2CommonIntegration struct { + Data v2CommonIntegrationData `json:"data"` +} + +// V2RawType is the interface that should be implemented when +// a struct is a response that contains v2CommonIntegrationData. +// This include AlertChannelRaw, CloudAccountRaw, ContainerRegistryRaw +type V2RawType interface { + GetData() any + GetCommon() v2CommonIntegrationData +} + +type V2Pagination struct { + Rows int `json:"rows"` + TotalRows int `json:"totalRows"` + Urls struct { + NextPage string `json:"nextPage"` + } `json:"urls"` +} + +// v2PageMetadata is used to compute the total pages and the page number +// when reading pages using the client.NextPage() function +type v2PageMetadata struct { + totalPages int + pageNumber int +} + +func (m v2PageMetadata) PageNumber() int { + return m.pageNumber +} +func (m v2PageMetadata) TotalPages() int { + return m.totalPages +} +func (m *v2PageMetadata) SetTotalPages(total int) { + m.totalPages = total +} +func (m *v2PageMetadata) PageRead() { + m.pageNumber++ +} + +// Pageable is the interface that structs should implement to become +// pageable and be able to use the client.NextPage() function +type Pageable interface { + PageInfo() *V2Pagination + ResetPaging() + + // all these functions are automatically implemented when attaching + // the v2PageMetadata type into any Pageable struct, so attaching that + // struct is a requirement + PageRead() + SetTotalPages(int) + TotalPages() int + PageNumber() int +} + +// NextPage +// +// Use this function to access the next page from an API v2 endpoint, the provided +// response must implement the Pageable interface and when it is passed, it will +// be overwritten, if the response doesn't have paging information this function +// returns false and not error +// +// Usage: To iterate over all pages +// +// ```go +// var ( +// +// response = api.MachineDetailEntityResponse{} +// err = client.V2.Entities.Search(&response, api.SearchFilter{}) +// +// ) +// +// for { +// // Use information from response.Data +// fmt.Printf("Data from page: %d\n", len(response.Data)) +// +// pageOk, err := client.NextPage(&response) +// if err != nil { +// fmt.Printf("Unable to access next page, error '%s'", err.Error()) +// break +// } +// +// if pageOk { +// continue +// } +// break +// } +// +// ``` +func (c *Client) NextPage(p Pageable) (bool, error) { + if p == nil { + return false, nil + } + pagination := p.PageInfo() + if pagination == nil { + c.log.Info("pagination information not found") + return false, nil + } + + if pagination.Urls.NextPage == "" { + return false, nil + } + + if p.PageNumber() == 0 { + // first page, initialize pagination metadata + p.SetTotalPages(pagination.TotalRows / pagination.Rows) + p.PageRead() + } + + c.log.Info("pagination", + zap.Int("page_number", p.PageNumber()), + zap.Int("total_pages", p.TotalPages()), + zap.Int("rows", pagination.Rows), + zap.Int("total_rows", pagination.TotalRows), + zap.String("next_page", pagination.Urls.NextPage), + ) + + pageURL, err := url.Parse(pagination.Urls.NextPage) + if err != nil { + return false, errors.Wrap(err, "unable to part next page url") + } + // some NextPage values have query parameters which should be concatenated + path := pageURL.Path + if len(pageURL.Query()) > 0 { + path += fmt.Sprintf("?%s", pageURL.Query().Encode()) + } + + p.ResetPaging() + c.log.Info("pagination reset") + err = c.RequestDecoder("GET", path, nil, p) + p.PageRead() + return true, err +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_configs.go b/vendor/github.com/lacework/go-sdk/api/v2_configs.go new file mode 100644 index 000000000..3338ce3df --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_configs.go @@ -0,0 +1,34 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// v2ConfigService is a service that interacts with the Configs +// endpoints from the Lacework APIv2 Server +type v2ConfigService struct { + client *Client + Azure *v2AzureConfigService + Gcp *v2GcpConfigService +} + +func NewV2ConfigService(c *Client) *v2ConfigService { + return &v2ConfigService{c, + &v2AzureConfigService{c}, + &v2GcpConfigService{c}, + } +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_configs_azure.go b/vendor/github.com/lacework/go-sdk/api/v2_configs_azure.go new file mode 100644 index 000000000..e3b38c237 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_configs_azure.go @@ -0,0 +1,56 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "fmt" + +// v2AzureConfigService is a service that interacts with the APIv2 +// vulnerabilities endpoints for hosts +type v2AzureConfigService struct { + client *Client +} + +// List returns a list of Azure tenants and subscriptions +func (svc *v2AzureConfigService) List() (response AzureConfigsResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2ConfigsAzure, nil, &response) + if err != nil { + return + } + + return +} + +// ListSubscriptions returns a list of Azure subscriptions for a given tenant +func (svc *v2AzureConfigService) ListSubscriptions(tenantID string) (response AzureConfigsResponse, err error) { + err = svc.client.RequestDecoder("GET", fmt.Sprintf(apiV2ConfigsAzureSubscriptions, tenantID), nil, &response) + if err != nil { + return + } + + return +} + +type AzureConfigsResponse struct { + Data []AzureConfigData `json:"data"` +} + +type AzureConfigData struct { + Tenant string `json:"tenant"` + Subscriptions []string `json:"subscriptions"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_configs_gcp.go b/vendor/github.com/lacework/go-sdk/api/v2_configs_gcp.go new file mode 100644 index 000000000..2f12a272a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_configs_gcp.go @@ -0,0 +1,56 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "fmt" + +// v2GcpConfigService is a service that interacts with the APIv2 +// vulnerabilities endpoints for hosts +type v2GcpConfigService struct { + client *Client +} + +// List returns a list of Gcp organizations and projects +func (svc *v2GcpConfigService) List() (response GcpConfigsResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2ConfigsGcp, nil, &response) + if err != nil { + return + } + + return +} + +// ListProjects returns a list of Gcp projects for a given organization +func (svc *v2GcpConfigService) ListProjects(orgID string) (response GcpConfigsResponse, err error) { + err = svc.client.RequestDecoder("GET", fmt.Sprintf(apiV2ConfigsGcpProjects, orgID), nil, &response) + if err != nil { + return + } + + return +} + +type GcpConfigsResponse struct { + Data []GcpConfigData `json:"data"` +} + +type GcpConfigData struct { + Organization string `json:"organization"` + Projects []string `json:"projects"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_recommendations.go b/vendor/github.com/lacework/go-sdk/api/v2_recommendations.go new file mode 100644 index 000000000..5fe6620bc --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_recommendations.go @@ -0,0 +1,143 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" +) + +// RecommendationsServiceV2 is a service that interacts with the V2 Recommendations +// endpoints from the Lacework Server +type RecommendationsServiceV2 struct { + client *Client + Aws recommendationServiceV2 + Azure recommendationServiceV2 + Gcp recommendationServiceV2 +} + +type recommendationServiceV2 interface { + List() ([]RecV2, error) + Patch(recommendations RecommendationStateV2) (RecommendationResponseV2, error) + GetReport(reportType string) ([]RecV2, error) +} + +type RecommendationTypeV2 string + +const ( + AwsRecommendation RecommendationTypeV2 = "aws" + AzureRecommendation RecommendationTypeV2 = "azure" + GcpRecommendation RecommendationTypeV2 = "gcp" +) + +func (svc *RecommendationsServiceV2) list(cloudType RecommendationTypeV2) ([]RecV2, error) { + var response RecommendationResponseV2 + err := svc.client.RequestDecoder("GET", fmt.Sprintf(apiRecommendations, cloudType), nil, &response) + return response.RecommendationList(), err +} + +func (svc *RecommendationsServiceV2) patch(cloudType RecommendationTypeV2, recommendations RecommendationStateV2) ( + response RecommendationResponseV2, + err error, +) { + err = svc.client.RequestEncoderDecoder("PATCH", fmt.Sprintf(apiRecommendations, cloudType), recommendations, &response) + return +} + +type RecommendationStateV2 map[string]string + +type RecommendationDataV2 map[string]RecommendationEnabledV2 + +type RecV2 struct { + ID string + State bool +} + +type RecommendationEnabledV2 struct { + Enabled bool `json:"enabled"` +} + +type RecommendationResponseV2 struct { + Data []RecommendationDataV2 `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +func (res *RecommendationResponseV2) RecommendationList() (recommendations []RecV2) { + if len(res.Data) > 0 { + for k, v := range res.Data[0] { + recommendations = append(recommendations, RecV2{k, v.Enabled}) + } + } + return +} + +type ReportSchema struct { + Name string `json:"name"` + RecommendationIDs map[string]string `json:"recommendationIDs"` +} + +func NewRecommendationV2State(recommendations []RecV2, state bool) RecommendationStateV2 { + request := make(map[string]string) + for _, rec := range recommendations { + if state { + request[rec.ID] = "enable" + + } else { + request[rec.ID] = "disable" + } + } + return request +} + +func NewRecommendationV2(recommendations []RecV2) RecommendationStateV2 { + request := make(map[string]string) + for _, rec := range recommendations { + if rec.State { + request[rec.ID] = "enable" + + } else { + request[rec.ID] = "disable" + } + } + return request +} + +// ReportStatus This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. +func (res *RecommendationResponseV2) ReportStatus() map[string]bool { + var recommendations = make(map[string]bool) + + for _, rec := range res.RecommendationList() { + recommendations[rec.ID] = rec.State + } + + return recommendations +} + +// filterRecommendations This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. +func filterRecommendations(allRecommendations []RecV2, schema ReportSchema) []RecV2 { + var recommendations []RecV2 + + for _, rec := range allRecommendations { + _, ok := schema.RecommendationIDs[rec.ID] + if ok { + recommendations = append(recommendations, rec) + } + } + return recommendations +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_recommendations_aws.go b/vendor/github.com/lacework/go-sdk/api/v2_recommendations_aws.go new file mode 100644 index 000000000..6542e84a6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_recommendations_aws.go @@ -0,0 +1,70 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/json" + "errors" + + "github.com/lacework/go-sdk/internal/databox" +) + +// AwsRecommendationsV2 is a service that interacts with the V2 Recommendations +// endpoints from the Lacework Server +type AwsRecommendationsV2 struct { + client *Client +} + +func (svc *AwsRecommendationsV2) List() ([]RecV2, error) { + return svc.client.V2.Recommendations.list(AwsRecommendation) +} + +func (svc *AwsRecommendationsV2) Patch(recommendations RecommendationStateV2) (RecommendationResponseV2, error) { + return svc.client.V2.Recommendations.patch(AwsRecommendation, recommendations) +} + +// GetReport This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. +// Scoped to Lacework Account/Subaccount +func (svc *AwsRecommendationsV2) GetReport(reportType string) ([]RecV2, error) { + report := struct { + Ids map[string]string `json:"recommendation_ids"` + }{} + + schemaBytes, ok := databox.Get("/reports/aws/cis.json") + if !ok { + return []RecV2{}, errors.New( + "compliance report schema not found", + ) + } + + err := json.Unmarshal(schemaBytes, &report) + if err != nil { + return []RecV2{}, err + } + + schema := ReportSchema{reportType, report.Ids} + + // fetch all aws recommendations + allRecommendations, err := svc.client.V2.Recommendations.Aws.List() + if err != nil { + return []RecV2{}, err + } + filteredRecommendations := filterRecommendations(allRecommendations, schema) + return filteredRecommendations, nil +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_recommendations_azure.go b/vendor/github.com/lacework/go-sdk/api/v2_recommendations_azure.go new file mode 100644 index 000000000..c08a79eae --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_recommendations_azure.go @@ -0,0 +1,87 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/lacework/go-sdk/internal/databox" +) + +// AzureRecommendationsV2 is a service that interacts with the V2 Recommendations +// endpoints from the Lacework Server +type AzureRecommendationsV2 struct { + client *Client +} + +func (svc *AzureRecommendationsV2) List() ([]RecV2, error) { + return svc.client.V2.Recommendations.list(AzureRecommendation) +} + +func (svc *AzureRecommendationsV2) Patch(recommendations RecommendationStateV2) (RecommendationResponseV2, error) { + return svc.client.V2.Recommendations.patch(AzureRecommendation, recommendations) +} + +// GetReport This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. +// Scoped to Lacework Account/Subaccount +func (svc *AzureRecommendationsV2) GetReport(reportType string) ([]RecV2, error) { + var ( + schemaBytes []byte + ok bool + ) + report := struct { + Ids map[string]string `json:"recommendation_ids"` + }{} + + switch reportType { + case "CIS_1_0": + schemaBytes, ok = databox.Get("/reports/azure/cis.json") + if !ok { + return []RecV2{}, errors.New( + "compliance report schema not found", + ) + } + case "CIS_1_3_1": + schemaBytes, ok = databox.Get("/reports/azure/cis_131.json") + if !ok { + return []RecV2{}, errors.New( + "compliance report schema not found", + ) + } + default: + return nil, fmt.Errorf("unable to find recommendations for report type %s", reportType) + } + + err := json.Unmarshal(schemaBytes, &report) + if err != nil { + return []RecV2{}, err + } + + schema := ReportSchema{reportType, report.Ids} + + // fetch all azure recommendations + allRecommendations, err := svc.client.V2.Recommendations.Azure.List() + if err != nil { + return []RecV2{}, err + } + filteredRecommendations := filterRecommendations(allRecommendations, schema) + return filteredRecommendations, nil +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_recommendations_gcp.go b/vendor/github.com/lacework/go-sdk/api/v2_recommendations_gcp.go new file mode 100644 index 000000000..495ce9b7f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_recommendations_gcp.go @@ -0,0 +1,87 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/lacework/go-sdk/internal/databox" +) + +// GcpRecommendationsV2 is a service that interacts with the V2 Recommendations +// endpoints from the Lacework Server +type GcpRecommendationsV2 struct { + client *Client +} + +func (svc *GcpRecommendationsV2) List() ([]RecV2, error) { + return svc.client.V2.Recommendations.list(GcpRecommendation) +} + +func (svc *GcpRecommendationsV2) Patch(recommendations RecommendationStateV2) (RecommendationResponseV2, error) { + return svc.client.V2.Recommendations.patch(GcpRecommendation, recommendations) +} + +// GetReport This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. +// Scoped to Lacework Account/Subaccount +func (svc *GcpRecommendationsV2) GetReport(reportType string) ([]RecV2, error) { + var ( + schemaBytes []byte + ok bool + ) + report := struct { + Ids map[string]string `json:"recommendation_ids"` + }{} + + switch reportType { + case "CIS_1_0": + schemaBytes, ok = databox.Get("/reports/gcp/cis.json") + if !ok { + return []RecV2{}, errors.New( + "compliance report schema not found", + ) + } + case "CIS_1_2": + schemaBytes, ok = databox.Get("/reports/gcp/cis_12.json") + if !ok { + return []RecV2{}, errors.New( + "compliance report schema not found", + ) + } + default: + return nil, fmt.Errorf("unable to find recommendations for report type %s", reportType) + } + + err := json.Unmarshal(schemaBytes, &report) + if err != nil { + return []RecV2{}, err + } + + schema := ReportSchema{reportType, report.Ids} + + // fetch all azure recommendations + allRecommendations, err := svc.client.V2.Recommendations.Gcp.List() + if err != nil { + return []RecV2{}, err + } + filteredRecommendations := filterRecommendations(allRecommendations, schema) + return filteredRecommendations, nil +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_search_filters.go b/vendor/github.com/lacework/go-sdk/api/v2_search_filters.go new file mode 100644 index 000000000..ba32115db --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_search_filters.go @@ -0,0 +1,119 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "errors" + "math" + "time" +) + +// SearchFilter is the representation of an advanced search payload +// for retrieving information out of the Lacework APIv2 Server +// +// An advanced example of a SearchFilter to search for an Agent +// Access Token that matches the provider token alias and return +// only the token found: +// +// SearchFilter{ +// Filters: []Filter{ +// Filter{ +// Field: "tokenAlias", +// Expression: "eq", +// Value: "k8s-deployment, +// }, +// }, +// Returns: []string{"accessToken"}, +// } +type SearchFilter struct { + *TimeFilter `json:"timeFilter,omitempty"` + Filters []Filter `json:"filters,omitempty"` + Returns []string `json:"returns,omitempty"` +} + +type Filter struct { + Expression string `json:"expression,omitempty"` + Field string `json:"field,omitempty"` + Value string `json:"value,omitempty"` + Values []string `json:"values,omitempty"` +} + +type TimeFilter struct { + StartTime *time.Time `json:"startTime,omitempty"` + EndTime *time.Time `json:"endTime,omitempty"` +} + +type SearchResponse interface { + GetDataLength() int +} + +type SearchableFilter interface { + GetTimeFilter() *TimeFilter + SetStartTime(*time.Time) + SetEndTime(*time.Time) +} + +// V2ApiMaxSearchHistoryDays defines the maximum number of days in the past api v2 allows to be searched +const V2ApiMaxSearchHistoryDays = 92 + +// V2ApiMaxSearchWindowDays defines the maximum number of days in a single request api v2 allows to be searched +const V2ApiMaxSearchWindowDays = 7 + +type search func(response interface{}, filters SearchableFilter) error + +// WindowedSearchFirst performs a new search of a specific time frame size, +// until response data is found or the max searchable days is reached +func WindowedSearchFirst(fn search, size int, max int, response SearchResponse, filter SearchableFilter) error { + if size > max { + return errors.New("window size cannot be greater than max history") + } + + // if start and end time are the same, adjust the windows + timeDifference := int(math.RoundToEven( + filter.GetTimeFilter().EndTime.Sub(*filter.GetTimeFilter().StartTime).Hours() / 24), + ) + + if timeDifference == 0 { + newStart := filter.GetTimeFilter().StartTime.AddDate(0, 0, -size) + filter.SetStartTime(&newStart) + } + + for i := timeDifference; i < max; i += size { + err := fn(&response, filter) + if err != nil { + return err + } + if response.GetDataLength() != 0 { + return nil + } + + // adjust window + newStart := filter.GetTimeFilter().StartTime.AddDate(0, 0, -size) + newEnd := filter.GetTimeFilter().EndTime.AddDate(0, 0, -size) + + // ensure we do not go over the max allowed searchable days + searchableDays := time.Since(newStart).Hours() / 24 + if int(searchableDays) > max { + newStart = time.Now().AddDate(0, 0, -max) + } + filter.SetStartTime(&newStart) + filter.SetEndTime(&newEnd) + } + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_suppressions.go b/vendor/github.com/lacework/go-sdk/api/v2_suppressions.go new file mode 100644 index 000000000..9d0033b74 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_suppressions.go @@ -0,0 +1,101 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/mitchellh/mapstructure" +) + +// SuppressionsServiceV2 is a service that interacts with the V2 Suppressions +// endpoints from the Lacework Server +type SuppressionsServiceV2 struct { + client *Client + Aws suppressionServiceV2 + Azure suppressionServiceV2 + Gcp suppressionServiceV2 +} + +type suppressionServiceV2 interface { + List() (map[string]SuppressionV2, error) +} + +type SuppressionTypeV2 string + +const ( + AwsSuppression SuppressionTypeV2 = "aws" + AzureSuppression SuppressionTypeV2 = "azure" + GcpSuppression SuppressionTypeV2 = "gcp" +) + +func (svc *SuppressionsServiceV2) list(cloudType SuppressionTypeV2) (map[string]SuppressionV2, + error) { + var response SuppressionResponseV2 + err := svc.client.RequestDecoder("GET", fmt.Sprintf(apiSuppressions, cloudType), nil, &response) + return response.SuppressionList(), err +} + +type SuppressionResponseV2 struct { + Data []SuppressionDataV2 `json:"data"` + Ok bool `json:"ok"` + Message string `json:"message"` +} + +type SuppressionDataV2 struct { + RecommendationSuppressions map[string]map[string]interface{} `json:"recommendationExceptions"` +} + +type SuppressionV2 struct { + Enabled bool `json:"enabled"` + SuppressionConditions []SuppressionConditions `json:"suppressionConditions"` +} + +type SuppressionConditions struct { + AccountIds []string `json:"accountIds,omitempty"` + OrganizationIds []string `json:"organizationIds,omitempty"` + ProjectIds []string `json:"projectIds,omitempty"` + RegionNames []string `json:"regionNames,omitempty"` + ResourceLabels []map[string]string `json:"resourceLabels,omitempty"` + ResourceGroupNames []string `json:"resourceGroupNames,omitempty"` + ResourceNames []string `json:"resourceNames,omitempty"` + ResourceTags []map[string]string `json:"resourceTags,omitempty"` + SubscriptionIds []string `json:"subscriptionIds,omitempty"` + TenantIds []string `json:"tenantIds,omitempty"` + Comment string `json:"comments,omitempty"` +} + +func (res *SuppressionResponseV2) SuppressionList() (suppressions map[string]SuppressionV2) { + if len(res.Data) > 0 { + suppressions = make(map[string]SuppressionV2) + for _, v := range res.Data { + for key, val := range v.RecommendationSuppressions { + var sup SuppressionV2 + + err := mapstructure.Decode(val, &sup) + if err != nil { + return nil + } + + suppressions[key] = sup + } + } + } + return +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_suppressions_aws.go b/vendor/github.com/lacework/go-sdk/api/v2_suppressions_aws.go new file mode 100644 index 000000000..461d9b542 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_suppressions_aws.go @@ -0,0 +1,29 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// AwsSuppressionsV2 is a service that interacts with the V2 Suppressions +// endpoints from the Lacework Server +type AwsSuppressionsV2 struct { + client *Client +} + +func (svc *AwsSuppressionsV2) List() (map[string]SuppressionV2, error) { + return svc.client.V2.Suppressions.list(AwsSuppression) +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_suppressions_azure.go b/vendor/github.com/lacework/go-sdk/api/v2_suppressions_azure.go new file mode 100644 index 000000000..2463db2a6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_suppressions_azure.go @@ -0,0 +1,29 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// AzureSuppressionsV2 is a service that interacts with the V2 Suppressions +// endpoints from the Lacework Server +type AzureSuppressionsV2 struct { + client *Client +} + +func (svc *AzureSuppressionsV2) List() (map[string]SuppressionV2, error) { + return svc.client.V2.Suppressions.list(AzureSuppression) +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_suppressions_gcp.go b/vendor/github.com/lacework/go-sdk/api/v2_suppressions_gcp.go new file mode 100644 index 000000000..edd9fc9a8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_suppressions_gcp.go @@ -0,0 +1,29 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +// GcpSuppressionsV2 is a service that interacts with the V2 Suppressions +// endpoints from the Lacework Server +type GcpSuppressionsV2 struct { + client *Client +} + +func (svc *GcpSuppressionsV2) List() (map[string]SuppressionV2, error) { + return svc.client.V2.Suppressions.list(GcpSuppression) +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities.go b/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities.go new file mode 100644 index 000000000..0c9085b8c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities.go @@ -0,0 +1,772 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/lwseverity" +) + +// v2VulnerabilitiesService is a service that interacts with the Vulnerabilities +// endpoints from the Lacework APIv2 Server +type v2VulnerabilitiesService struct { + client *Client + Hosts *v2HostVulnerabilityService + Containers *v2ContainerVulnerabilityService + SoftwarePackages *v2SoftwarePackagesVulnerabilityService +} + +func NewV2VulnerabilitiesService(c *Client) *v2VulnerabilitiesService { + return &v2VulnerabilitiesService{c, + &v2HostVulnerabilityService{c}, + &v2ContainerVulnerabilityService{c}, + &v2SoftwarePackagesVulnerabilityService{c}, + } +} + +// v2ContainerVulnerabilityService is a service that interacts with the APIv2 +// vulnerabilities endpoints for containers +type v2ContainerVulnerabilityService struct { + client *Client +} + +// SearchLastWeek returns a list of VulnerabilityContainer from the last 7 days +func (svc *v2ContainerVulnerabilityService) SearchLastWeek() (VulnerabilitiesContainersResponse, error) { + var ( + now = time.Now().UTC() + before = now.AddDate(0, 0, -7) // 7 days from ago + ) + + return svc.Search(SearchFilter{ + TimeFilter: &TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + }) +} + +// Search returns a list of VulnerabilityContainer from the last 7 days +func (svc *v2ContainerVulnerabilityService) Search(filters SearchFilter) ( + response VulnerabilitiesContainersResponse, err error, +) { + err = svc.client.RequestEncoderDecoder( + "POST", apiV2VulnerabilitiesContainersSearch, + filters, &response, + ) + return +} + +// SearchAllPages iterates over all pages and returns a list of VulnerabilityContainer +func (svc *v2ContainerVulnerabilityService) SearchAllPages(filters SearchFilter) ( + response VulnerabilitiesContainersResponse, err error, +) { + response, err = svc.Search(filters) + if err != nil { + return + } + + var ( + all []VulnerabilityContainer + pageOk bool + ) + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +func (svc *v2ContainerVulnerabilityService) ScanStatus(id string) ( + response VulnerabilitiesContainersScanStatusResponse, err error, +) { + err = svc.client.RequestDecoder("GET", + fmt.Sprintf(apiV2VulnerabilitiesContainersScanStatus, id), + nil, + &response) + return +} + +func (svc *v2ContainerVulnerabilityService) Scan(registry, repository, tagOrHash string) ( + response VulnerabilitiesContainerScanResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("POST", + apiV2VulnerabilitiesContainersScan, + vulnContainerScanRequest{registry, repository, tagOrHash}, + &response, + ) + return +} + +type vulnContainerScanRequest struct { + Registry string `json:"registry"` + Repository string `json:"repository"` + Tag string `json:"tag"` +} + +type VulnerabilitiesContainersScanStatusResponse struct { + Message string `json:"message"` + Data struct { + EvalGuid string `json:"evalGuid"` + Status string `json:"status"` + } `json:"data"` +} + +func (res *VulnerabilitiesContainersScanStatusResponse) CheckStatus() string { + if res.Data.Status != "" { + return res.Data.Status + } + + return "Unknown" +} + +type VulnerabilitiesContainerScanResponse struct { + Message string `json:"message"` + Data struct { + RequestID string `json:"requestId"` + Status string `json:"status"` + } `json:"data"` +} + +func (res *VulnerabilitiesContainerScanResponse) CheckStatus() string { + if res.Data.Status != "" { + return res.Data.Status + } + + return "Unknown" +} + +type VulnerabilitiesContainersResponse struct { + Data []VulnerabilityContainer `json:"data"` + Paging V2Pagination `json:"paging"` + + v2PageMetadata `json:"-"` +} + +func (r *VulnerabilitiesContainersResponse) FilterSingleVulnIDData(vulnID string) { + var singleVulnData = make([]VulnerabilityContainer, 0) + for _, vuln := range r.Data { + if vuln.VulnID == vulnID { + singleVulnData = append(singleVulnData, vuln) + } + } + r.Data = singleVulnData +} + +func (r VulnerabilitiesContainersResponse) HighestSeverity() string { + var sevs []lwseverity.Severity + + for _, vuln := range r.Data { + if lwseverity.NewSeverity(vuln.Severity) != lwseverity.Unknown { + sevs = append(sevs, lwseverity.NewSeverity(vuln.Severity)) + } + } + + if len(sevs) == 0 { + return lwseverity.Unknown.String() + } + + lwseverity.SortSlice(sevs) + return sevs[0].GetSeverity() +} + +func (r VulnerabilitiesContainersResponse) HighestFixableSeverity() string { + var ( + sevs []int + max int + ) + for _, vuln := range r.Data { + if vuln.FixInfo.FixAvailable == 1 { + sevs = append(sevs, SeverityOrder(vuln.Severity)) + if len(sevs) == 1 { + max = SeverityOrder(vuln.Severity) + } else if SeverityOrder(vuln.Severity) > max { + max = SeverityOrder(vuln.Severity) + } + } + } + + return SeverityInt(max) +} + +func (r VulnerabilitiesContainersResponse) VulnFixableCount(severity string) int32 { + count := 0 + for _, vuln := range r.Data { + if vuln.FixInfo.FixAvailable == 1 && strings.EqualFold(vuln.Severity, severity) { + count = count + 1 + } + } + return int32(count) +} + +func (r VulnerabilitiesContainersResponse) TotalVulnerabilities() int { + count := 0 + for _, vuln := range r.Data { + if vuln.Status == "VULNERABLE" { + count = count + 1 + } + } + return count +} + +// Fulfill Pagination interface (look at api/v2.go) +func (r VulnerabilitiesContainersResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *VulnerabilitiesContainersResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +func (r VulnerabilitiesContainersResponse) CriticalVulnerabilities() int32 { + count := 0 + for _, vuln := range r.Data { + if vuln.Severity == "Critical" { + count = count + 1 + } + } + return int32(count) +} + +func (r VulnerabilitiesContainersResponse) HighVulnerabilities() int32 { + count := 0 + for _, vuln := range r.Data { + if vuln.Severity == "High" { + count = count + 1 + } + } + return int32(count) +} + +func (r VulnerabilitiesContainersResponse) MediumVulnerabilities() int32 { + count := 0 + for _, vuln := range r.Data { + if vuln.Severity == "Medium" { + count = count + 1 + } + } + return int32(count) +} + +func (r VulnerabilitiesContainersResponse) LowVulnerabilities() int32 { + count := 0 + for _, vuln := range r.Data { + if vuln.Severity == "Low" { + count = count + 1 + } + } + return int32(count) +} + +func (r VulnerabilitiesContainersResponse) InfoVulnerabilities() int32 { + count := 0 + for _, vuln := range r.Data { + if vuln.Severity == "Info" { + count = count + 1 + } + } + return int32(count) +} + +func (r VulnerabilitiesContainersResponse) FixableVulnerabilities() int32 { + count := 0 + for _, vuln := range r.Data { + if vuln.FixInfo.FixAvailable == 1 { + count = count + 1 + } + } + return int32(count) +} + +func (r VulnerabilitiesContainersResponse) TotalFixableVulnerabilities() int32 { + count := 0 + for _, vuln := range r.Data { + if vuln.FixInfo.FixAvailable == 1 { + count = count + 1 + } + } + return int32(count) +} + +type ImageInfo struct { + CreatedTime int64 `json:"created_time"` + Digest string `json:"digest"` + ErrorMsg []string `json:"error_msg"` + ID string `json:"id"` + Registry string `json:"registry"` + Repo string `json:"repo"` + Size int `json:"size"` + Status string `json:"status"` + Tags []string `json:"tags"` + Type string `json:"type"` +} + +type VulnerabilityContainer struct { + EvalGUID string `json:"evalGuid"` + EvalCtx struct { + CveBatchInfo []struct { + CveBatchID string `json:"cve_batch_id"` + CveCreatedTime string `json:"cve_created_time"` + } `json:"cve_batch_info"` + ExceptionProps []struct { + Status string `json:"status"` + } `json:"exception_props"` + ImageInfo ImageInfo `json:"image_info"` + IsDailyJob string `json:"isDailyJob"` + IsReeval bool `json:"is_reeval"` + ScanBatchID string `json:"scan_batch_id"` + ScanCreatedTime string `json:"scan_created_time"` + ScanRequestProps struct { + DataFormatVersion string `json:"data_format_version"` + Environment struct { + DockerVersion struct { + ErrorMessage string `json:"error_message"` + } `json:"docker_version"` + } `json:"environment"` + Props struct { + DataFormatVersion string `json:"data_format_version"` + ScannerVersion string `json:"scanner_version"` + } `json:"props"` + ScanCompletionUtcTime int `json:"scanCompletionUtcTime"` + ScanStartTime int `json:"scan_start_time"` + ScannerVersion string `json:"scanner_version"` + } `json:"scan_request_props"` + VulnBatchID string `json:"vuln_batch_id"` + VulnCreatedTime string `json:"vuln_created_time"` + } `json:"evalCtx"` + FeatureKey struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Version string `json:"version"` + } `json:"featureKey"` + FeatureProps struct { + IntroducedIn string `json:"introduced_in"` + Layer string `json:"layer"` + Feed string `json:"feed"` + Src string `json:"src"` + VersionFormat string `json:"version_format"` + } `json:"featureProps"` + FixInfo struct { + CompareResult int `json:"compare_result"` + FixAvailable int `json:"fix_available"` + FixedVersion string `json:"fixed_version"` + } `json:"fixInfo"` + ImageID string `json:"imageId"` + Severity string `json:"severity"` + StartTime time.Time `json:"startTime"` + Status string `json:"status"` + VulnID string `json:"vulnId"` +} + +// v2HostVulnerabilityService is a service that interacts with the APIv2 +// vulnerabilities endpoints for hosts +type v2HostVulnerabilityService struct { + client *Client +} + +// SearchLastWeek returns a list of VulnerabilityHost from the last 7 days +func (svc *v2HostVulnerabilityService) SearchLastWeek() (VulnerabilitiesHostResponse, error) { + var ( + now = time.Now().UTC() + before = now.AddDate(0, 0, -7) // 7 days from ago + ) + + return svc.Search(SearchFilter{ + TimeFilter: &TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + }) +} + +// Search returns a list of VulnerabilityHost from the last 7 days +func (svc *v2HostVulnerabilityService) Search(filters SearchFilter) ( + response VulnerabilitiesHostResponse, err error, +) { + err = svc.client.RequestEncoderDecoder( + "POST", apiV2VulnerabilitiesHostsSearch, + filters, &response, + ) + return +} + +// SearchAllPages iterates over all pages and returns a list of VulnerabilityHost +func (svc *v2HostVulnerabilityService) SearchAllPages(filters SearchFilter) ( + response VulnerabilitiesHostResponse, err error, +) { + response, err = svc.Search(filters) + if err != nil { + return + } + + var ( + all []VulnerabilityHost + pageOk bool + ) + for { + all = append(all, response.Data...) + + pageOk, err = svc.client.NextPage(&response) + if err == nil && pageOk { + continue + } + break + } + + response.ResetPaging() + response.Data = all + return +} + +type VulnerabilitiesHostResponse struct { + Data []VulnerabilityHost `json:"data"` + Paging V2Pagination `json:"paging"` + + v2PageMetadata `json:"-"` +} + +// Fulfill Pagination interface (look at api/v2.go) +func (r VulnerabilitiesHostResponse) PageInfo() *V2Pagination { + return &r.Paging +} +func (r *VulnerabilitiesHostResponse) ResetPaging() { + r.Paging = V2Pagination{} + r.Data = nil +} + +type VulnerabilityHost struct { + CveProps struct { + CveBatchID string `json:"cve_batch_id"` + Description string `json:"description"` + Link string `json:"link"` + Metadata *VulnerabilityHostMetadata `json:"metadata,omitempty"` + } `json:"cveProps"` + EvalCtx struct { + ExceptionProps []interface{} `json:"exception_props"` + Hostname string `json:"hostname"` + McEvalGUID string `json:"mc_eval_guid"` + CollectorType string `json:"collector_type"` + } `json:"evalCtx"` + FeatureKey struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + PackageActive int `json:"package_active"` + VersionInstalled string `json:"version_installed"` + } `json:"featureKey"` + FixInfo struct { + CompareResult string `json:"compare_result"` + EvalStatus string `json:"eval_status"` + FixAvailable string `json:"fix_available"` + FixedVersion string `json:"fixed_version"` + FixedVersionComparisonInfos []struct { + CurrFixVer string `json:"curr_fix_ver"` + IsCurrFixVerGreaterThanOtherFixVer string `json:"is_curr_fix_ver_greater_than_other_fix_ver"` + OtherFixVer string `json:"other_fix_ver"` + } `json:"fixed_version_comparison_infos"` + FixedVersionComparisonScore int `json:"fixed_version_comparison_score"` + VersionInstalled string `json:"version_installed"` + } `json:"fixInfo"` + MachineTags any `json:"machineTags"` + Props VulnerabilityHostProps `json:"props"` + Mid int `json:"mid"` + Severity string `json:"severity"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + EvalGUID string `json:"evalGuid"` + Status string `json:"status"` + VulnID string `json:"vulnId"` +} + +func (v *VulnerabilityHost) GetMachineTags() (machineTags VulnerabilityHostMachineTags, err error) { + jsonTags, err := json.Marshal(v.MachineTags) + if err != nil { + return + } + + err = json.Unmarshal(jsonTags, &machineTags) + return +} + +func (v *VulnerabilityHost) GetMachineTagsRaw() (map[string]interface{}, error) { + jsonTags, err := json.Marshal(v.MachineTags) + if err != nil { + return nil, err + } + + var rawTags map[string]interface{} + + if err := json.Unmarshal(jsonTags, &rawTags); err != nil { + return nil, err + } + + return rawTags, nil +} + +type VulnerabilityHostMachineTags struct { + Account string `json:"Account"` + AmiID string `json:"AmiId"` + Env string `json:"Env"` + ExternalIP string `json:"ExternalIp"` + Hostname string `json:"Hostname"` + InstanceID string `json:"InstanceId"` + InternalIP string `json:"InternalIp"` + LwTokenShort string `json:"LwTokenShort"` + Name string `json:"Name"` + SubnetID string `json:"SubnetId"` + VMInstanceType string `json:"VmInstanceType"` + VMProvider string `json:"VmProvider"` + VpcID string `json:"VpcId"` + Zone string `json:"Zone"` + AlphaEksctlIoNodegroupName string `json:"alpha.eksctl.io/nodegroup-name"` + AlphaEksctlIoNodegroupType string `json:"alpha.eksctl.io/nodegroup-type"` + Arch string `json:"arch"` + AwsAutoscalingGroupName string `json:"aws:autoscaling:groupName"` + AwsEc2FleetID string `json:"aws:ec2:fleet-id"` + AwsEc2LaunchtemplateID string `json:"aws:ec2launchtemplate:id"` + AwsEc2LaunchtemplateVersion string `json:"aws:ec2launchtemplate:version"` + EksClusterName string `json:"eks:cluster-name"` + EksNodegroupName string `json:"eks:nodegroup-name"` + K8SIoClusterAutoscalerEnabled int `json:"k8s.io/cluster-autoscaler/enabled"` + K8SIoClusterAutoscalerTechallySandbox string `json:"k8s.io/cluster-autoscaler/techally-sandbox"` + KubernetesIoClusterTechallySandbox string `json:"kubernetes.io/cluster/techally-sandbox"` + LwKubernetesCluster string `json:"lw_KubernetesCluster"` + Os string `json:"os"` + LwInternetExposure string `json:"lw_InternetExposure"` + + //gcp + GCEtags any `json:"GCEtags"` + InstanceName string `json:"InstanceName"` + NumericProjectId string `json:"NumericProjectId"` + ProjectId string `json:"ProjectId"` +} + +func SeverityOrder(severity string) int { + switch strings.ToLower(severity) { + case "critical": + return 1 + case "high": + return 2 + case "medium": + return 3 + case "low": + return 4 + case "info": + return 5 + default: + return 6 + } +} + +func SeverityInt(sev int) string { + switch sev { + case 1: + return "Critical" + case 2: + return "High" + case 3: + return "Medium" + case 4: + return "Low" + case 5: + return "Info" + default: + return "Unknown" + } +} + +func (v *VulnerabilityHost) PackageActive() string { + if v.FeatureKey.PackageActive == 0 { + return "" + } + return "ACTIVE" +} + +func (v *VulnerabilityHost) CvssV2() string { + if v.CveProps.Metadata == nil { + return "0" + } + score := v.CveProps.Metadata.NVD.CVSSv2.Score + return strconv.FormatFloat(score, 'f', 1, 64) +} + +func (v *VulnerabilityHost) CvssV3() string { + if v.CveProps.Metadata == nil { + return "0" + } + score := v.CveProps.Metadata.NVD.CVSSv3.Score + return strconv.FormatFloat(score, 'f', 1, 64) +} + +type VulnerabilityHostMetadata struct { + NVD struct { + CVSSv2 struct { + PublishedDateTime string `json:"PublishedDateTime"` + Score float64 `json:"Score"` + Vectors string `json:"Vectors"` + } `json:"CVSSv2"` + CVSSv3 struct { + ExploitabilityScore float64 `json:"ExploitabilityScore"` + ImpactScore float64 `json:"ImpactScore"` + Score float64 `json:"Score"` + Vectors string `json:"Vectors"` + } `json:"CVSSv3"` + } `json:"NVD"` +} + +type VulnerabilityHostProps struct { + FirstTimeSeen *time.Time `json:"first_time_seen,omitempty"` + IsDailyJob int `json:"isDailyJob,omitempty"` + LastUpdatedTime *time.Time `json:"last_updated_time,omitempty"` +} + +func (v *VulnerabilityHost) HasFix() bool { + return v.FixInfo.FixAvailable == "1" +} + +func (hosts *VulnerabilitiesHostResponse) VulnerabilityCounts() HostVulnCounts { + var ( + hostCounts = HostVulnCounts{} + cves []string + ) + + for _, h := range hosts.Data { + // avoid counting duplicates + if h.VulnID == "" || array.ContainsStr(cves, h.VulnID) { + continue + } + cves = append(cves, h.VulnID) + + switch h.Severity { + case "Critical": + hostCounts.Critical++ + hostCounts.Total++ + if h.HasFix() { + hostCounts.CritFixable++ + hostCounts.TotalFixable++ + } + case "High": + hostCounts.High++ + hostCounts.Total++ + if h.HasFix() { + hostCounts.HighFixable++ + hostCounts.TotalFixable++ + } + case "Medium": + hostCounts.Medium++ + hostCounts.Total++ + if h.HasFix() { + hostCounts.MedFixable++ + hostCounts.TotalFixable++ + } + case "Low": + hostCounts.Low++ + hostCounts.Total++ + if h.HasFix() { + hostCounts.LowFixable++ + hostCounts.TotalFixable++ + } + default: + hostCounts.Info++ + hostCounts.Total++ + if h.HasFix() { + hostCounts.InfoFixable++ + hostCounts.TotalFixable++ + } + } + } + + return hostCounts +} + +// VulnerabilityAssessment is used to provide common functions that are +// required by host or container vulnerability assessments, this is used +// to treat them both as equal +type VulnerabilityAssessment interface { + HighestSeverity() string + HighestFixableSeverity() string + TotalFixableVulnerabilities() int32 +} + +type HostVulnCounts struct { + Critical int32 + CritFixable int32 + High int32 + HighFixable int32 + Medium int32 + MedFixable int32 + Low int32 + LowFixable int32 + Info int32 + InfoFixable int32 + Total int32 + TotalFixable int32 +} + +// HighestSeverity returns the highest severity level vulnerability +func (h *HostVulnCounts) HighestSeverity() string { + if h.Critical != 0 { + return "critical" + } + if h.High != 0 { + return "high" + } + if h.Medium != 0 { + return "medium" + } + if h.Low != 0 { + return "low" + } + return "unknown" +} + +// HighestFixableSeverity returns the highest fixable severity level vulnerability +func (h *HostVulnCounts) HighestFixableSeverity() string { + if h.CritFixable != 0 { + return "critical" + } + if h.HighFixable != 0 { + return "high" + } + if h.MedFixable != 0 { + return "medium" + } + if h.LowFixable != 0 { + return "low" + } + return "unknown" +} + +// TotalFixableVulnerabilities returns the total number of vulnerabilities that have a fix available +func (h *HostVulnCounts) TotalFixableVulnerabilities() int32 { + return h.TotalFixable +} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities_software_packages.go b/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities_software_packages.go new file mode 100644 index 000000000..ca8671933 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities_software_packages.go @@ -0,0 +1,205 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import "fmt" + +// v2SoftwarePackagesVulnerabilityService is a service that interacts with the APIv2 +// vulnerabilities endpoints for software packages +type v2SoftwarePackagesVulnerabilityService struct { + client *Client +} + +// Scan on-demand vulnerability assessment of your software packages +func (svc *v2SoftwarePackagesVulnerabilityService) Scan(manifest VulnerabilitiesPackageManifest) ( + response VulnerabilitySoftwarePackagesResponse, err error, +) { + err = svc.client.RequestEncoderDecoder( + "POST", apiV2VulnerabilitiesSoftwarePackagesScan, + manifest, &response, + ) + return +} + +func (v *VulnerabilitySoftwarePackage) HasFix() bool { + return v.FixInfo.FixAvailable == 1 +} + +func (v *VulnerabilitySoftwarePackage) IsVulnerable() bool { + return v.FixInfo.EvalStatus == "VULNERABLE" +} + +func (v *VulnerabilitySoftwarePackage) ScoreString() string { + if v.CveProps.Metadata.Nvd.Cvssv3.Score != 0 { + return fmt.Sprintf("%.1f", v.CveProps.Metadata.Nvd.Cvssv3.Score) + } + + if v.CveProps.Metadata.Nvd.Cvssv2.Score != 0 { + return fmt.Sprintf("%.1f", v.CveProps.Metadata.Nvd.Cvssv2.Score) + } + + return "" +} + +func (v *VulnerabilitySoftwarePackagesResponse) VulnerabilityCounts() HostVulnCounts { + var hostCounts = HostVulnCounts{} + + for _, vuln := range v.Data { + switch vuln.Severity { + case "Critical": + hostCounts.Critical++ + hostCounts.Total++ + if vuln.HasFix() { + hostCounts.CritFixable++ + hostCounts.TotalFixable++ + } + case "High": + hostCounts.High++ + hostCounts.Total++ + if vuln.HasFix() { + hostCounts.HighFixable++ + hostCounts.TotalFixable++ + } + case "Medium": + hostCounts.Medium++ + hostCounts.Total++ + if vuln.HasFix() { + hostCounts.MedFixable++ + hostCounts.TotalFixable++ + } + case "Low": + hostCounts.Low++ + hostCounts.Total++ + if vuln.HasFix() { + hostCounts.LowFixable++ + hostCounts.TotalFixable++ + } + default: + hostCounts.Info++ + hostCounts.Total++ + if vuln.HasFix() { + hostCounts.InfoFixable++ + hostCounts.TotalFixable++ + } + } + } + + return hostCounts +} + +type VulnerabilitySoftwarePackagesResponse struct { + Data []VulnerabilitySoftwarePackage `json:"data"` +} + +type VulnerabilitySoftwarePackage struct { + OsPkgInfo struct { + Namespace string `json:"namespace"` + Os string `json:"os"` + OsVer string `json:"osVer"` + Pkg string `json:"pkg"` + PkgVer string `json:"pkgVer"` + VersionFormat string `json:"versionFormat"` + } `json:"osPkgInfo"` + VulnID string `json:"vulnId"` + Severity string `json:"severity"` + FeatureKey struct { + AffectedRange struct { + End struct { + Inclusive bool `json:"inclusive"` + Value string `json:"value"` + } `json:"end"` + FixVersion string `json:"fixVersion"` + Start struct { + Inclusive bool `json:"inclusive"` + Value string `json:"value"` + } `json:"start"` + } `json:"affectedRange"` + Name string `json:"name"` + Namespace string `json:"namespace"` + } `json:"featureKey"` + CveProps struct { + CveBatchId string `json:"cveBatchId"` + Description string `json:"description"` + Link string `json:"link"` + Metadata struct { + Nvd struct { + Cvssv2 struct { + Publisheddatetime string `json:"publisheddatetime"` + Score float64 `json:"score"` + Vectors string `json:"vectors"` + } `json:"cvssv2"` + Cvssv3 struct { + Exploitabilityscore float64 `json:"exploitabilityscore"` + Impactscore float64 `json:"impactscore"` + Score float64 `json:"score"` + Vectors string `json:"vectors"` + } `json:"cvssv3"` + } `json:"nvd"` + } `json:"metadata"` + } `json:"cveProps"` + FixInfo struct { + CompareResult int `json:"compareResult"` + EvalStatus string `json:"evalStatus"` + FixAvailable int `json:"fixAvailable"` + FixedVersion string `json:"fixedVersion"` + FixedVersionComparisonInfos []struct { + CurrFixVer string `json:"currFixVer"` + IsCurrFixVerGreaterThanOtherFixVer string `json:"isCurrFixVerGreaterThanOtherFixVer"` + OtherFixVer string `json:"otherFixVer"` + } `json:"fixedVersionComparisonInfos"` + FixedVersionComparisonScore int `json:"fixedVersionComparisonScore"` + MaxPrefixMatchingLenScore int `json:"maxPrefixMatchingLenScore"` + VersionInstalled string `json:"versionInstalled"` + } `json:"fixInfo"` + Summary struct { + EvalCreatedTime string `json:"evalCreatedTime"` + EvalStatus string `json:"evalStatus"` + NumFixableVuln int `json:"numFixableVuln"` + NumFixableVulnBySeverity struct { + Critical int `json:"1"` + High int `json:"2"` + Medium int `json:"3"` + Low int `json:"4"` + Info int `json:"5"` + } `json:"numFixableVulnBySeverity"` + NumTotal int `json:"numTotal"` + NumVuln int `json:"numVuln"` + NumVulnBySeverity struct { + Critical int `json:"1"` + High int `json:"2"` + Field3 int `json:"3"` + Medium int `json:"4"` + Info int `json:"5"` + } `json:"numVulnBySeverity"` + } `json:"summary"` + Props struct { + EvalAlgo string `json:"evalAlgo"` + } `json:"props"` +} + +type VulnerabilitiesPackageManifest struct { + OsPkgInfoList []VulnerabilitiesOsPkgInfo `json:"osPkgInfoList"` +} + +type VulnerabilitiesOsPkgInfo struct { + Os string `json:"os"` + OsVer string `json:"osVer"` + Pkg string `json:"pkg"` + PkgVer string `json:"pkgVer"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/version.go b/vendor/github.com/lacework/go-sdk/api/version.go new file mode 100644 index 000000000..3720821db --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/version.go @@ -0,0 +1,10 @@ +// Code generated by: scripts/version_updater.sh +// File generated at: 20241017162419 +// +// <<< DO NOT EDIT >>> +// + +package api + +// Version is the semver coming from the VERSION file +const Version = "1.54.1-dev" diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go new file mode 100644 index 000000000..c356321ff --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go @@ -0,0 +1,474 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "strings" + "time" + + "github.com/pkg/errors" +) + +// VulnerabilityExceptionsService is the service that interacts with +// the VulnerabilityExceptions schema from the Lacework APIv2 Server +type VulnerabilityExceptionsService struct { + client *Client +} + +// vulnerabilityExceptionResourceScope is an interface for the 2 types of vulnerability exceptions resource scopes: +// 'VulnerabilityExceptionContainerResourceScope' or 'VulnerabilityExceptionHostResourceScope' +type vulnerabilityExceptionResourceScope interface { + Type() vulnerabilityExceptionType + Scope() VulnerabilityExceptionResourceScope +} + +// vulnerabilityExceptionReason represents the types of vulnerability exceptions reasons: +// 'False Positive', 'Accepted Risk', 'Compensating Controls', 'Fix Pending' or 'Other' +type vulnerabilityExceptionReason int + +const ( + VulnerabilityExceptionReasonAcceptedRisk vulnerabilityExceptionReason = iota + VulnerabilityExceptionReasonAcceptedFalsePositive + VulnerabilityExceptionReasonCompensatingControls + VulnerabilityExceptionReasonFixPending + VulnerabilityExceptionReasonOther + VulnerabilityExceptionReasonUnknown +) + +var VulnerabilityExceptionReasons = map[vulnerabilityExceptionReason]string{ + VulnerabilityExceptionReasonAcceptedRisk: "Accepted Risk", + VulnerabilityExceptionReasonAcceptedFalsePositive: "False Positive", + VulnerabilityExceptionReasonCompensatingControls: "Compensating Controls", + VulnerabilityExceptionReasonFixPending: "Fix Pending", + VulnerabilityExceptionReasonOther: "Other", + VulnerabilityExceptionReasonUnknown: "Unknown", +} + +func (i vulnerabilityExceptionReason) String() string { + return VulnerabilityExceptionReasons[i] +} + +func NewVulnerabilityExceptionReason(reason string) vulnerabilityExceptionReason { + switch reason { + case "Accepted Risk": + return VulnerabilityExceptionReasonAcceptedRisk + case "False Positive": + return VulnerabilityExceptionReasonAcceptedFalsePositive + case "Compensating Controls": + return VulnerabilityExceptionReasonCompensatingControls + case "Fix Pending": + return VulnerabilityExceptionReasonFixPending + case "Other": + return VulnerabilityExceptionReasonOther + default: + return VulnerabilityExceptionReasonUnknown + } +} + +// vulnerabilityExceptionType represents the types of vulnerability exceptions 'Host' or 'Container' +type vulnerabilityExceptionType int + +const ( + VulnerabilityExceptionTypeHost vulnerabilityExceptionType = iota + VulnerabilityExceptionTypeContainer +) + +var VulnerabilityExceptionTypes = map[vulnerabilityExceptionType]string{ + VulnerabilityExceptionTypeHost: "Host", + VulnerabilityExceptionTypeContainer: "Container", +} + +func (i vulnerabilityExceptionType) String() string { + return VulnerabilityExceptionTypes[i] +} + +// vulnerabilityExceptionSeverity represents the types of vulnerability severities: +// 'Critical', 'High', 'Medium', 'Low' or 'Info' +type vulnerabilityExceptionSeverity string + +type VulnerabilityExceptionSeverities []vulnerabilityExceptionSeverity + +func (sevs VulnerabilityExceptionSeverities) ToStringSlice() []string { + var res []string + for _, i := range sevs { + switch i { + case VulnerabilityExceptionSeverityCritical: + res = append(res, "Critical") + case VulnerabilityExceptionSeverityHigh: + res = append(res, "High") + case VulnerabilityExceptionSeverityMedium: + res = append(res, "Medium") + case VulnerabilityExceptionSeverityLow: + res = append(res, "Low") + case VulnerabilityExceptionSeverityInfo: + res = append(res, "Info") + default: + continue + } + } + return res +} + +func NewVulnerabilityExceptionSeverities(sevSlice []string) VulnerabilityExceptionSeverities { + var res VulnerabilityExceptionSeverities + for _, i := range sevSlice { + sev := convertVulnerabilityExceptionSeverity(i) + if sev != VulnerabilityExceptionSeverityUnknown { + res = append(res, sev) + } + } + return res +} + +func convertVulnerabilityExceptionSeverity(sev string) vulnerabilityExceptionSeverity { + switch strings.ToLower(sev) { + case "critical": + return VulnerabilityExceptionSeverityCritical + case "high": + return VulnerabilityExceptionSeverityHigh + case "medium": + return VulnerabilityExceptionSeverityMedium + case "low": + return VulnerabilityExceptionSeverityLow + case "info": + return VulnerabilityExceptionSeverityInfo + default: + return VulnerabilityExceptionSeverityUnknown + } +} + +const ( + VulnerabilityExceptionSeverityCritical vulnerabilityExceptionSeverity = "Critical" + VulnerabilityExceptionSeverityHigh vulnerabilityExceptionSeverity = "High" + VulnerabilityExceptionSeverityMedium vulnerabilityExceptionSeverity = "Medium" + VulnerabilityExceptionSeverityLow vulnerabilityExceptionSeverity = "Low" + VulnerabilityExceptionSeverityInfo vulnerabilityExceptionSeverity = "Info" + VulnerabilityExceptionSeverityUnknown vulnerabilityExceptionSeverity = "Unknown" +) + +// NewVulnerabilityException returns an instance of the VulnerabilityException struct +// +// Basic usage: Initialize a new VulnerabilityException struct, then +// +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// exception := api.VulnerabilityExceptionConfig{ +// Type: api.VulnerabilityExceptionTypeHost, +// Description: "This is a vuln exception", +// ExceptionReason: api.VulnerabilityExceptionReasonCompensatingControls, +// Severities: api.VulnerabilityExceptionSeverities{api.VulnerabilityExceptionSeverityCritical}, +// Fixable: true, +// ResourceScope: api.VulnerabilityExceptionContainerResourceScope{ +// ImageID: []string{""}, +// ImageTag: []string{""}, +// Registry: []string{""}, +// Repository: []string{""}, +// Namespace: []string{""}, +// }, +// ExpiryTime: time.Now().AddDate(0, 1, 0), +// } +// +// vulnerabilityException := api.NewVulnerabilityException("vulnerabilityException", exception) +// +// client.V2.VulnerabilityExceptions.Create(vulnerabilityException) +func NewVulnerabilityException(name string, exception VulnerabilityExceptionConfig) VulnerabilityException { + var ( + packages = aggregatePackages(exception.Package) + vulnException = VulnerabilityException{ + Enabled: 1, + ExceptionName: name, + ExceptionReason: exception.ExceptionReason.String(), + Props: VulnerabilityExceptionProps{Description: exception.Description}, + VulnerabilityCriteria: VulnerabilityExceptionCriteria{ + Severity: exception.Severities.ToStringSlice(), + Package: packages, + Cve: exception.Cve, + Fixable: exception.FixableEnabled(), + }, + } + ) + + if !exception.ExpiryTime.IsZero() { + vulnException.ExpiryTime = exception.ExpiryTime.UTC().Format(time.RFC3339) + } + + vulnException.ExceptionType = exception.Type.String() + vulnException.setResourceScope(exception.ResourceScope) + + return vulnException +} + +func aggregatePackages(packages []VulnerabilityExceptionPackage) []map[string][]string { + var packs []map[string][]string + for _, pck := range packages { + var packagesMap = make(map[string][]string) + //aggregate packages with same name + if len(packs) > 0 { + if _, ok := packs[0][pck.Name]; ok { + packs[0][pck.Name] = append(packs[0][pck.Name], pck.Version) + continue + } + } + packagesMap[pck.Name] = []string{pck.Version} + packs = append(packs, packagesMap) + } + return packs +} + +func (exception *VulnerabilityException) setResourceScope(scope vulnerabilityExceptionResourceScope) { + if scope == nil { + return + } + switch scope.Type() { + case VulnerabilityExceptionTypeContainer: + ctr := scope.Scope() + exception.ResourceScope = &VulnerabilityExceptionResourceScope{ + ImageID: ctr.ImageID, + ImageTag: ctr.ImageTag, + Registry: ctr.Registry, + Repository: ctr.Repository, + Namespace: ctr.Namespace, + } + case VulnerabilityExceptionTypeHost: + host := scope.Scope() + exception.ResourceScope = &VulnerabilityExceptionResourceScope{ + Hostname: host.Hostname, + ClusterName: host.ClusterName, + ExternalIP: host.ExternalIP, + Namespace: host.Namespace, + } + default: + exception.ResourceScope = &VulnerabilityExceptionResourceScope{} + } +} + +func (exception VulnerabilityException) Status() string { + if exception.Enabled == 1 { + return "Enabled" + } + return "Disabled" +} + +func (cfg VulnerabilityExceptionConfig) FixableEnabled() []int { + if cfg.Fixable == nil { + return nil + } + + if *cfg.Fixable { + return []int{1} + } + return []int{0} +} + +// List returns a list of Vulnerability Exceptions +func (svc *VulnerabilityExceptionsService) List() (response VulnerabilityExceptionsResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2VulnerabilityExceptions, nil, &response) + return +} + +// Create creates a single Vulnerability Exception +func (svc *VulnerabilityExceptionsService) Create(vuln VulnerabilityException) ( + response VulnerabilityExceptionResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("POST", apiV2VulnerabilityExceptions, vuln, &response) + return +} + +// Delete deletes a Vulnerability Exception that matches the provided guid +func (svc *VulnerabilityExceptionsService) Delete(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid), + nil, + nil, + ) +} + +// Update updates a single Vulnerability Exception. +func (svc *VulnerabilityExceptionsService) Update(data VulnerabilityException) ( + response VulnerabilityExceptionResponse, + err error, +) { + if data.Guid == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, data.Guid) + // Request is invalid if it contains the ID field. We set the id field to empty + data.Guid = "" + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) + return +} + +// Get returns a raw response of the Vulnerability Exception with the matching guid. +func (svc *VulnerabilityExceptionsService) Get(guid string, response interface{}) error { + if guid == "" { + return errors.New("specify a Guid") + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid) + return svc.client.RequestDecoder("GET", apiPath, nil, &response) +} + +type VulnerabilityExceptionConfig struct { + Description string + Type vulnerabilityExceptionType + ExceptionReason vulnerabilityExceptionReason + Severities VulnerabilityExceptionSeverities + Cve []string + Package []VulnerabilityExceptionPackage + Fixable *bool + ResourceScope vulnerabilityExceptionResourceScope + ExpiryTime time.Time +} + +type VulnerabilityExceptionContainerResourceScope struct { + ImageID []string `json:"imageId,omitempty"` + ImageTag []string `json:"imageTag,omitempty"` + Registry []string `json:"registry,omitempty"` + Repository []string `json:"repository,omitempty"` + Namespace []string `json:"namespace,omitempty"` +} + +func (ctr VulnerabilityExceptionContainerResourceScope) Type() vulnerabilityExceptionType { + return VulnerabilityExceptionTypeContainer +} + +func (ctr VulnerabilityExceptionContainerResourceScope) Scope() VulnerabilityExceptionResourceScope { + return VulnerabilityExceptionResourceScope{ + ImageID: ctr.ImageID, + ImageTag: ctr.ImageTag, + Registry: ctr.Registry, + Repository: ctr.Repository, + Namespace: ctr.Namespace, + } +} + +func (host VulnerabilityExceptionHostResourceScope) Scope() VulnerabilityExceptionResourceScope { + return VulnerabilityExceptionResourceScope{ + Hostname: host.Hostname, + ExternalIP: host.ExternalIP, + ClusterName: host.ClusterName, + Namespace: host.Namespace, + } +} + +type VulnerabilityExceptionHostResourceScope struct { + Hostname []string `json:"hostname,omitempty"` + ExternalIP []string `json:"externalIp,omitempty"` + ClusterName []string `json:"clusterName,omitempty"` + Namespace []string `json:"namespace,omitempty"` +} + +func (host VulnerabilityExceptionHostResourceScope) Type() vulnerabilityExceptionType { + return VulnerabilityExceptionTypeHost +} + +type VulnerabilityException struct { + Guid string `json:"exceptionGuid,omitempty"` + Enabled int `json:"state"` + ExceptionName string `json:"exceptionName"` + ExceptionType string `json:"exceptionType"` + ExceptionReason string `json:"exceptionReason"` + Props VulnerabilityExceptionProps `json:"props"` + VulnerabilityCriteria VulnerabilityExceptionCriteria `json:"vulnerabilityCriteria"` + ResourceScope *VulnerabilityExceptionResourceScope `json:"resourceScope,omitempty"` + CreatedTime string `json:"createdTime,omitempty"` + UpdatedTime string `json:"updatedTime,omitempty"` + ExpiryTime string `json:"expiryTime,omitempty"` +} + +type VulnerabilityExceptionProps struct { + Description string `json:"description,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + UpdatedBy string `json:"updatedBy,omitempty"` +} + +type VulnerabilityExceptionResourceScope struct { + // Container properties + ImageID []string `json:"imageId,omitempty"` + ImageTag []string `json:"imageTag,omitempty"` + Registry []string `json:"registry,omitempty"` + Repository []string `json:"repository,omitempty"` + + // Host properties + Hostname []string `json:"hostname,omitempty"` + ExternalIP []string `json:"externalIp,omitempty"` + ClusterName []string `json:"clusterName,omitempty"` + + // Shared properties + Namespace []string `json:"namespace,omitempty"` +} + +type VulnerabilityExceptionCriteria struct { + Cve []string `json:"cve,omitempty"` + Package []map[string][]string `json:"package,omitempty"` + Severity []string `json:"severity,omitempty"` + Fixable []int `json:"fixable,omitempty"` +} + +type VulnerabilityExceptionResponse struct { + Data VulnerabilityException `json:"data"` +} + +type VulnerabilityExceptionsResponse struct { + Data []VulnerabilityException `json:"data"` +} + +type VulnerabilityExceptionPackage struct { + Name string + Version string +} + +func (vc VulnerabilityExceptionCriteria) FixableEnabled() *bool { + if vc.Fixable == nil || len(vc.Fixable) == 0 { + return nil + } + + if len(vc.Fixable) > 0 { + truePtr := vc.Fixable[0] == 1 + return &truePtr + } + falsePtr := false + return &falsePtr +} + +func NewVulnerabilityExceptionPackages(packageMap []map[string]string) []VulnerabilityExceptionPackage { + var packages []VulnerabilityExceptionPackage + for _, m := range packageMap { + for k, v := range m { + pck := VulnerabilityExceptionPackage{ + Name: k, + Version: v, + } + packages = append(packages, pck) + } + } + return packages +} diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go new file mode 100644 index 000000000..fe273624f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go @@ -0,0 +1,91 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +func (svc *VulnerabilityExceptionsService) CreateVulnerabilityExceptionsContainer(vuln VulnerabilityException) ( + response VulnerabilityExceptionContainerResponse, err error) { + err = svc.client.RequestEncoderDecoder("POST", apiV2VulnerabilityExceptions, vuln, &response) + return +} + +func (svc *VulnerabilityExceptionsService) GetVulnerabilityExceptionsContainer(guid string) ( + response VulnerabilityExceptionContainerResponse, err error, +) { + if guid == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +func (svc *VulnerabilityExceptionsService) ListVulnerabilityExceptionsContainers() ( + response VulnerabilityExceptionContainerResponse, err error, +) { + err = svc.client.RequestDecoder("GET", apiV2VulnerabilityExceptions, nil, &response) + return +} + +func (svc *VulnerabilityExceptionsService) UpdateVulnerabilityExceptionsContainer( + data VulnerabilityException, id string, +) ( + response VulnerabilityExceptionContainerResponse, + err error, +) { + if id == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, id) + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) + return +} + +type VulnerabilityExceptionContainerResponse struct { + Data VulnerabilityExceptionContainer `json:"data"` +} + +type VulnerabilityExceptionContainer struct { + Guid string `json:"exceptionGuid,omitempty"` + Enabled int `json:"state"` + ExceptionName string `json:"exceptionName"` + ExceptionType string `json:"exceptionType"` + ExceptionReason string `json:"exceptionReason"` + Props VulnerabilityExceptionProps `json:"props"` + VulnerabilityCriteria VulnerabilityExceptionCriteria `json:"vulnerabilityCriteria"` + ResourceScope VulnerabilityExceptionResourceScopeContainer `json:"resourceScope,omitempty"` + CreatedTime string `json:"createdTime,omitempty"` + UpdatedTime string `json:"updatedTime,omitempty"` + ExpiryTime string `json:"expiryTime,omitempty"` +} + +type VulnerabilityExceptionResourceScopeContainer struct { + ImageID []string `json:"imageId,omitempty"` + ImageTag []string `json:"imageTag,omitempty"` + Registry []string `json:"registry,omitempty"` + Repository []string `json:"repository,omitempty"` + Namespace []string `json:"namespace,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go new file mode 100644 index 000000000..536c93b56 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go @@ -0,0 +1,88 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +func (svc *VulnerabilityExceptionsService) CreateVulnerabilityExceptionsHost(vuln VulnerabilityException) ( + response VulnerabilityExceptionHostResponse, err error) { + err = svc.client.RequestEncoderDecoder("POST", apiV2VulnerabilityExceptions, vuln, &response) + return +} + +func (svc *VulnerabilityExceptionsService) GetVulnerabilityExceptionsHost(guid string) ( + response VulnerabilityExceptionHostResponse, err error, +) { + if guid == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +func (svc *VulnerabilityExceptionsService) ListVulnerabilityExceptionsHosts() ( + response VulnerabilityExceptionHostResponse, err error, +) { + err = svc.client.RequestDecoder("GET", apiV2VulnerabilityExceptions, nil, &response) + return +} + +func (svc *VulnerabilityExceptionsService) UpdateVulnerabilityExceptionsHost(data VulnerabilityException, id string) ( + response VulnerabilityExceptionHostResponse, + err error, +) { + if id == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, id) + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) + return +} + +type VulnerabilityExceptionHostResponse struct { + Data VulnerabilityExceptionHost `json:"data"` +} + +type VulnerabilityExceptionHost struct { + Guid string `json:"exceptionGuid,omitempty"` + Enabled int `json:"state"` + ExceptionName string `json:"exceptionName"` + ExceptionType string `json:"exceptionType"` + ExceptionReason string `json:"exceptionReason"` + Props VulnerabilityExceptionProps `json:"props"` + VulnerabilityCriteria VulnerabilityExceptionCriteria `json:"vulnerabilityCriteria"` + ResourceScope VulnerabilityExceptionResourceScopeHost `json:"resourceScope,omitempty"` + CreatedTime string `json:"createdTime,omitempty"` + UpdatedTime string `json:"updatedTime,omitempty"` + ExpiryTime string `json:"expiryTime,omitempty"` +} + +type VulnerabilityExceptionResourceScopeHost struct { + Hostname []string `json:"hostname,omitempty"` + ExternalIP []string `json:"externalIp,omitempty"` + ClusterName []string `json:"clusterName,omitempty"` + Namespace []string `json:"namespace,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cdk/client/go/client.go b/vendor/github.com/lacework/go-sdk/cli/cdk/client/go/client.go new file mode 100644 index 000000000..1cf57c744 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cdk/client/go/client.go @@ -0,0 +1,215 @@ +package cdk + +import ( + "context" + "encoding/json" + "os" + "time" + + cdk "github.com/lacework/go-sdk/cli/cdk/go/proto/v1" + "github.com/lacework/go-sdk/lwlogger" + "github.com/pkg/errors" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type ComponentCDKClient struct { + logger ComponentCDKLogger + coreClient cdk.CoreClient + conn *grpc.ClientConn + componentVersion string +} + +type ComponentCDKLogger interface { + Infow(msg string, keysAndValues ...interface{}) + Debugf(template string, args ...interface{}) + Debug(args ...interface{}) + Warn(args ...interface{}) +} + +type CDKClientOption func(c *ComponentCDKClient) + +func WithLogger(logger *zap.SugaredLogger) CDKClientOption { + return func(c *ComponentCDKClient) { + c.logger = logger + } +} + +// NewCDKClient creates a new component CDK client +// +// This client provides opinionated access to the services offerred from gRPC in the CDK (caching, metric data, etc) +// +// Note, ensure you are closing the gRPC connection when your component ends using the `Close()` method +// on the ComponentCDKClient +func NewCDKClient(componentVersion string, opts ...CDKClientOption) (*ComponentCDKClient, error) { + // set default logger + defaultLogger := lwlogger.New(os.Getenv("LW_LOG")).Sugar() + client := &ComponentCDKClient{logger: defaultLogger, componentVersion: componentVersion} + + for _, o := range opts { + o(client) + } + + client.logger.Infow("connecting to gRPC server", "address", os.Getenv("LW_CDK_TARGET")) + conn, err := grpc.Dial(os.Getenv("LW_CDK_TARGET"), + // we allow insecure connections since we are connecting to 'localhost' + grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, errors.Wrap(err, "cannot initilize cdk client") + } + + client.conn = conn + client.coreClient = cdk.NewCoreClient(conn) + return client, nil +} + +// Close terminates the gRPC connection to the CDK service +func (c *ComponentCDKClient) Close() error { + return c.conn.Close() +} + +// SetLogger enables overwriting the built-in logger to a custom logger that satifies the ComponentCDKLogger interface +func (c *ComponentCDKClient) SetLogger(logger ComponentCDKLogger) { + c.logger = logger +} + +type CDKCacheMissError struct { + Err error +} + +func (c *CDKCacheMissError) Error() string { + return c.Err.Error() +} + +// ReadCacheAsset fetch key from cache +// +// when an error is returned, if the reason for the error is a cache miss it will be of type +// CDKCacheMissError which should be handled/treated as non-fatal +// +// Response data is in []byte format and will need to be unmarshalled to the correct data type +func (c *ComponentCDKClient) ReadCacheAsset(key string) ([]byte, error) { + response, err := c.coreClient.ReadCache(context.Background(), &cdk.ReadCacheRequest{ + Key: key, + }) + + if err != nil { + c.logger.Debugf("error reading cache; %s", err.Error()) + return nil, err + } + + if response.Hit { + c.logger.Debug("cache hit", + "type", "data", + ) + return response.Data, nil + } + + c.logger.Debug("cache miss", + "type", "data", + ) + return nil, &CDKCacheMissError{Err: errors.New("cache miss")} +} + +// WriteCacheAsset persists data to the Lacework CLI on-disk cache via the CDK service +// +// Note, data written to the cache is marshalled to JSON first. +// +// If there is an error writing to cache the error is logged but the return will be nil. Errors writing to +// should never be fatal and stop a component. However, if the data supplied cannot be marshalled into JSON +// an actual error will be returned. +func (c *ComponentCDKClient) WriteCacheAsset(key string, expires time.Time, data interface{}) error { + jsonData, err := json.Marshal(data) + if err != nil { + return errors.Wrapf(err, "failed to convert data to be cached") + } + + res, err := c.coreClient.WriteCache(context.Background(), &cdk.WriteCacheRequest{ + Key: key, + Expires: timestamppb.New(expires), + Data: jsonData, + }) + + if res != nil && res.Error { + c.logger.Debugf("error writing to cache; %s", res.Message) + } + + if err != nil { + c.logger.Debugf("error writing to cache; %s", err.Error()) + } + + return nil +} + +// MetricData is used when sending data to Honeycomb +// +// For convience, use the `Metric()` method on the ComponentCDKClient instead of this struct directly +type MetricData struct { + // Feature name in Honeycomb + Feature string + + // Feature data in Honeycomb + FeatureData map[string]string + + // Duration for this span (each MetricData is a unique span) + Duration int64 + + client *ComponentCDKClient +} + +// WithDuration attaches a duration to the given span that will be created in Honeycomb (which is optional) +func (m *MetricData) WithDuration(duration int64) *MetricData { + m.Duration = duration + return m +} + +// Send Writes the MetricData to Honeycomb +func (m *MetricData) Send() error { + return m.client.sendMetricData(m) +} + +// Metric is used to create a new MetricData struct that can be augmented and ultimately sent +// +// now := time.Now() +// c, _ := NewCDKClient("0.0.1") +// _ = c.Metric("example", map[string]string{"example": "data"}).Send() +// _ = c.Metric("example2", map[string]string{"example2": "data"}). +// WithDuration(time.Since(now).Milliseconds()). +// Send() +func (c *ComponentCDKClient) Metric(feature string, featureData map[string]string) *MetricData { + return &MetricData{Feature: feature, FeatureData: featureData, client: c} +} + +func (c *ComponentCDKClient) sendMetricData(data *MetricData) error { + data.FeatureData["version"] = c.componentVersion + + request := &cdk.HoneyventRequest{ + Feature: data.Feature, FeatureData: data.FeatureData, + } + + if data.Duration != 0 { + request.DurationMs = data.Duration + } + + _, err := c.coreClient.Honeyvent(context.Background(), request) + if err != nil { + c.logger.Warn("unable to send telemetry", + "type", "data", "error", err.Error(), + ) + } + + return nil +} + +// MetricError is used to write an error to Honeycomb +func (c *ComponentCDKClient) MetricError(e error) { + _, err := c.coreClient.Honeyvent(context.Background(), &cdk.HoneyventRequest{ + Error: e.Error(), + }) + if err != nil { + c.logger.Warn("unable to send telemetry", + "type", "error", "error", err.Error(), + ) + } +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk.pb.go b/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk.pb.go new file mode 100644 index 000000000..4ab3a18e6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk.pb.go @@ -0,0 +1,708 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.12.4 +// source: proto/v1/cdk.proto + +package cdk + +import ( + reflect "reflect" + sync "sync" + + timestamp "github.com/golang/protobuf/ptypes/timestamp" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PingRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ComponentName string `protobuf:"bytes,1,opt,name=component_name,json=componentName,proto3" json:"component_name,omitempty"` +} + +func (x *PingRequest) Reset() { + *x = PingRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_v1_cdk_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRequest) ProtoMessage() {} + +func (x *PingRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_cdk_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. +func (*PingRequest) Descriptor() ([]byte, []int) { + return file_proto_v1_cdk_proto_rawDescGZIP(), []int{0} +} + +func (x *PingRequest) GetComponentName() string { + if x != nil { + return x.ComponentName + } + return "" +} + +type PongReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *PongReply) Reset() { + *x = PongReply{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_v1_cdk_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PongReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PongReply) ProtoMessage() {} + +func (x *PongReply) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_cdk_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PongReply.ProtoReflect.Descriptor instead. +func (*PongReply) Descriptor() ([]byte, []int) { + return file_proto_v1_cdk_proto_rawDescGZIP(), []int{1} +} + +func (x *PongReply) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +// Reply is a generic reply for for rpc definitions that only requires +// acknowledgement of the remote procedure +type Reply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Reply) Reset() { + *x = Reply{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_v1_cdk_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Reply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Reply) ProtoMessage() {} + +func (x *Reply) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_cdk_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Reply.ProtoReflect.Descriptor instead. +func (*Reply) Descriptor() ([]byte, []int) { + return file_proto_v1_cdk_proto_rawDescGZIP(), []int{2} +} + +type HoneyventRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Feature string `protobuf:"bytes,1,opt,name=feature,proto3" json:"feature,omitempty"` + FeatureData map[string]string `protobuf:"bytes,2,rep,name=feature_data,json=featureData,proto3" json:"feature_data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` + DurationMs int64 `protobuf:"varint,4,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"` +} + +func (x *HoneyventRequest) Reset() { + *x = HoneyventRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_v1_cdk_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HoneyventRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HoneyventRequest) ProtoMessage() {} + +func (x *HoneyventRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_cdk_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HoneyventRequest.ProtoReflect.Descriptor instead. +func (*HoneyventRequest) Descriptor() ([]byte, []int) { + return file_proto_v1_cdk_proto_rawDescGZIP(), []int{3} +} + +func (x *HoneyventRequest) GetFeature() string { + if x != nil { + return x.Feature + } + return "" +} + +func (x *HoneyventRequest) GetFeatureData() map[string]string { + if x != nil { + return x.FeatureData + } + return nil +} + +func (x *HoneyventRequest) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *HoneyventRequest) GetDurationMs() int64 { + if x != nil { + return x.DurationMs + } + return 0 +} + +// WriteCacheRequest is used to submit data that should be written to cache, included its expiry +type WriteCacheRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // name of the cache key to write + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + Expires *timestamp.Timestamp `protobuf:"bytes,3,opt,name=expires,proto3" json:"expires,omitempty"` +} + +func (x *WriteCacheRequest) Reset() { + *x = WriteCacheRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_v1_cdk_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WriteCacheRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteCacheRequest) ProtoMessage() {} + +func (x *WriteCacheRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_cdk_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteCacheRequest.ProtoReflect.Descriptor instead. +func (*WriteCacheRequest) Descriptor() ([]byte, []int) { + return file_proto_v1_cdk_proto_rawDescGZIP(), []int{4} +} + +func (x *WriteCacheRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *WriteCacheRequest) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *WriteCacheRequest) GetExpires() *timestamp.Timestamp { + if x != nil { + return x.Expires + } + return nil +} + +// ReadCacheRequest is used to fetch data from the cache +type ReadCacheRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // name of the cache key to read + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (x *ReadCacheRequest) Reset() { + *x = ReadCacheRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_v1_cdk_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReadCacheRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadCacheRequest) ProtoMessage() {} + +func (x *ReadCacheRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_cdk_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadCacheRequest.ProtoReflect.Descriptor instead. +func (*ReadCacheRequest) Descriptor() ([]byte, []int) { + return file_proto_v1_cdk_proto_rawDescGZIP(), []int{5} +} + +func (x *ReadCacheRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +// ReadCacheResponse is the response type after a ReadCacheRequest is made +type ReadCacheResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // hit is true when data was found, false otherwise + Hit bool `protobuf:"varint,1,opt,name=hit,proto3" json:"hit,omitempty"` + // empty if cache miss (hit == false) + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *ReadCacheResponse) Reset() { + *x = ReadCacheResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_v1_cdk_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReadCacheResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadCacheResponse) ProtoMessage() {} + +func (x *ReadCacheResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_cdk_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadCacheResponse.ProtoReflect.Descriptor instead. +func (*ReadCacheResponse) Descriptor() ([]byte, []int) { + return file_proto_v1_cdk_proto_rawDescGZIP(), []int{6} +} + +func (x *ReadCacheResponse) GetHit() bool { + if x != nil { + return x.Hit + } + return false +} + +func (x *ReadCacheResponse) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +// WriteCacheResult is the response type after a WriteCacheRequest is made +type WriteCacheResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Error bool `protobuf:"varint,1,opt,name=error,proto3" json:"error,omitempty"` + // message stores the error body if error == true, otherwise empty + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *WriteCacheResult) Reset() { + *x = WriteCacheResult{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_v1_cdk_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WriteCacheResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteCacheResult) ProtoMessage() {} + +func (x *WriteCacheResult) ProtoReflect() protoreflect.Message { + mi := &file_proto_v1_cdk_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteCacheResult.ProtoReflect.Descriptor instead. +func (*WriteCacheResult) Descriptor() ([]byte, []int) { + return file_proto_v1_cdk_proto_rawDescGZIP(), []int{7} +} + +func (x *WriteCacheResult) GetError() bool { + if x != nil { + return x.Error + } + return false +} + +func (x *WriteCacheResult) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_proto_v1_cdk_proto protoreflect.FileDescriptor + +var file_proto_v1_cdk_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x64, 0x6b, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x34, 0x0a, + 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, + 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x22, 0x25, 0x0a, 0x09, 0x50, 0x6f, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x07, 0x0a, 0x05, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x22, 0xf1, 0x01, 0x0a, 0x10, 0x48, 0x6f, 0x6e, 0x65, 0x79, 0x76, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x12, 0x4c, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x48, 0x6f, 0x6e, 0x65, 0x79, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6f, 0x0a, 0x11, 0x57, 0x72, 0x69, 0x74, 0x65, + 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, + 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x22, 0x24, 0x0a, 0x10, 0x52, 0x65, 0x61, 0x64, + 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x39, + 0x0a, 0x11, 0x52, 0x65, 0x61, 0x64, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x68, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x03, 0x68, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x42, 0x0a, 0x10, 0x57, 0x72, 0x69, + 0x74, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0xf9, 0x01, + 0x0a, 0x04, 0x43, 0x6f, 0x72, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, + 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6e, + 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x09, 0x48, 0x6f, 0x6e, 0x65, + 0x79, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x6f, 0x6e, 0x65, 0x79, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x0d, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, + 0x12, 0x42, 0x0a, 0x09, 0x52, 0x65, 0x61, 0x64, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x18, 0x2e, + 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x43, 0x61, 0x63, 0x68, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x61, 0x64, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0a, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x63, + 0x68, 0x65, 0x12, 0x19, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, + 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x63, 0x68, + 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x6c, 0x61, 0x63, + 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x67, 0x6f, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x63, 0x6c, 0x69, + 0x2f, 0x63, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x64, + 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_v1_cdk_proto_rawDescOnce sync.Once + file_proto_v1_cdk_proto_rawDescData = file_proto_v1_cdk_proto_rawDesc +) + +func file_proto_v1_cdk_proto_rawDescGZIP() []byte { + file_proto_v1_cdk_proto_rawDescOnce.Do(func() { + file_proto_v1_cdk_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_v1_cdk_proto_rawDescData) + }) + return file_proto_v1_cdk_proto_rawDescData +} + +var file_proto_v1_cdk_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_proto_v1_cdk_proto_goTypes = []interface{}{ + (*PingRequest)(nil), // 0: cdk.v1.PingRequest + (*PongReply)(nil), // 1: cdk.v1.PongReply + (*Reply)(nil), // 2: cdk.v1.Reply + (*HoneyventRequest)(nil), // 3: cdk.v1.HoneyventRequest + (*WriteCacheRequest)(nil), // 4: cdk.v1.WriteCacheRequest + (*ReadCacheRequest)(nil), // 5: cdk.v1.ReadCacheRequest + (*ReadCacheResponse)(nil), // 6: cdk.v1.ReadCacheResponse + (*WriteCacheResult)(nil), // 7: cdk.v1.WriteCacheResult + nil, // 8: cdk.v1.HoneyventRequest.FeatureDataEntry + (*timestamp.Timestamp)(nil), // 9: google.protobuf.Timestamp +} +var file_proto_v1_cdk_proto_depIdxs = []int32{ + 8, // 0: cdk.v1.HoneyventRequest.feature_data:type_name -> cdk.v1.HoneyventRequest.FeatureDataEntry + 9, // 1: cdk.v1.WriteCacheRequest.expires:type_name -> google.protobuf.Timestamp + 0, // 2: cdk.v1.Core.Ping:input_type -> cdk.v1.PingRequest + 3, // 3: cdk.v1.Core.Honeyvent:input_type -> cdk.v1.HoneyventRequest + 5, // 4: cdk.v1.Core.ReadCache:input_type -> cdk.v1.ReadCacheRequest + 4, // 5: cdk.v1.Core.WriteCache:input_type -> cdk.v1.WriteCacheRequest + 1, // 6: cdk.v1.Core.Ping:output_type -> cdk.v1.PongReply + 2, // 7: cdk.v1.Core.Honeyvent:output_type -> cdk.v1.Reply + 6, // 8: cdk.v1.Core.ReadCache:output_type -> cdk.v1.ReadCacheResponse + 7, // 9: cdk.v1.Core.WriteCache:output_type -> cdk.v1.WriteCacheResult + 6, // [6:10] is the sub-list for method output_type + 2, // [2:6] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_proto_v1_cdk_proto_init() } +func file_proto_v1_cdk_proto_init() { + if File_proto_v1_cdk_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_v1_cdk_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_v1_cdk_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PongReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_v1_cdk_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Reply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_v1_cdk_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HoneyventRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_v1_cdk_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WriteCacheRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_v1_cdk_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReadCacheRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_v1_cdk_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReadCacheResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_v1_cdk_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WriteCacheResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_v1_cdk_proto_rawDesc, + NumEnums: 0, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_v1_cdk_proto_goTypes, + DependencyIndexes: file_proto_v1_cdk_proto_depIdxs, + MessageInfos: file_proto_v1_cdk_proto_msgTypes, + }.Build() + File_proto_v1_cdk_proto = out.File + file_proto_v1_cdk_proto_rawDesc = nil + file_proto_v1_cdk_proto_goTypes = nil + file_proto_v1_cdk_proto_depIdxs = nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go b/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go new file mode 100644 index 000000000..3603a28ac --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go @@ -0,0 +1,226 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.12.4 +// source: proto/v1/cdk.proto + +package cdk + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// CoreClient is the client API for Core service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CoreClient interface { + // Sends a ping -> pong between server and client + // + // Component -> CDK Server + Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongReply, error) + // Sends a Honeyvent + Honeyvent(ctx context.Context, in *HoneyventRequest, opts ...grpc.CallOption) (*Reply, error) + // Read from CLI cache + ReadCache(ctx context.Context, in *ReadCacheRequest, opts ...grpc.CallOption) (*ReadCacheResponse, error) + // Write to CLI cache + WriteCache(ctx context.Context, in *WriteCacheRequest, opts ...grpc.CallOption) (*WriteCacheResult, error) +} + +type coreClient struct { + cc grpc.ClientConnInterface +} + +func NewCoreClient(cc grpc.ClientConnInterface) CoreClient { + return &coreClient{cc} +} + +func (c *coreClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongReply, error) { + out := new(PongReply) + err := c.cc.Invoke(ctx, "/cdk.v1.Core/Ping", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreClient) Honeyvent(ctx context.Context, in *HoneyventRequest, opts ...grpc.CallOption) (*Reply, error) { + out := new(Reply) + err := c.cc.Invoke(ctx, "/cdk.v1.Core/Honeyvent", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreClient) ReadCache(ctx context.Context, in *ReadCacheRequest, opts ...grpc.CallOption) (*ReadCacheResponse, error) { + out := new(ReadCacheResponse) + err := c.cc.Invoke(ctx, "/cdk.v1.Core/ReadCache", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *coreClient) WriteCache(ctx context.Context, in *WriteCacheRequest, opts ...grpc.CallOption) (*WriteCacheResult, error) { + out := new(WriteCacheResult) + err := c.cc.Invoke(ctx, "/cdk.v1.Core/WriteCache", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CoreServer is the server API for Core service. +// All implementations must embed UnimplementedCoreServer +// for forward compatibility +type CoreServer interface { + // Sends a ping -> pong between server and client + // + // Component -> CDK Server + Ping(context.Context, *PingRequest) (*PongReply, error) + // Sends a Honeyvent + Honeyvent(context.Context, *HoneyventRequest) (*Reply, error) + // Read from CLI cache + ReadCache(context.Context, *ReadCacheRequest) (*ReadCacheResponse, error) + // Write to CLI cache + WriteCache(context.Context, *WriteCacheRequest) (*WriteCacheResult, error) + mustEmbedUnimplementedCoreServer() +} + +// UnimplementedCoreServer must be embedded to have forward compatible implementations. +type UnimplementedCoreServer struct { +} + +func (UnimplementedCoreServer) Ping(context.Context, *PingRequest) (*PongReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") +} +func (UnimplementedCoreServer) Honeyvent(context.Context, *HoneyventRequest) (*Reply, error) { + return nil, status.Errorf(codes.Unimplemented, "method Honeyvent not implemented") +} +func (UnimplementedCoreServer) ReadCache(context.Context, *ReadCacheRequest) (*ReadCacheResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReadCache not implemented") +} +func (UnimplementedCoreServer) WriteCache(context.Context, *WriteCacheRequest) (*WriteCacheResult, error) { + return nil, status.Errorf(codes.Unimplemented, "method WriteCache not implemented") +} +func (UnimplementedCoreServer) mustEmbedUnimplementedCoreServer() {} + +// UnsafeCoreServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CoreServer will +// result in compilation errors. +type UnsafeCoreServer interface { + mustEmbedUnimplementedCoreServer() +} + +func RegisterCoreServer(s grpc.ServiceRegistrar, srv CoreServer) { + s.RegisterService(&Core_ServiceDesc, srv) +} + +func _Core_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServer).Ping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cdk.v1.Core/Ping", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServer).Ping(ctx, req.(*PingRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Core_Honeyvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HoneyventRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServer).Honeyvent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cdk.v1.Core/Honeyvent", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServer).Honeyvent(ctx, req.(*HoneyventRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Core_ReadCache_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReadCacheRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServer).ReadCache(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cdk.v1.Core/ReadCache", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServer).ReadCache(ctx, req.(*ReadCacheRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Core_WriteCache_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WriteCacheRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CoreServer).WriteCache(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cdk.v1.Core/WriteCache", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CoreServer).WriteCache(ctx, req.(*WriteCacheRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Core_ServiceDesc is the grpc.ServiceDesc for Core service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Core_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "cdk.v1.Core", + HandlerType: (*CoreServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Ping", + Handler: _Core_Ping_Handler, + }, + { + MethodName: "Honeyvent", + Handler: _Core_Honeyvent_Handler, + }, + { + MethodName: "ReadCache", + Handler: _Core_ReadCache_Handler, + }, + { + MethodName: "WriteCache", + Handler: _Core_WriteCache_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/v1/cdk.proto", +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/access_token.go b/vendor/github.com/lacework/go-sdk/cli/cmd/access_token.go new file mode 100644 index 000000000..c1e77b673 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/access_token.go @@ -0,0 +1,100 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + // duration of the access token in seconds + durationSeconds int + + // accessTokenCmd represents the access-token command + accessTokenCmd = &cobra.Command{ + Use: "access-token", + Short: "Generate temporary API access tokens", + Long: `Generates a temporary API access token that can be used to access the +Lacework API. The token will be valid for the duration that you specify.`, + Args: cobra.NoArgs, + RunE: generateAccessToken, + } +) + +func init() { + // add the access-token command + rootCmd.AddCommand(accessTokenCmd) + + accessTokenCmd.Flags().IntVarP(&durationSeconds, + "duration_seconds", "d", api.DefaultTokenExpiryTime, + "duration in seconds that the access token should remain valid", + ) +} + +func generateAccessToken(_ *cobra.Command, args []string) error { + var ( + response *api.TokenData + err error + ) + + if durationSeconds == api.DefaultTokenExpiryTime { + response, err = cli.LwApi.GenerateToken() + if err != nil { + return errors.Wrap(err, "unable to generate access token") + } + } else { + // if the duration is different from the default, + // regenerate the lacework api client + client, err := api.NewClient(cli.Account, + api.WithLogLevel(cli.Log.Level().CapitalString()), + api.WithExpirationTime(durationSeconds), + api.WithHeader("User-Agent", fmt.Sprintf("Command-Line/%s", Version)), + ) + if err != nil { + return errors.Wrap(err, "unable to generate api client") + } + + response, err = client.GenerateTokenWithKeys(cli.KeyID, cli.Secret) + if err != nil { + return errors.Wrap(err, "unable to generate access token") + } + } + + // cache new token + err = cli.Cache.Write("token", structToString(response)) + if err != nil { + cli.Log.Warnw("unable to write token in cache", + "feature", "cache", + "error", err.Error(), + ) + } + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + + cli.OutputHuman(response.Token) + cli.OutputHuman("\n") + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/account.go b/vendor/github.com/lacework/go-sdk/cli/cmd/account.go new file mode 100644 index 000000000..f2b25b3e6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/account.go @@ -0,0 +1,88 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/spf13/cobra" +) + +var ( + // accountCmd represents the account command + accountCmd = &cobra.Command{ + Use: "account", + Aliases: []string{"accounts", "acc"}, + Short: "Manage accounts in an organization (org admins only)", + Long: `Manage accounts inside your Lacework organization. + +An organization can contain multiple accounts so you can also manage components +such as alerts, resource groups, team members, and audit logs at a more granular +level inside an organization. A team member may have access to multiple accounts +and can easily switch between them. + +To enroll your Lacework account in an organization follow the documentation: + + https://docs.lacework.com/organization-overview + `, + } + + // accountListCmd represents the list command inside the account command + accountListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all accounts", + Long: `List all accounts in your organization.`, + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + cli.StartProgress(" Loading account information ...") + user, err := cli.LwApi.V2.UserProfile.Get() + cli.StopProgress() + if err != nil { + return err + } + + if cli.JSONOutput() { + return cli.OutputJSON(user.Data) + } + + if len(user.Data) == 0 { + return yikes("unable to load account information.") + } + + profile := user.Data[0] + if !profile.OrgAccount { + cli.OutputHuman("Your account is not enrolled in an organization.\n") + return nil + } + + rows := [][]string{{profile.OrgAccountName()}} + for _, acc := range profile.SubAccountNames() { + rows = append(rows, []string{acc}) + } + + cli.OutputHuman(renderSimpleTable([]string{"Accounts"}, rows)) + cli.OutputHuman("\nUse '--subaccount ' to switch any command to a different account.\n") + return nil + }, + } +) + +func init() { + rootCmd.AddCommand(accountCmd) + accountCmd.AddCommand(accountListCmd) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent.go new file mode 100644 index 000000000..5fa523475 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/agent.go @@ -0,0 +1,395 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "time" + + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + agentCmdState = struct { + TokenUpdateEnable bool + TokenUpdateDisable bool + TokenUpdateName string + TokenUpdateDesc string + InstallForce bool + InstallSshUser string + InstallSshPort int + InstallAgentToken string + InstallTrustHostKey bool + InstallPassword string + InstallIdentityFile string + InstallTagKey string + InstallTag []string + InstallIncludeRegions []string + InstallDryRun bool + InstallProjectId string + InstallMaxParallelism int + InstallBYORole string + InstallSkipCreatInfra bool + InstallForceReinstall bool + InstallServerURL string + InstallAWSProfile string + }{} + + defaultSshIdentityKey = "~/.ssh/id_rsa" + + agentCmd = &cobra.Command{ + Use: "agent", + Short: "Manage Lacework agents", + Long: `Manage agents and agent access tokens in your account. + +To analyze application, host, and user behavior, Lacework uses a lightweight agent, +which securely forwards collected metadata to the Lacework cloud for analysis. The +agent requires minimal system resources and runs on most 64-bit Linux distributions. + +For a complete list of supported operating systems, visit: + + https://docs.lacework.com/supported-operating-systems`, + } + + agentTokenCmd = &cobra.Command{ + Use: "token", + Aliases: []string{"tokens"}, + Short: "Manage agent access tokens", + Long: `Manage agent access tokens in your account. + +Agent tokens should be treated as secret and not published. A token uniquely identifies +a Lacework customer. If you suspect your token has been publicly exposed or compromised, +generate a new token, update the new token on all machines using the old token. When +complete, the old token can safely be disabled without interrupting Lacework services.`, + } + + agentTokenListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all agent access tokens", + Args: cobra.NoArgs, + RunE: listAgentTokens, + } + + agentTokenCreateCmd = &cobra.Command{ + Use: "create [description]", + Short: "Create a new agent access token", + Args: cobra.RangeArgs(1, 2), + RunE: createAgentToken, + } + + agentTokenShowCmd = &cobra.Command{ + Use: "show ", + Short: "Show details about an agent access token", + Args: cobra.ExactArgs(1), + RunE: showAgentToken, + } + + agentTokenUpdateCmd = &cobra.Command{ + Use: "update ", + Short: "Update an agent access token", + Long: `Update an agent access token. + +To update the token name and description: + + lacework agent token update --name dev --description "k8s deployment for dev" + +To disable a token: + + lacework agent token update --disable + +To enable a token: + + lacework agent token update --enable`, + Args: cobra.ExactArgs(1), + RunE: updateAgentToken, + } + + // TODO hidden for now + agentGenerateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate agent deployment scripts", + Long: `TBA`, + Hidden: true, + RunE: func(_ *cobra.Command, _ []string) error { + return nil + }, + } + + agentInstallCmd = &cobra.Command{ + Use: "install <[user@]host[:port]>", + Short: "Install the datacollector agent on a remote host", + Args: cobra.ExactArgs(1), + Long: `For single host installation of the Lacework agent via Secure Shell (SSH). + +When this command is executed without any additional flag, an interactive prompt will be +launched to help gather the necessary authentication information to access the remote host. + +To authenticate to the remote host with a username and password. + + lacework agent install --ssh_username --ssh_password + +To authenticate to the remote host with an identity file instead. + + lacework agent install -i /path/to/your/key + +To provide an agent access token of your choice, use the command 'lacework agent token list', +select a token and pass it to the '--token' flag. + + lacework agent install -i /path/to/your/key --token + +To authenticate to the remote host on a non-standard SSH port use the '--ssh_port' flag or +pass it directly via the argument. + + lacework agent install + +To explicitly specify the server URL that the agent will connect to: + + lacework agent install --server_url https://your.server.url.lacework.net + +To list all active agents in your environment. + + lacework agent list + +NOTE: New agents could take up to an hour to report back to the platform.`, + RunE: installRemoteAgent, + } + + agentAWSInstallCmd = &cobra.Command{ + Use: "aws-install", + Args: cobra.NoArgs, + Short: "Install the datacollector agent on all remote AWS hosts", + } + + agentGCPInstallCmd = &cobra.Command{ + Use: "gcp-install", + Args: cobra.NoArgs, + Short: "Install the datacollector agent on all remote GCE hosts", + } +) + +func init() { + // add the agent command + rootCmd.AddCommand(agentCmd) + + // add the token sub-command to the agent cmd + agentCmd.AddCommand(agentTokenCmd) + agentCmd.AddCommand(agentInstallCmd) + agentCmd.AddCommand(agentGenerateCmd) + agentCmd.AddCommand(agentListCmd) + agentCmd.AddCommand(agentAWSInstallCmd) + agentCmd.AddCommand(agentGCPInstallCmd) + + // add the list sub-command to the 'agent token' cmd + agentTokenCmd.AddCommand(agentTokenListCmd) + agentTokenCmd.AddCommand(agentTokenCreateCmd) + agentTokenCmd.AddCommand(agentTokenShowCmd) + agentTokenCmd.AddCommand(agentTokenUpdateCmd) + + // add sub-commands to the 'agent aws-install' command for different install methods + agentAWSInstallCmd.AddCommand(agentInstallAWSEC2ICCmd) + agentAWSInstallCmd.AddCommand(agentInstallAWSSSHCmd) + agentAWSInstallCmd.AddCommand(agentInstallAWSSSMCmd) + + // add sub-commands to the 'agent gcp-install' command for different install methods + agentGCPInstallCmd.AddCommand(agentInstallGCPOSLCmd) + + // 'agent token update' flags + agentTokenUpdateCmd.Flags().BoolVar(&agentCmdState.TokenUpdateEnable, + "enable", false, "enable agent access token", + ) + agentTokenUpdateCmd.Flags().BoolVar(&agentCmdState.TokenUpdateDisable, + "disable", false, "disable agent access token", + ) + agentTokenUpdateCmd.Flags().StringVar(&agentCmdState.TokenUpdateName, + "name", "", "new agent access token name", + ) + agentTokenUpdateCmd.Flags().StringVar(&agentCmdState.TokenUpdateDesc, + "description", "", "new agent access token description", + ) + + // 'agent install' flags + agentInstallCmd.Flags().StringVarP(&agentCmdState.InstallIdentityFile, + "identity_file", "i", defaultSshIdentityKey, + "identity (private key) for public key authentication", + ) + agentInstallCmd.Flags().StringVar(&agentCmdState.InstallPassword, + "ssh_password", "", "password for authentication", + ) + agentInstallCmd.Flags().StringVar(&agentCmdState.InstallSshUser, + "ssh_username", "", "username to login with", + ) + agentInstallCmd.Flags().IntVar(&agentCmdState.InstallSshPort, + "ssh_port", 22, "port to connect to on the remote host", + ) + agentInstallCmd.Flags().BoolVar(&agentCmdState.InstallForce, + "force", false, "override any pre-installed agent", + ) + agentInstallCmd.Flags().StringVar(&agentCmdState.InstallAgentToken, + "token", "", "agent access token", + ) + agentInstallCmd.Flags().BoolVar(&agentCmdState.InstallTrustHostKey, + "trust_host_key", false, "automatically add host keys to the ~/.ssh/known_hosts file", + ) + agentInstallCmd.Flags().StringVar(&agentCmdState.InstallServerURL, + "server_url", "https://agent.lacework.net", "server URL that agents will talk to, prefixed with `https://`", + ) +} + +func showAgentToken(_ *cobra.Command, args []string) error { + response, err := cli.LwApi.V2.AgentAccessTokens.Get(args[0]) + if err != nil { + return errors.Wrap(err, "unable to get agent access token") + } + + if cli.JSONOutput() { + return cli.OutputJSON(response.Data) + } + + cli.OutputHuman(buildAgentTokenDetailsTable(response.Data)) + return nil +} + +func updateAgentToken(_ *cobra.Command, args []string) error { + if agentCmdState.TokenUpdateEnable && agentCmdState.TokenUpdateDisable { + return errors.New("specify only one --enable or --disable") + } + + // read the current state + response, err := cli.LwApi.V2.AgentAccessTokens.Get(args[0]) + if err != nil { + return errors.Wrap(err, "unable to get agent access token") + } + actual := response.Data + updated := api.AgentAccessTokenRequest{ + TokenAlias: actual.TokenAlias, + Enabled: actual.Enabled, + Props: &api.AgentAccessTokenProps{ + CreatedTime: actual.Props.CreatedTime, + }, + } + + if agentCmdState.TokenUpdateEnable { + updated.Enabled = 1 + } + + if agentCmdState.TokenUpdateDisable { + updated.Enabled = 0 + } + + if agentCmdState.TokenUpdateName != "" { + updated.TokenAlias = agentCmdState.TokenUpdateName + } + + if agentCmdState.TokenUpdateDesc != "" { + updated.Props.Description = agentCmdState.TokenUpdateDesc + } + + response, err = cli.LwApi.V2.AgentAccessTokens.Update(args[0], updated) + if err != nil { + return errors.Wrap(err, "unable to update the agent access token") + } + + if cli.JSONOutput() { + return cli.OutputJSON(response.Data) + } + + cli.OutputHuman(buildAgentTokenDetailsTable(response.Data)) + return nil +} + +func createAgentToken(_ *cobra.Command, args []string) error { + var desc string + if len(args) == 2 { + desc = args[1] + } + + response, err := cli.LwApi.V2.AgentAccessTokens.Create(args[0], desc) + if err != nil { + return errors.Wrap(err, "unable to create agent access token") + } + + if cli.JSONOutput() { + return cli.OutputJSON(response.Data) + } + + cli.OutputHuman(buildAgentTokenDetailsTable(response.Data)) + return nil +} + +func listAgentTokens(_ *cobra.Command, _ []string) error { + response, err := cli.LwApi.V2.AgentAccessTokens.List() + if err != nil { + return errors.Wrap(err, "unable to list agent access token") + } + + if len(response.Data) == 0 { + cli.OutputHuman( + "There are no agent access tokens. Try creating one with 'lacework agent token create%s'\n", + cli.OutputNonDefaultProfileFlag(), + ) + return nil + } + + if cli.JSONOutput() { + return cli.OutputJSON(response.Data) + } + + cli.OutputHuman( + renderSimpleTable( + []string{"Token", "Name", "State"}, + agentTokensToTable(response.Data), + ), + ) + return nil +} + +func agentTokensToTable(tokens []api.AgentAccessToken) [][]string { + out := [][]string{} + for _, token := range tokens { + out = append(out, []string{ + token.AccessToken, + token.TokenAlias, + token.PrettyState(), + }) + } + return out +} + +func buildAgentTokenDetailsTable(token api.AgentAccessToken) string { + return renderOneLineCustomTable("Agent Access Token Details", + renderSimpleTable([]string{}, + [][]string{ + {"TOKEN", token.AccessToken}, + {"NAME", token.TokenAlias}, + {"DESCRIPTION", token.Props.Description}, + {"VERSION", token.Version}, + {"STATE", token.PrettyState()}, + {"CREATED AT", token.Props.CreatedTime.Format(time.RFC3339)}, + }, + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ic.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ic.go new file mode 100644 index 000000000..100ed910f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ic.go @@ -0,0 +1,205 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "context" + "fmt" + "sync" + + "github.com/aws/aws-sdk-go-v2/config" + "github.com/gammazero/workerpool" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + agentInstallAWSEC2ICCmd = &cobra.Command{ + Use: "ec2ic", + Args: cobra.NoArgs, + Short: "Use EC2InstanceConnect to securely connect to EC2 instances", + RunE: installAWSEC2IC, + Long: `This command installs the agent on all EC2 instances in an AWS account using EC2InstanceConnect. + +To filter by one or more regions: + + lacework agent aws-install ec2ic --include_regions us-west-2,us-east-2 + +To filter by instance tag: + + lacework agent aws-install ec2ic --tag TagName,TagValue + +To filter by instance tag key: + + lacework agent aws-install ec2ic --tag_key TagName + +To explicitly specify the username for all SSH logins: + + lacework agent aws-install ec2ic --ssh_username + +To provide an agent access token of your choice, use the command 'lacework agent token list', +select a token and pass it to the '--token' flag. This flag must be selected if the +'--noninteractive' flag is set. + + lacework agent aws-install ec2ic --token + +To explicitly specify the server URL that the agent will connect to: + + lacework agent aws-install ec2ic --server_url https://your.server.url.lacework.net + +To specify an AWS credential profile other than 'default': + + lacework agent aws-install ec2ic --credential_profile aws-profile-name + +AWS credentials are read from the following environment variables: +- AWS_ACCESS_KEY_ID +- AWS_SECRET_ACCESS_KEY +- AWS_SESSION_TOKEN (optional) +- AWS_REGION (optional) + +This command will only install the agent on hosts that are supported by +EC2InstanceConnect. The supported AMI types are Amazon Linux 2 and Ubuntu +16.04 and later. There may also be a region restriction. + +This command will automatically add hosts with successful connections to +'~/.ssh/known_hosts' unless specified with '--trust_host_key=false'.`, + } +) + +func init() { + // 'agent aws-install ec2ic' flags + agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallTagKey, + "tag_key", "", "only install agents on infra with this tag key set", + ) + agentInstallAWSEC2ICCmd.Flags().StringSliceVar(&agentCmdState.InstallTag, + "tag", []string{}, "only install agents on infra with this tag", + ) + agentInstallAWSEC2ICCmd.Flags().BoolVar(&agentCmdState.InstallTrustHostKey, + "trust_host_key", true, "automatically add host keys to the ~/.ssh/known_hosts file", + ) + agentInstallAWSEC2ICCmd.Flags().StringSliceVarP(&agentCmdState.InstallIncludeRegions, + "include_regions", "r", []string{}, "list of regions to filter on", + ) + agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallSshUser, + "ssh_username", "", "username to login with", + ) + agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallAgentToken, + "token", "", "agent access token", + ) + agentInstallAWSEC2ICCmd.Flags().IntVarP( + &agentCmdState.InstallMaxParallelism, + "max_parallelism", + "n", + 50, + "maximum number of workers executing AWS API calls, set if rate limits are lower or higher than normal", + ) + agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallServerURL, + "server_url", "https://agent.lacework.net", "server URL that agents will talk to, prefixed with `https://`", + ) + agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallAWSProfile, + "credential_profile", "default", "AWS credential profile to use", + ) +} + +func installAWSEC2IC(_ *cobra.Command, _ []string) error { + token := agentCmdState.InstallAgentToken + if token == "" { + if cli.InteractiveMode() { + // user didn't provide an agent token + cli.Log.Debugw("agent token not provided, asking user to select one now") + var err error + token, err = selectAgentAccessToken() + if err != nil { + return err + } + } else { + return errors.New("user did not provide or interactively select an agent token") + } + } + + runners, err := awsDescribeInstances(true /* filter on SSH support */) + if err != nil { + return err + } + + cfg, err := config.LoadDefaultConfig(context.Background(), + config.WithSharedConfigProfile(agentCmdState.InstallAWSProfile), + ) + if err != nil { + return err + } + + wg := new(sync.WaitGroup) + wp := workerpool.New(agentCmdState.InstallMaxParallelism) + for _, runner := range runners { + wg.Add(1) + + // In order to use `wp.Submit()`, the input func() must not take any arguments. + // Copy the runner info to dedicated variable in the goroutine to prevent race overwrite + runnerCopyWg := new(sync.WaitGroup) + runnerCopyWg.Add(1) + + wp.Submit(func() { + defer wg.Done() + + threadRunner := *runner + runnerCopyWg.Done() + + cli.Log.Debugw("runner info: ", + "user", threadRunner.Runner.User, + "region", threadRunner.Region, + "az", threadRunner.AvailabilityZone, + "instance_id", threadRunner.InstanceID, + "hostname", threadRunner.Runner.Hostname, + "image name", threadRunner.ImageName, + ) + err := threadRunner.SendAndUseIdentityFile(cfg) + if err != nil { + cli.Log.Debugw("ec2ic key send failed", "err", err, "runner", threadRunner.InstanceID) + return + } + + if err := verifyAccessToRemoteHost(&threadRunner.Runner); err != nil { + cli.Log.Debugw("verifyAccessToRemoteHost failed", "err", err, "runner", threadRunner.InstanceID) + return + } + + if alreadyInstalled := isAgentInstalledOnRemoteHost(&threadRunner.Runner); alreadyInstalled != nil { + cli.Log.Debugw("agent already installed on host, skipping", "runner", threadRunner.InstanceID) + return + } + + cmd := fmt.Sprintf("sudo sh -c \"curl -sSL %s | sh -s -- %s -U %s\"", + agentInstallDownloadURL, token, agentCmdState.InstallServerURL, + ) + err = runInstallCommandOnRemoteHost(&threadRunner.Runner, cmd) + if err != nil { + cli.Log.Debugw("runInstallCommandOnRemoteHost failed", "err", err, "runner", threadRunner.InstanceID) + } + if threadRunner != *runner { + cli.Log.Debugw("mutated runner", "thread_runner", threadRunner, "runner", runner) + } + }) + runnerCopyWg.Wait() + } + wg.Wait() + wp.StopWait() + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssh.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssh.go new file mode 100644 index 000000000..ee8e6b040 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssh.go @@ -0,0 +1,212 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sync" + + "github.com/gammazero/workerpool" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + agentInstallAWSSSHCmd = &cobra.Command{ + Use: "ec2ssh", + Args: cobra.NoArgs, + Short: "Use SSH to securely connect to EC2 instances", + Long: `This command installs the agent on all EC2 instances in an AWS account +using SSH. + +To filter by one or more regions: + + lacework agent aws-install ec2ssh --include_regions us-west-2,us-east-2 + +To filter by instance tag: + + lacework agent aws-install ec2ssh --tag TagName,TagValue + +To filter by instance tag key: + + lacework agent aws-install ec2ssh --tag_key TagName + +To provide an existing access token, use the '--token' flag. This flag is required +when running non-interactively ('--noninteractive' flag). The interactive command +'lacework agent token list' can be used to query existing tokens. + + lacework agent aws-install ec2ssh --token + +To explicitly specify the server URL that the agent will connect to: + + lacework agent aws-install ec2ssh --server_url https://your.server.url.lacework.net + +You will need to provide an SSH authentication method. This authentication method +should work for all instances that your tag or region filters select. Instances must +be routable from your local host. + +To authenticate using username and password: + + lacework agent aws-install ec2ssh --ssh_username --ssh_password + +To authenticate using an identity file: + + lacework agent aws-install ec2ssh -i /path/to/your/key + +To specify an AWS credential profile other than 'default': + + lacework agent aws-install ec2ssh --credential_profile aws-profile-name + +The environment should contain AWS credentials in the following variables: +- AWS_ACCESS_KEY_ID +- AWS_SECRET_ACCESS_KEY +- AWS_SESSION_TOKEN (optional), +- AWS_REGION (optional) + +This command will automatically add hosts with successful connections to +'~/.ssh/known_hosts' unless specified with '--trust_host_key=false'.`, + RunE: installAWSSSH, + } +) + +func init() { + // 'agent aws-install ec2ssh' flags + agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallTagKey, + "tag_key", "", "only install agents on infra with this tag key", + ) + agentInstallAWSSSHCmd.Flags().StringSliceVar(&agentCmdState.InstallTag, + "tag", []string{}, "only select instances with this tag", + ) + agentInstallAWSSSHCmd.Flags().StringVarP(&agentCmdState.InstallIdentityFile, + "identity_file", "i", defaultSshIdentityKey, + "identity (private key) for public key authentication", + ) + agentInstallAWSSSHCmd.Flags().BoolVar(&agentCmdState.InstallTrustHostKey, + "trust_host_key", true, "automatically add host keys to the ~/.ssh/known_hosts file", + ) + agentInstallAWSSSHCmd.Flags().StringSliceVarP(&agentCmdState.InstallIncludeRegions, + "include_regions", "r", []string{}, "list of regions to filter on", + ) + agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallPassword, + "ssh_password", "", "password for authentication", + ) + agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallSshUser, + "ssh_username", "", "username to login with", + ) + agentInstallAWSSSHCmd.Flags().IntVar(&agentCmdState.InstallSshPort, + "ssh_port", 22, "port to connect to on the remote host", + ) + agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallAgentToken, + "token", "", "agent access token", + ) + agentInstallAWSSSHCmd.Flags().IntVarP( + &agentCmdState.InstallMaxParallelism, + "max_parallelism", + "n", + 50, + "maximum number of workers executing AWS API calls, set if rate limits are lower or higher than normal", + ) + agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallServerURL, + "server_url", "https://agent.lacework.net", "server URL that agents will talk to, prefixed with `https://`", + ) + agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallAWSProfile, + "credential_profile", "default", "AWS credential profile to use", + ) +} + +func installAWSSSH(_ *cobra.Command, args []string) error { + token := agentCmdState.InstallAgentToken + if token == "" { + if cli.InteractiveMode() { + // user didn't provide an agent token + cli.Log.Debugw("agent token not provided, asking user to select one now") + var err error + token, err = selectAgentAccessToken() + if err != nil { + return err + } + } else { + return errors.New("--token flag is required when --noninteractive flag is set") + } + } + + runners, err := awsDescribeInstances(true /* filter on SSH support */) + if err != nil { + return err + } + + wg := new(sync.WaitGroup) + wp := workerpool.New(agentCmdState.InstallMaxParallelism) + for _, runner := range runners { + wg.Add(1) + + // In order to use `wp.Submit()`, the input func() must not take any arguments. + // Copy the runner info to dedicated variable in the goroutine to prevent race overwrite + runnerCopyWg := new(sync.WaitGroup) + runnerCopyWg.Add(1) + + wp.Submit(func() { + defer wg.Done() + + threadRunner := *runner + runnerCopyWg.Done() + cli.Log.Debugw("threadRunner info: ", + "user", threadRunner.Runner.User, + "region", threadRunner.Region, + "az", threadRunner.AvailabilityZone, + "instance_id", threadRunner.InstanceID, + "hostname", threadRunner.Runner.Hostname, + "image name", threadRunner.ImageName, + ) + + err := threadRunner.Runner.UseIdentityFile(agentCmdState.InstallIdentityFile) + if err != nil { + cli.Log.Warnw("unable to use provided identity file", "err", err, "thread_runner", threadRunner.InstanceID) + return + } + + if err := verifyAccessToRemoteHost(&threadRunner.Runner); err != nil { + cli.Log.Debugw("verifyAccessToRemoteHost failed", "err", err, "thread_runner", threadRunner.InstanceID) + return + } + + if alreadyInstalled := isAgentInstalledOnRemoteHost(&threadRunner.Runner); alreadyInstalled != nil { + cli.Log.Debugw("agent already installed on host, skipping", "thread_runner", threadRunner.InstanceID) + return + } + + cmd := fmt.Sprintf("sudo sh -c \"curl -sSL %s | sh -s -- %s -U %s\"", + agentInstallDownloadURL, token, agentCmdState.InstallServerURL, + ) + err = runInstallCommandOnRemoteHost(&threadRunner.Runner, cmd) + if err != nil { + cli.Log.Debugw("runInstallCommandOnRemoteHost failed", "thread_runner", threadRunner.InstanceID) + } + if threadRunner != *runner { + cli.Log.Debugw("mutated runner", "thread_runner", threadRunner, "runner", runner) + } + }) + runnerCopyWg.Wait() + } + + wg.Wait() + wp.StopWait() + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssm.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssm.go new file mode 100644 index 000000000..3d1804675 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssm.go @@ -0,0 +1,405 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/aws/aws-sdk-go-v2/service/ssm" + ssmtypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" + "github.com/gammazero/workerpool" + "github.com/lacework/go-sdk/lwrunner" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + agentInstallAWSSSMCmd = &cobra.Command{ + Use: "ec2ssm", + Args: cobra.NoArgs, + Short: "Use SSM to securely install the Lacework agent on EC2 instances", + RunE: installAWSSSM, + Long: `This command installs the Lacework agent on all EC2 instances in an AWS account using SSM. + +This command will create a role and instance profile with 'SSMManagedInstanceCore' +attached and associate that instance profile with the target instances. If the target +instances already have associated instance profiles, this command will not change +their state. This command will teardown the IAM role and instance profile before exiting. + +This command authenticates with AWS credentials from well-known locations on the user's +machine. The principal associated with these credentials should have the +'AmazonEC2FullAccess', 'IAMFullAccess' and 'AmazonSSMFullAccess' policies attached. + +Target instances must have the SSM agent installed and running for successful +installation. + +To skip IAM role / instance profile creation and instance profile association: + + lacework agent aws-install ec2ssm --skip_iam_role_creation + +To provide a preexisting IAM role with the 'SSMManagedInstanceCore' policy + + lacework agent aws-install ec2ssm --iam_role_name IAMRoleName + +To filter by one or more regions: + + lacework agent aws-install ec2ssm --include_regions us-west-2,us-east-2 + +To filter by instance tag: + + lacework agent aws-install ec2ssm --tag TagName,TagValue + +To filter by instance tag key: + + lacework agent aws-install ec2ssm --tag_key TagName + +To provide an agent access token of your choice, use the command 'lacework agent token list', +select a token and pass it to the '--token' flag. This flag must be selected if the +'--noninteractive' flag is set. + + lacework agent aws-install ec2ssm --token + +To explicitly specify the server URL that the agent will connect to: + + lacework agent aws-install ec2ssm --server_url https://your.server.url.lacework.net + +To specify an AWS credential profile other than 'default': + + lacework agent aws-install ec2ssm --credential_profile aws-profile-name + +AWS credentials are read from the following environment variables: +- AWS_ACCESS_KEY_ID +- AWS_SECRET_ACCESS_KEY +- AWS_SESSION_TOKEN (optional) +- AWS_REGION`, + } +) + +func init() { + // 'agent aws-install ec2ssm' flags + agentInstallAWSSSMCmd.Flags().StringVar(&agentCmdState.InstallTagKey, + "tag_key", "", "only install agents on infra with this tag key set", + ) + agentInstallAWSSSMCmd.Flags().StringSliceVar(&agentCmdState.InstallTag, + "tag", []string{}, "only install agents on infra with this tag", + ) + agentInstallAWSSSMCmd.Flags().StringSliceVarP(&agentCmdState.InstallIncludeRegions, + "include_regions", "r", []string{}, "list of regions to filter on", + ) + agentInstallAWSSSMCmd.Flags().StringVar(&agentCmdState.InstallAgentToken, + "token", "", "agent access token", + ) + agentInstallAWSSSMCmd.Flags().IntVarP( + &agentCmdState.InstallMaxParallelism, + "max_parallelism", + "n", + 50, + "maximum number of workers executing AWS API calls, set if rate limits are lower or higher than normal", + ) + agentInstallAWSSSMCmd.Flags().StringVar( + &agentCmdState.InstallBYORole, + "iam_role_name", + "", + "IAM role name (not ARN) with SSM policy, if not provided then an ephemeral role will be created", + ) + agentInstallAWSSSMCmd.Flags().BoolVar( + &agentCmdState.InstallSkipCreatInfra, + "skip_iam_role_creation", + false, + "set this flag to skip creating an IAM role and instance profile and associating the instance profile."+ + " Assumes all instances are already setup for SSM", + ) + agentInstallAWSSSMCmd.Flags().BoolVarP( + &agentCmdState.InstallDryRun, + "dry_run", + "d", + false, + "set this flag to print out the target instances and exit", + ) + agentInstallAWSSSMCmd.Flags().BoolVarP( + &agentCmdState.InstallForceReinstall, + "force_reinstall", + "f", + false, + "set this flag to force-reinstall the agent, even if already running on the target instance", + ) + agentInstallAWSSSMCmd.Flags().StringVar(&agentCmdState.InstallServerURL, + "server_url", "https://agent.lacework.net", "server URL that agents will talk to, prefixed with `https://`", + ) + agentInstallAWSSSMCmd.Flags().StringVar(&agentCmdState.InstallAWSProfile, + "credential_profile", "default", "AWS credential profile to use", + ) +} + +func installAWSSSM(_ *cobra.Command, _ []string) error { + token := agentCmdState.InstallAgentToken + if token == "" { + if !cli.InteractiveMode() { + return errors.New("agent token not provided. Use '--token' when running in non interactive mode") + } + // user didn't provide an agent token + cli.Log.Debug("agent token not provided, asking user to select one now") + var err error + token, err = selectAgentAccessToken() + if err != nil { + return err + } + } + + runners, err := awsDescribeInstances(false /* filter on SSH support */) + if err != nil { + return err + } + + if agentCmdState.InstallDryRun { + cli.OutputHuman("Dry run, listing target instances for installation\n") + for _, runner := range runners { + cli.Log.Info(runner) + cli.OutputHuman("target instance %v\n", *runner) + } + cli.OutputHuman("Dry run finished, exiting now.\n") + return nil + } + + cfg, err := config.LoadDefaultConfig( + context.Background(), config.WithSharedConfigProfile(agentCmdState.InstallAWSProfile), + ) + if err != nil { + return err + } + + var role types.Role + var instanceProfile types.InstanceProfile + if !agentCmdState.InstallSkipCreatInfra { + cli.StartProgress("Setting up IAM role and instance profile...") + + var err error + role, instanceProfile, err = setupSSMAccess(cfg, agentCmdState.InstallBYORole, token) + defer func() { + cli.StopProgress() + err := teardownSSMAccess(cfg, role, instanceProfile, agentCmdState.InstallBYORole) // clean up after ourselves + if err != nil { + cli.OutputHuman("got an error %v while tearing down IAM role / infra\n", err) + cli.Log.Debugw("IAM infra info after error", + "role", role, + "instance profile", instanceProfile, + "error", err, + ) + } + }() + if err != nil { + cli.StopProgress() + return err + } + cli.StopProgress() + cli.OutputHuman( + "Created role %s with policy %s and instance profile %s, added role to profile\n", + *role.RoleName, + lwrunner.SSMInstancePolicy, + *instanceProfile.InstanceProfileName, + ) + } + + var successfulCount int32 = 0 + totalCount := len(runners) + cli.StartProgress(fmt.Sprintf("Installing agents on %d total instances...", totalCount)) + defer cli.StopProgress() + + wg := new(sync.WaitGroup) + wp := workerpool.New(agentCmdState.InstallMaxParallelism) + for _, runner := range runners { + wg.Add(1) + + // In order to use `wp.Submit()`, the input func() must not take any arguments. + // Copy the runner info to dedicated variable in the goroutine to prevent race overwrite + runnerCopyWg := new(sync.WaitGroup) + runnerCopyWg.Add(1) + + wp.Submit(func() { + defer wg.Done() + + threadRunner := *runner + runnerCopyWg.Done() + + cli.Log.Debugw("runner info", + "user", threadRunner.Runner.User, + "region", threadRunner.Region, + "az", threadRunner.AvailabilityZone, + "instance_id", threadRunner.InstanceID, + "hostname", threadRunner.Runner.Hostname, + ) + + if !agentCmdState.InstallSkipCreatInfra { + // Attach an instance profile with our new role to the instance + associationID, err := threadRunner.AssociateInstanceProfileWithRunner(cfg, instanceProfile) + if err != nil { + cli.OutputHuman( + "Failed to attach instance profile %s to instance %s with error %v\n", + *instanceProfile.InstanceProfileName, + threadRunner.InstanceID, + err, + ) + return + } + defer func(cfg aws.Config, associationID string) { + cli.Log.Debugw("disassociating instance profile from runner", + "association ID", associationID, + "instance_id", threadRunner.InstanceID, + ) + err := threadRunner.DisassociateInstanceProfileFromRunner(cfg, associationID) + if err != nil { + cli.Log.Debugw("failed to disassociate instance profile from runner", + "association ID", associationID, + "instance_id", threadRunner.InstanceID, + "error", err, + ) + } + }(cfg, associationID) + } + + // Establish SSM Command connection to the runner + + // Check if agent is already installed on the host, skip if yes + // Sleep for up to 7min to wait for instance profile to associate with instance + var ssmError error + var commandOutput ssm.GetCommandInvocationOutput + const maxSleepTime int = 8 + for i := 0; i < maxSleepTime; i++ { + const agentVersionCmd = "sudo sh -c '/var/lib/lacework/datacollector -v'" + commandOutput, ssmError = threadRunner.RunSSMCommandOnRemoteHost(cfg, agentVersionCmd) + if ssmError != nil { + cli.Log.Debugw("error when checking if agent already installed on host, retrying", + "ssmError", ssmError, + "instance_id", threadRunner.InstanceID, + ) + } else if commandOutput.Status == ssmtypes.CommandInvocationStatusCancelled || + commandOutput.Status == ssmtypes.CommandInvocationStatusTimedOut { + cli.Log.Debugw("command did not complete successfully, retrying", + "command output", commandOutput, + "instance_id", threadRunner.InstanceID, + ) + } else if commandOutput.Status == ssmtypes.CommandInvocationStatusSuccess { + if agentCmdState.InstallForceReinstall { + cli.OutputHuman( + "Lacework Agent already installed on instance %s, forcing reinstall\n", + threadRunner.InstanceID, + ) + break + } else { + cli.OutputHuman( + "Lacework Agent already installed on instance %s, skipping\n", + threadRunner.InstanceID, + ) + return + } + } else if commandOutput.Status == ssmtypes.CommandInvocationStatusFailed { + cli.Log.Infow("no agent found on host, proceeding to install", + "command output", commandOutput, + "time slept in minutes", i, + "instance_id", threadRunner.InstanceID, + ) + cli.OutputHuman( + "No agent found on instance %s, proceeding to install\n", + threadRunner.InstanceID, + ) + break + } else { + cli.OutputHuman( + "Unexpected SSM command exit %v, stderr %s, skipping instance %s\n", + commandOutput.ResponseCode, + lwrunner.GetSSMCommandInvocationStdErr(commandOutput), + threadRunner.InstanceID, + ) + return + } + + if i < maxSleepTime-1 { // only sleep when we have a next iteration + cli.OutputHuman( + "Waiting for AWS to associate instance profile with instance %s, sleeping 1min, already slept %d min\n", + threadRunner.InstanceID, + i, + ) + time.Sleep(1 * time.Minute) + } + } + if ssmError != nil { // SSM still erroring after 7min of sleep, skip this host + cli.Log.Warnw("error when checking if agent already installed on host, skipping runner", + "SSM error", ssmError, + "command output", commandOutput, + "instance_id", threadRunner.InstanceID, + ) + cli.OutputHuman( + "Error %v when checking if agent already installed on instance %s, skipping\n", + ssmError, + threadRunner.InstanceID, + ) + return + } + + // Install the agent on the host + // No need to sleep because instance profile already associated + const runInstallCmdTmpl = "sudo sh -c 'curl -sSL %s | sh -s -- %s -U %s'" + runInstallCmd := fmt.Sprintf(runInstallCmdTmpl, agentInstallDownloadURL, token, agentCmdState.InstallServerURL) + commandOutput, err := threadRunner.RunSSMCommandOnRemoteHost(cfg, runInstallCmd) + if err != nil { + cli.OutputHuman( + "Install failed on instance %s with error %v, stdout %s, stderr %s\n", + threadRunner.InstanceID, + err, + lwrunner.GetSSMCommandInvocationStdOut(commandOutput), + lwrunner.GetSSMCommandInvocationStdErr(commandOutput), + ) + } else if commandOutput.Status == ssmtypes.CommandInvocationStatusSuccess { + cli.OutputHuman("Lacework agent installed successfully on host %s\n\n", threadRunner.InstanceID) + cli.OutputHuman(fmtSuccessfulAgentInstallString(lwrunner.GetSSMCommandInvocationStdOut(commandOutput))) + atomic.AddInt32(&successfulCount, 1) + } else { + cli.Log.Warnw("Install command did not return `Success` exit status for this instance", + "instance_id", threadRunner.InstanceID, + "output", commandOutput, + ) + cli.OutputHuman( + "Install command failed with %s exit status, %s stdout, %s stderr for instance %s\n", + commandOutput.Status, + lwrunner.GetSSMCommandInvocationStdOut(commandOutput), + lwrunner.GetSSMCommandInvocationStdErr(commandOutput), + threadRunner.InstanceID, + ) + } + }) + runnerCopyWg.Wait() + } + wg.Wait() + wp.StopWait() + + cli.OutputHuman( + "Completed installing the Lacework Agent on %d out of %d instances.\n", + successfulCount, + totalCount, + ) + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_gcp-install-osl.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_gcp-install-osl.go new file mode 100644 index 000000000..b8e01203b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_gcp-install-osl.go @@ -0,0 +1,215 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sync" + + "github.com/gammazero/workerpool" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + agentInstallGCPOSLCmd = &cobra.Command{ + Use: "osl", + Args: cobra.ExactArgs(1), + Short: "Use OSLogin to securely connect to GCE instances", + RunE: installGCPOSL, + Long: `This command installs the agent on all GCE instances in a GCP organization using OSLogin. + +The username of the GCP user or service account, in the form ` + "`users/`" + `, is a +required argument. + +This command will attempt to query the GCE metadata server for the current project. If this +command is not run on a GCE instance, pass the project ID as: + + lacework agent gcp-install osl --project_id my-project-id + +To filter by one or more regions: + + lacework agent gcp-install osl --include_regions us-west1,europe-west2 + +To filter by instance metadata: + + lacework agent gcp-install osl --metadata MetadataKey,MetadataValue + +To filter by instance metadata key: + + lacework agent gcp-install osl --metadata_key MetadataKey + +To provide an agent access token of your choice, use the command 'lacework agent token list', +select a token and pass it to the '--token' flag. This flag must be selected if the +'--noninteractive' flag is set. + + lacework agent gcp-install osl --token + +To explicitly specify the server URL that the agent will connect to: + + lacework agent gcp-install osl --server_url https://your.server.url.lacework.net + +GCP credentials are read using the following environment variables: +- GOOGLE_APPLICATION_CREDENTIALS + +This command will automatically add hosts with successful connections to +'~/.ssh/known_hosts' unless specified with '--trust_host_key=false'.`, + } +) + +func init() { + // 'agent gcp-install osl' flags + agentInstallGCPOSLCmd.Flags().StringVar( + &agentCmdState.InstallTagKey, + "metadata_key", + "", + "only install agents on infra with this metadata key set", + ) + agentInstallGCPOSLCmd.Flags().StringSliceVar( + &agentCmdState.InstallTag, + "metadata", + []string{}, + "only install agents on infra with this metadata", + ) + agentInstallGCPOSLCmd.Flags().StringSliceVarP( + &agentCmdState.InstallIncludeRegions, + "include_regions", + "r", + []string{}, + "list of regions to filter on", + ) + agentInstallGCPOSLCmd.Flags().BoolVar( + &agentCmdState.InstallTrustHostKey, + "trust_host_key", + true, + "automatically add host keys to the ~/.ssh/known_hosts file", + ) + agentInstallGCPOSLCmd.Flags().IntVarP( + &agentCmdState.InstallMaxParallelism, + "max_parallelism", + "n", + 50, + "maximum number of workers executing GCP API calls, set if rate limits are lower or higher than normal", + ) + agentInstallGCPOSLCmd.Flags().StringVar( + &agentCmdState.InstallProjectId, + "project_id", + "", + "ID of the GCP project, set if metadata server does not provide", + ) + agentInstallGCPOSLCmd.Flags().StringVar( + &agentCmdState.InstallAgentToken, + "token", + "", + "agent access token", + ) + agentInstallGCPOSLCmd.Flags().StringVar( + &agentCmdState.InstallServerURL, + "server_url", + "https://agent.lacework.net", + "server URL that agents will talk to, prefixed with `https://`", + ) +} + +func installGCPOSL(_ *cobra.Command, args []string) error { + token := agentCmdState.InstallAgentToken + if token == "" { + if cli.InteractiveMode() { + // user didn't provide an agent token + cli.Log.Debugw("agent token not provided, asking user to select one now") + var err error + token, err = selectAgentAccessToken() + if err != nil { + return err + } + } else { + return errors.New("user did not provide or interactively select an agent token") + } + } + + var projectID string + if agentCmdState.InstallProjectId != "" { + projectID = agentCmdState.InstallProjectId // prioritize CLI flag + } else if mdProjID, err := gcpGetProjectIDFromMetadataServer(); mdProjID != "" && err == nil { + projectID = mdProjID // if flag not passed, check the metadata server + } else { + return fmt.Errorf("could not find project ID, no metadata server (%v) and ID not passed as flag", err) + } + + runners, err := gcpDescribeInstancesInProject(args[0], projectID) + if err != nil { + return err + } + + wg := new(sync.WaitGroup) + wp := workerpool.New(agentCmdState.InstallMaxParallelism) + for _, runner := range runners { + wg.Add(1) + + // In order to use `wp.Submit()`, the input func() must not take any arguments. + // Copy the runner info to dedicated variable in the goroutine to prevent race overwrite + runnerCopyWg := new(sync.WaitGroup) + runnerCopyWg.Add(1) + + wp.Submit(func() { + defer wg.Done() + + threadRunner := *runner + runnerCopyWg.Done() + + cli.Log.Debugw("runner info: ", + "user", threadRunner.Runner.User, + "zone", threadRunner.AvailabilityZone, + "instance_id", threadRunner.InstanceID, + "hostname", threadRunner.Runner.Hostname, + ) + err := threadRunner.SendAndUseIdentityFile() + if err != nil { + cli.Log.Debugw("osl key send failed", "err", err, "runner", threadRunner.InstanceID) + return + } + + if err := verifyAccessToRemoteHost(&threadRunner.Runner); err != nil { + cli.Log.Debugw("verifyAccessToRemoteHost failed", "err", err, "runner", threadRunner.InstanceID) + return + } + + if alreadyInstalled := isAgentInstalledOnRemoteHost(&threadRunner.Runner); alreadyInstalled != nil { + cli.Log.Debugw("agent already installed on host, skipping", "runner", threadRunner.InstanceID) + return + } + + cmd := fmt.Sprintf("sudo sh -c \"curl -sSL %s | sh -s -- %s -U %s\"", + agentInstallDownloadURL, token, agentCmdState.InstallServerURL, + ) + err = runInstallCommandOnRemoteHost(&threadRunner.Runner, cmd) + if err != nil { + cli.Log.Debugw("runInstallCommandOnRemoteHost failed", "err", err, "runner", threadRunner.InstanceID) + } + if threadRunner != *runner { + cli.Log.Debugw("mutated runner", "thread_runner", threadRunner, "runner", runner) + } + }) + runnerCopyWg.Wait() + } + wg.Wait() + wp.StopWait() + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_install.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_install.go new file mode 100644 index 000000000..99ed24955 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_install.go @@ -0,0 +1,384 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "bytes" + "fmt" + "net" + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh" + + "github.com/lacework/go-sdk/lwrunner" +) + +// Official download url for installing Lacework agents +const agentInstallDownloadURL = "https://packages.lacework.net/install.sh" + +func installRemoteAgent(_ *cobra.Command, args []string) error { + var ( + user = agentCmdState.InstallSshUser + port = agentCmdState.InstallSshPort + host = args[0] + authSet = false + ) + + // verify if the user specified the username via "user@host" + if strings.Contains(host, "@") { + userHost := strings.Split(host, "@") + user = userHost[0] + host = userHost[1] + } + + // verify if the user specified the port via "host:port" + if strings.Contains(host, ":") { + userHost := strings.Split(host, ":") + host = userHost[0] + p, err := strconv.Atoi(userHost[1]) + if err != nil { + return errors.Wrap(err, "invalid port") + } + port = p + } + + cli.Log.Debugw("creating runner", "user", user, "host", host) + runner := lwrunner.New(user, host, verifyHostCallback) + + if runner.Port != port { + cli.Log.Debugw("ssh settings", "port", port) + runner.Port = port + } + + if runner.User == "" { + cli.Log.Debugw("ssh username not set") + user, err := askForUsername() + if err != nil { + return err + } + + runner.User = user + cli.Log.Debugw("ssh settings", "user", runner.User) + } + + if agentCmdState.InstallIdentityFile != defaultSshIdentityKey { + cli.Log.Debugw("ssh settings", "identity_file", agentCmdState.InstallIdentityFile) + err := runner.UseIdentityFile(agentCmdState.InstallIdentityFile) + if err != nil { + return errors.Wrap(err, "unable to use provided identity file") + } + authSet = true + } + + if agentCmdState.InstallPassword != "" { + cli.Log.Debugw("ssh settings", "auth", "password_from_flag") + runner.UsePassword(agentCmdState.InstallPassword) + authSet = true + } + + // if no authentication was set + if !authSet { + // try to use the default identity file + identityFile, err := lwrunner.DefaultIdentityFilePath() + if err != nil { + return err + } + + err = runner.UseIdentityFile(identityFile) + if err != nil { + cli.Log.Debugw("unable to use default identity file", "error", err) + + // if the default identity file didn't work, ask the user for auth details + cli.Log.Debugw("ssh auth settings not configured") + if err := askForAuthenticationDetails(runner); err != nil { + return err + } + } + } + + if err := verifyAccessToRemoteHost(runner); err != nil { + return err + } + + if err := isAgentInstalledOnRemoteHost(runner); err != nil { + return err + } + + token := agentCmdState.InstallAgentToken + if token == "" { + // user didn't provide an agent token + cli.Log.Debugw("agent token not provided") + var err error + token, err = selectAgentAccessToken() + if err != nil { + return err + } + } + cmd := fmt.Sprintf("sudo sh -c \"curl -sSL %s | sh -s -- %s -U %s\"", + agentInstallDownloadURL, token, agentCmdState.InstallServerURL) + return runInstallCommandOnRemoteHost(runner, cmd) +} + +func runInstallCommandOnRemoteHost(runner *lwrunner.Runner, cmd string) error { + cli.StartProgress(" Installing agent on the remote host...") + cli.Log.Debugw("exec remote command", "cmd", cmd) + stdout, stderr, err := runner.Exec(cmd) + cli.StopProgress() + cli.Log.Debugw("remote command results", + "cmd", cmd, + "stdout", stdout.String(), + "stderr", stderr.String(), + "error", err, + ) + if err != nil { + return errors.Wrap(formatRunnerError(stdout, stderr, err), "unable to install agent on the remote host") + } + + cli.OutputHuman("Lacework agent installed successfully on host %s\n\n", runner.Hostname) + cli.OutputHuman(fmtSuccessfulAgentInstallString(stdout.String())) + return nil +} + +func fmtSuccessfulAgentInstallString(stdout string) string { + return renderOneLineCustomTable("Installation Details", stdout, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + })) +} + +func isAgentInstalledOnRemoteHost(runner *lwrunner.Runner) error { + agentVersionCmd := "sudo sh -c \"/var/lib/lacework/datacollector -v\"" + + cli.StartProgress(" Verifying previous agent installations...") + cli.Log.Debugw("exec remote command", "cmd", agentVersionCmd) + stdout, stderr, err := runner.Exec(agentVersionCmd) + cli.StopProgress() + cli.Log.Debugw("remote command results", "cmd", agentVersionCmd, + "stdout", stdout.String(), + "stderr", stderr.String(), + "error", err, + ) + + if err != nil { + // if we couldn't run the agent version command it means that + // the agent is not yet installed, so we return nil to continue + // with the agent installation process + return nil + } + + if agentCmdState.InstallForce { + cli.Log.Debugw("forcing previous agent installation on remote host") + return nil + } + + return errors.Errorf("agent already installed on the remote host. %s", stderr.String()) +} + +func verifyAccessToRemoteHost(runner *lwrunner.Runner) error { + accessCmd := "echo we-are-in" + + cli.StartProgress(" Verifying access to the remote host...") + cli.Log.Debugw("exec remote command", "cmd", accessCmd) + stdout, stderr, err := runner.Exec(accessCmd) + cli.StopProgress() + cli.Log.Debugw("remote command results", "cmd", accessCmd, + "stdout", stdout.String(), + "stderr", stderr.String(), + "error", err, + ) + + if err != nil || !strings.Contains(stdout.String(), "we-are-in") { + return errors.Wrap(formatRunnerError(stdout, stderr, err), "unable to connect to the remote host") + } + + return nil +} + +func selectAgentAccessToken() (string, error) { + cli.StartProgress(" Searching for agent access tokens...") + response, err := cli.LwApi.V2.AgentAccessTokens.List() + cli.StopProgress() + if err != nil { + return "", errors.Wrap(err, "unable to list agent access token") + } + + var ( + tokenNames = make([]string, 0) + tokenName = "" + ) + for _, aTkn := range response.Data { + // only display tokens that have a name (a.k.a Alias) + if strings.TrimSpace(aTkn.TokenAlias) != "" { + tokenNames = append(tokenNames, aTkn.TokenAlias) + } + } + + err = survey.AskOne(&survey.Select{ + Message: "Choose an agent access token: ", + Options: tokenNames, + }, &tokenName, survey.WithValidator(survey.Required)) + if err != nil { + return "", errors.Wrap(err, "unable to ask for agent access token") + } + for _, aTkn := range response.Data { + if tokenName == aTkn.TokenAlias { + return aTkn.AccessToken, nil + } + } + + // @afiune this should never happen + return "", errors.New("something went pretty wrong here, contact support@lacework.net") +} + +// ask for the ssh username +func askForUsername() (string, error) { + var user string + + err := survey.AskOne(&survey.Input{ + Message: "SSH username:", + }, &user, survey.WithValidator(survey.Required)) + if err != nil { + return "", errors.Wrap(err, "unable to ask for username") + } + + return user, nil +} + +func verifyHostCallback(host string, remote net.Addr, key ssh.PublicKey) error { + // error if key does not exist inside the default known_hosts file, + // or if host in known_hosts file but key changed! + hostFound, err := lwrunner.CheckKnownHost(host, remote, key, "") + if hostFound && err != nil { + // the host in known_hosts file was found but key mismatch + return err + } + + // handshake because public key already exists + if hostFound && err == nil { + return nil + } + + if agentCmdState.InstallTrustHostKey { + // the user wants to add the new host to known hosts file automatically + return lwrunner.AddKnownHost(host, remote, key, "") + } + + // ask user to check if he/she trust the host public key + if askIsHostTrusted(host, key) { + // add the new host to known hosts file. + return lwrunner.AddKnownHost(host, remote, key, "") + } + + // non trusted key + return errors.New("you typed no, the agent installation was aborted!") +} + +// ask user to check if he/she trust the host public key +func askIsHostTrusted(host string, key ssh.PublicKey) bool { + // about to ask a question to the user + cli.StopProgress() + + var ( + trust = false + question = fmt.Sprintf( + "Unknown Host: %s\nFingerprint: %s\nWould you like to continue connecting?", + host, ssh.FingerprintSHA256(key), + ) + err = survey.AskOne(&survey.Confirm{ + Message: question, + Help: "By typing 'yes', the host will be added to the $HOME/.ssh/known_hosts file.", + }, &trust) + ) + if err != nil { + cli.Log.Debugw("unable to ask if host is trusted", "error", err) + return false + } + return trust +} + +func askForAuthenticationDetails(runner *lwrunner.Runner) error { + authMethod := "" + err := survey.AskOne(&survey.Select{ + Message: "Choose SSH authentication method: ", + Options: []string{"Identity File", "Password"}, + }, &authMethod, survey.WithValidator(survey.Required)) + if err != nil { + return errors.Wrap(err, "unable to ask for authentication method") + } + switch authMethod { + case "Password": + // ask for a password + var password string + err = survey.AskOne(&survey.Password{ + Message: "SSH password:", + }, &password, survey.WithValidator(survey.Required)) + if err != nil { + return errors.Wrap(err, "unable to ask for password") + } + + runner.UsePassword(password) + cli.Log.Debugw("ssh settings", "auth", "password_from_input") + default: + // ask for an identity file + var identityFile string + err = survey.AskOne(&survey.Input{ + Message: "SSH identity file:", + }, &identityFile, survey.WithValidator(survey.Required)) + if err != nil { + return errors.Wrap(err, "unable to ask for identity file") + } + + err = runner.UseIdentityFile(identityFile) + if err != nil { + return errors.Wrap(err, "unable to use provided identity file") + } + cli.Log.Debugw("ssh settings", "identity_file", identityFile) + } + + return nil +} + +func formatRunnerError(stdout, stderr bytes.Buffer, err error) error { + formatted := "" + + if stdout.String() != "" { + formatted = fmt.Sprintf("%s\n\nSTDOUT:\n%s", formatted, stdout.String()) + } + + if stderr.String() != "" { + formatted = fmt.Sprintf("%s\n\nSTDERR:\n%s", formatted, stderr.String()) + } + + if formatted == "" { + return err + } + + if err == nil { + return errors.New(formatted) + } + + return errors.Wrap(err, formatted) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_list.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_list.go new file mode 100644 index 000000000..218301267 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_list.go @@ -0,0 +1,241 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sort" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + agentListCmdState = struct { + // The available filters for the agent list command + AvailableFilters CmdFilters + + // List of filters to apply to the agent list command + Filters []string + }{} + + agentListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all hosts with a running agent", + Long: `List all hosts that have a running agent in your environment. + +You can use 'key:value' pairs to filter the list of hosts with the --filter flag. + + lacework agent list --filter 'os:Linux' --filter 'tags.VpcId:vpc-72225916' + +**NOTE:** The value can be a regular expression such as 'hostname:db-server.*' + +To filter hosts with a running agent version '5.8.0'. + + lacework agent list --filter 'agentVersion:5.8.0.*' --filter 'status:ACTIVE' + +The available keys for this command are: +` + stringSliceToMarkdownList( + agentListCmdState.AvailableFilters.GetFiltersFrom( + api.AgentInfo{}, + ), + ), + PreRunE: func(_ *cobra.Command, _ []string) error { + return validateKeyValuePairs(agentListCmdState.Filters) + }, + RunE: listAgents, + } +) + +func init() { + agentListCmd.Flags().StringSliceVar(&agentListCmdState.Filters, "filter", []string{}, + "filter results by key:value pairs (e.g. 'hostname:db-server.*')", + ) +} + +func listAgents(_ *cobra.Command, _ []string) error { + var ( + progressMsg = "Fetching list of agents" + response = &api.AgentInfoResponse{} + now = time.Now().UTC().Add(-1 * time.Minute) + before = now.AddDate(0, 0, -7) // 7 days from ago + filters = api.SearchFilter{ + TimeFilter: &api.TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + } + ) + + cleanedF := []string{} + if len(agentListCmdState.Filters) != 0 { + filters.Filters = []api.Filter{} + for _, pair := range agentListCmdState.Filters { + + kv := strings.Split(pair, ":") + if len(kv) != 2 || kv[0] == "" || kv[1] == "" { + cli.Log.Warnw("malformed filter, ignoring", + "pair", pair, "expected_format", "key:value", + ) + continue + } + + cleanedF = append(cleanedF, pair) + cli.Log.Infow("adding filter", "key", kv[0], "value", kv[1]) + filters.Filters = append(filters.Filters, api.Filter{ + Field: kv[0], + Expression: cli.lqlOperator, // @afiune we use rlike to allow user to pass regex + Value: kv[1], + }) + } + + if len(cleanedF) != 0 { + progressMsg = fmt.Sprintf( + "%s with filters (%s)", + progressMsg, strings.Join(cleanedF, ", "), + ) + } + + agentListCmdState.Filters = cleanedF + } + + cli.StartProgress(fmt.Sprintf("%s...", progressMsg)) + err := cli.LwApi.V2.AgentInfo.Search(response, filters) + cli.StopProgress() + if err != nil { + if strings.Contains(err.Error(), "Invalid field") { + return errors.Errorf("the provided filter key is invalid.\n\nThe available keys for this command are:\n%s", + stringSliceToMarkdownList(agentListCmdState.AvailableFilters.Filters)) + } + return errors.Wrap(err, "unable to list agents via AgentInfo search") + } + + agents := response.Data + + if response.Paging.Urls.NextPage != "" { + totalPages := response.Paging.TotalRows / response.Paging.Rows + + agents = []api.AgentInfo{} + page := 0 + for { + agents = append(agents, response.Data...) + + cli.StartProgress(fmt.Sprintf("%s [%.0f%%]...", progressMsg, float32(page)/float32(totalPages)*100)) + pageOk, err := cli.LwApi.NextPage(response) + if err == nil && pageOk { + page += 1 + continue + } + break + } + response.ResetPaging() + response.Data = agents + } + + cli.StartProgress("Crunching agent data...") + // Sort agents by last updated time (last time they check-in) + sort.Slice(agents, func(i, j int) bool { + return agents[i].LastUpdate.After(agents[j].LastUpdate) + }) + cli.StopProgress() + + if cli.JSONOutput() { + return cli.OutputJSON(agents) + } + + if len(agents) == 0 { + if len(agentListCmdState.Filters) != 0 { + cli.OutputHuman("No agents found with the provided filter(s).\n") + } else { + cli.OutputHuman( + "There are no agents running in your account.\n\nTry installing one with 'lacework agent install %s'\n", + cli.OutputNonDefaultProfileFlag(), + ) + } + return nil + } + + cli.OutputHuman( + renderSimpleTable( + []string{ + "MID", "Status", "Agent Version", "Hostname", "Name", + "Internal IP", "External IP", "OS Arch", "Last Check-in", "Created Time", + }, + agentInfoToListAgentTable(agents), + ), + ) + + // breadcrumbs + if len(agentListCmdState.Filters) == 0 { + cli.OutputHuman("\nTry adding '--filter status:ACTIVE' to show only active agents.\n") + } else if hasWindowsAgents(agents) && !agentListFiltersContains("os") { + cli.OutputHuman("\nTry adding '--filter os:Windows' to show only windows agents.\n") + } else if !hasWindowsAgents(agents) && !agentListFiltersContains("agentVersion") { + cli.OutputHuman("\nTry adding '--filter \"agentVersion:5.8.0.*\"' to show agents with version '5.8.0'.\n") + } + return nil +} + +func agentInfoToListAgentTable(agents []api.AgentInfo) [][]string { + out := [][]string{} + for _, a := range agents { + out = append(out, []string{ + fmt.Sprintf("%d", a.Mid), + a.Status, + a.AgentVersion, + a.Hostname, + a.Tags.Name, + a.Tags.InternalIP, + a.Tags.ExternalIP, + fmt.Sprintf("%s/%s", a.Tags.Os, a.Tags.Arch), + a.LastUpdate.Format(time.RFC3339), + a.CreatedTime.Format(time.RFC3339), + }) + } + + return out +} + +// agentListFiltersContains returns true if one of filters passed to this function +// matches the filters provided to the 'agent list' command +func agentListFiltersContains(filters ...string) bool { + for _, cmdFilter := range agentListCmdState.Filters { + for _, expectedFilter := range filters { + if strings.Contains(cmdFilter, expectedFilter) { + return true + } + } + } + return false +} + +// hasWindowsAgents returns true if there the user has windows agents +func hasWindowsAgents(agents []api.AgentInfo) bool { + for _, a := range agents { + if a.Os == "Windows" { + return true + } + } + return false +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert.go new file mode 100644 index 000000000..9ec915001 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert.go @@ -0,0 +1,163 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "net/url" + "os/exec" + "runtime" + "strconv" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +type alertCmdStateType struct { + Comment string + End string + Fixable bool + Range string + Reason int + Scope string + Severity string + Status string + Start string + Type string +} + +// hasFilters returns true if certain filters are present +// in the command state. excludes time filters (start, end, range). +func (s alertCmdStateType) hasFilters() bool { + // severity / status / type filters + if s.Severity != "" || s.Status != "" || s.Type != "" { + return true + } + return s.Fixable +} + +var ( + alertCmdState = alertCmdStateType{} + + // alertCmd represents the alert parent command + alertCmd = &cobra.Command{ + Use: "alert", + Aliases: []string{"alerts"}, + Short: "Inspect and manage alerts", + Long: `Inspect and manage alerts. + +Lacework provides real-time alerts that are interactive and manageable. + +Each alert contains various metadata information, such as severity level, type, +status, alert category, and associated tags. + +You can also post a comment to an alert's timeline; or change an alert status +from Open to Closed. + +For more information about alerts, visit: + +https://docs.lacework.com/console/alerts-overview + +To view all alerts in your Lacework account. + + lacework alert ls + +To show an alert. + + lacework alert show + +To close an alert. + + lacework alert close +`, + } + + alertOpenCmd = &cobra.Command{ + Use: "open ", + Short: "Open a specified alert in a web browser", + Long: `Open a specified alert in a web browser.`, + Args: cobra.ExactArgs(1), + RunE: openAlert, + } +) + +func init() { + // add the alert command + rootCmd.AddCommand(alertCmd) + + // add the alert open command + alertCmd.AddCommand(alertOpenCmd) +} + +// Generates a URL similar to: +// +// => https://account.lacework.net/ui/investigation/monitor/AlertInbox/123/details?accountName=subaccount +func alertLinkBuilder(id int) string { // nolint + return alertLinkBuilderWithCLI(cli, id) +} + +func alertLinkBuilderWithCLI(c *cliState, id int) string { + u, err := url.Parse( + fmt.Sprintf( + "https://%s.lacework.net/ui/investigation/monitor/AlertInbox/%d/details", + c.Account, + id, + ), + ) + if err != nil { + return "" + } + + q := u.Query() + q.Set("accountName", c.Account) + if c.Subaccount != "" { + q.Set("accountName", c.Subaccount) + } + if r := q.Encode(); r != "" { + u.RawQuery = r + } + return u.String() +} + +func openAlert(_ *cobra.Command, args []string) error { + cli.Log.Debugw("opening alert", "alert", args[0]) + + id, err := strconv.Atoi(args[0]) + if err != nil { + return errors.New("alert ID must be a number") + } + + url := alertLinkBuilder(id) + + switch runtime.GOOS { + case "linux": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + err = exec.Command("open", url).Start() + default: + err = fmt.Errorf("unsupported platform\n\nNavigate to %s", url) + } + if err != nil { + return errors.Wrap(err, "unable to open web browser") + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_channel.go new file mode 100644 index 000000000..48f6f91a9 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_channel.go @@ -0,0 +1,227 @@ +package cmd + +import ( + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + alertChannelCommand = &cobra.Command{ + Use: "alert-channel", + Aliases: []string{"alert-channels", "ac"}, + Short: "Manage alert channels", + Long: "Manage alert channels integrations with Lacework", + } + + // alertChannelsListCmd represents the list sub-command inside the alert channels command + alertChannelListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all available alert channel integrations", + Args: cobra.NoArgs, + RunE: alertChannelList, + } + + // alertChannelShowCmd represents the show sub-command inside the alert channel command + alertChannelShowCmd = &cobra.Command{ + Use: "show", + Aliases: []string{"get"}, + Short: "Show a single alert channel integration", + Args: cobra.ExactArgs(1), + RunE: alertChannelShow, + } + + // alertChannelCreateCmd represents the show sub-command inside the alert channels command + alertChannelCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new alert channel integration", + Args: cobra.NoArgs, + RunE: alertChannelCreate, + } + + // alertChannelDeleteCmd represents the delete sub-command inside the alert channels command + alertChannelDeleteCmd = &cobra.Command{ + Use: "delete", + Aliases: []string{"rm"}, + Short: "Delete a alert channel integration", + Args: cobra.ExactArgs(1), + RunE: alertChannelDelete, + } +) + +func init() { + // add the alert-channel command + rootCmd.AddCommand(alertChannelCommand) + alertChannelCommand.AddCommand(alertChannelListCmd) + alertChannelCommand.AddCommand(alertChannelShowCmd) + alertChannelCommand.AddCommand(alertChannelCreateCmd) + alertChannelCommand.AddCommand(alertChannelDeleteCmd) +} + +func alertChannelsToTable(alertChannels []api.AlertChannelRaw) [][]string { + var out [][]string + for _, cadata := range alertChannels { + out = append(out, []string{ + cadata.IntgGuid, + cadata.Name, + cadata.Type, + cadata.Status(), + cadata.StateString(), + }) + } + return out +} + +func alertChannelList(_ *cobra.Command, _ []string) error { + alertChannels, err := cli.LwApi.V2.AlertChannels.List() + + if err != nil { + return errors.Wrap(err, "unable to get alert channels") + } + + if cli.JSONOutput() { + return cli.OutputJSON(alertChannels.Data) + } + + if len(alertChannels.Data) == 0 { + cli.OutputHuman("No alert channels found.\n") + return nil + } + + cli.OutputHuman( + renderSimpleTable( + []string{"alert channel GUID", "Name", "Type", "Status", "State"}, + alertChannelsToTable(alertChannels.Data), + ), + ) + return nil +} + +func alertChannelShow(_ *cobra.Command, args []string) error { + var ( + alertChannel api.AlertChannelResponse + out [][]string + ) + cli.StartProgress("Fetching alert channel...") + time.Sleep(time.Second * 3) + err := cli.LwApi.V2.AlertChannels.Get(args[0], &alertChannel) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to retrieve alert channel") + } + + if cli.JSONOutput() { + return cli.OutputJSON(alertChannel.Data) + } + + out = append(out, []string{alertChannel.Data.IntgGuid, + alertChannel.Data.Name, + alertChannel.Data.Type, + alertChannel.Data.Status(), + alertChannel.Data.StateString()}) + + cli.OutputHuman(renderSimpleTable([]string{"Alert Channel GUID", "Name", "Type", "Status", "State"}, out)) + cli.OutputHuman("\n") + cli.OutputHuman(buildDetailsTable(alertChannel.Data)) + return nil +} + +func alertChannelCreate(_ *cobra.Command, _ []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + + err := promptCreateAlertChannel() + if err != nil { + return errors.Wrap(err, "unable to create alert channel") + } + + cli.OutputHuman("The alert channel was created.\n") + return nil +} + +func alertChannelDelete(_ *cobra.Command, args []string) error { + cli.StartProgress(" Deleting alert channel...") + err := cli.LwApi.V2.AlertChannels.Delete(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to delete alert channel") + } + cli.OutputHuman("The alert channel %s was deleted.\n", args[0]) + return nil +} + +func promptCreateAlertChannel() error { + var ( + alertChannel = "" + prompt = &survey.Select{ + Message: "Choose an alert channel type to create: ", + Options: []string{ + "Slack", + "Email", + "Amazon S3", + "Cisco Webex", + "Datadog", + "GCP PubSub", + "Microsoft Teams", + "New Relic Insights", + "Webhook", + "VictorOps", + "Splunk", + "QRadar", + "Service Now", + "PagerDuty", + "Amazon CloudWatch", + "Jira Cloud", + "Jira Server", + }, + } + err = survey.AskOne(prompt, &alertChannel) + ) + if err != nil { + return err + } + + switch alertChannel { + case "Slack": + return createSlackAlertChannelIntegration() + case "Email": + return createEmailAlertChannelIntegration() + case "GCP PubSub": + return createGcpPubSubChannelIntegration() + case "Microsoft Teams": + return createMicrosoftTeamsChannelIntegration() + case "New Relic Insights": + return createNewRelicAlertChannelIntegration() + case "Amazon S3": + return createAwsS3ChannelIntegration() + case "Cisco Webex": + return createCiscoWebexChannelIntegration() + case "Datadog": + return createDatadogIntegration() + case "Webhook": + return createWebhookIntegration() + case "VictorOps": + return createVictorOpsChannelIntegration() + case "Splunk": + return createSplunkIntegration() + case "PagerDuty": + return createPagerDutyAlertChannelIntegration() + case "QRadar": + return createQRadarAlertChannelIntegration() + case "Service Now": + return createServiceNowAlertChannelIntegration() + case "Amazon CloudWatch": + return createAwsCloudWatchAlertChannelIntegration() + case "Jira Cloud": + return createJiraAlertChannelIntegration(api.JiraCloudAlertType) + case "Jira Server": + return createJiraAlertChannelIntegration(api.JiraServerAlertType) + default: + return errors.New("unknown alert channel type") + } +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_close.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_close.go new file mode 100644 index 000000000..86affdc36 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_close.go @@ -0,0 +1,172 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strconv" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +const ( + ReasonUnset = -1 +) + +var ( + // alertCloseCmd represents the alert close command + alertCloseCmd = &cobra.Command{ + Use: "close ", + Short: "Close an alert", + Long: `Use this command to change the status of an alert to closed. + +The reason for closing the alert must be provided from the following options: + + * 0 - Other + * 1 - False positive + * 2 - Not enough information + * 3 - Malicious and have resolution in place + * 4 - Expected because of routine testing. + +Reasons may be provided inline or via prompt. + +If you choose Other, a comment is required and should contain a brief explanation of why the alert is closed. +Comments may be provided inline or via editor. + +**Note: A closed alert cannot be reopened. You will be prompted to confirm closure of the alert. +This prompt can be bypassed with the --noninteractive flag** +`, + Args: cobra.ExactArgs(1), + RunE: closeAlert, + } +) + +func init() { + alertCmd.AddCommand(alertCloseCmd) + + // reason flag + alertCloseCmd.Flags().IntVarP( + &alertCmdState.Reason, + "reason", "r", ReasonUnset, + "the reason for closing the alert", + ) + + // comment flag + alertCloseCmd.Flags().StringVarP( + &alertCmdState.Comment, + "comment", "c", "", + "a comment to associate with the alert closure", + ) +} + +func inputReason() (reason int, err error) { + if alertCmdState.Reason != ReasonUnset { + reason = alertCmdState.Reason + return + } + + prompt := &survey.Select{ + Message: "Reason:", + Options: api.AlertCloseReasons.GetOrderedReasonStrings(), + } + err = survey.AskOne(prompt, &reason) + + return +} + +func inputComment() (comment string, err error) { + if alertCmdState.Comment != "" { + comment = alertCmdState.Comment + return + } + + prompt := &survey.Editor{ + Message: "Type a comment", + FileName: "alert.comment", + } + err = survey.AskOne(prompt, &comment) + + return +} + +func closeAlert(_ *cobra.Command, args []string) error { + cli.Log.Debugw("closing alert", "alert", args[0]) + + id, err := strconv.Atoi(args[0]) + if err != nil { + return errors.New("alert ID must be a number") + } + + // if comment is not supplied inline + // validate that the alert exists + if alertCmdState.Comment == "" || alertCmdState.Reason == ReasonUnset { + exists, err := cli.LwApi.V2.Alerts.Exists(id) + // if we are very certain the alert doesn't exist + if !exists && err == nil { + return errors.New(fmt.Sprintf("alert %d does not exist", id)) + } + } + + reason, err := inputReason() + if err != nil { + return errors.Wrap(err, "unable to process alert close reason") + } + + comment, err := inputComment() + if err != nil { + return errors.Wrap(err, "unable to process alert close comment") + } + + // ask user to confirm + if !cli.nonInteractive { + var confirm bool + prompt := &survey.Confirm{ + Message: fmt.Sprintf( + "Are you sure you want to close alert %d. Alerts cannot be reopend.", id), + Default: false, + } + err = survey.AskOne(prompt, &confirm) + if err != nil { + return errors.Wrap(err, "unable to confirm alert close attempt") + } + if !confirm { + return nil + } + } + + request := api.AlertCloseRequest{ + AlertID: id, + Reason: reason, + Comment: comment, + } + + cli.StartProgress(" Closing alert...") + _, err = cli.LwApi.V2.Alerts.Close(request) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to close alert") + } + + cli.OutputHuman("Alert %d was successfully closed.\n", id) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_comment.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_comment.go new file mode 100644 index 000000000..c4323d897 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_comment.go @@ -0,0 +1,90 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strconv" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // alertCommentCmd represents the alert comment command + alertCommentCmd = &cobra.Command{ + Use: "comment ", + Short: "Add a comment", + Long: `Post a user comment on an alert's timeline . + +Comments may be provided inline or via editor. +`, + Args: cobra.ExactArgs(1), + RunE: commentAlert, + } +) + +func init() { + alertCmd.AddCommand(alertCommentCmd) + + // comment flag + alertCommentCmd.Flags().StringVarP( + &alertCmdState.Comment, + "comment", "c", "", + "a comment to add to the alert", + ) +} + +func commentAlert(_ *cobra.Command, args []string) error { + cli.Log.Debugw("commenting on alert", "alert", args[0]) + + id, err := strconv.Atoi(args[0]) + if err != nil { + return errors.New("alert ID must be a number") + } + + // if comment is not supplied inline + // validate that the alert exists + if alertCmdState.Comment == "" { + exists, err := cli.LwApi.V2.Alerts.Exists(id) + // if we are very certain the alert doesn't exist + if !exists && err == nil { + return errors.New(fmt.Sprintf("alert %d does not exist", id)) + } + } + + comment, err := inputComment() + if err != nil { + return errors.Wrap(err, "unable to process alert comment") + } + + cli.StartProgress(" Adding alert comment...") + commentResponse, err := cli.LwApi.V2.Alerts.Comment(id, comment) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to add alert comment") + } + + if cli.JSONOutput() { + return cli.OutputJSON(commentResponse.Data) + } + + cli.OutputHuman("Comment added successfully.") + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list.go new file mode 100644 index 000000000..ac93fc93a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list.go @@ -0,0 +1,291 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/lwseverity" + "github.com/lacework/go-sdk/lwtime" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // alertListCmd represents the alert list command + alertListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all alerts", + Long: `List all alerts. + +By default, alerts are shown for the last 24 hours. +Use a custom time range by suppling a range flag... + + lacework alert ls --range "last 7 days" + +Or by specifying start and end flags. + + lacework alert ls --start "-7d@d" --end "now" + +Start and end times may be specified in one of the following formats: + A. A relative time specifier + B. RFC3339 date and time + C. Epoch time in milliseconds + +To list open alerts of type "NewViolations" with high or critical severity. + + lacework alert ls --status Open --severity high --type NewViolations +`, + Args: cobra.NoArgs, + PreRunE: func(_ *cobra.Command, args []string) error { + // validate severity + if alertCmdState.Severity != "" && !array.ContainsStr( + api.ValidAlertSeverities, alertCmdState.Severity) { + return errors.Wrap( + errors.New(fmt.Sprintf("the severity (%s) is not valid, use one of (%s)", + alertCmdState.Severity, strings.Join(api.ValidAlertSeverities, ", "))), + "unable to list alerts", + ) + } + // validate status + if alertCmdState.Status != "" && !array.ContainsStr( + api.ValidAlertStatuses, alertCmdState.Status) { + return errors.Wrap( + errors.New(fmt.Sprintf("the status (%s) is not valid, use one of (%s)", + alertCmdState.Status, strings.Join(api.ValidAlertStatuses, ", "))), + "unable to list alerts", + ) + } + return nil + }, + RunE: listAlert, + } +) + +func init() { + alertCmd.AddCommand(alertListCmd) + + // range time flag + alertListCmd.Flags().StringVar( + &alertCmdState.Range, + "range", "", + "natural time range for alerts", + ) + + // start time flag + alertListCmd.Flags().StringVar( + &alertCmdState.Start, + "start", "-24h", + "start time for alerts", + ) + // end time flag + alertListCmd.Flags().StringVar( + &alertCmdState.End, + "end", "now", + "end time for alerts", + ) + + // severity flag + alertListCmd.Flags().StringVar( + &alertCmdState.Severity, + "severity", "", + fmt.Sprintf( + "filter alerts by severity threshold (%s)", + strings.Join(api.ValidAlertSeverities, ", "), + ), + ) + + // status flag + alertListCmd.Flags().StringVar( + &alertCmdState.Status, + "status", "", + fmt.Sprintf( + "filter alerts by status (%s)", + strings.Join(api.ValidAlertStatuses, ", "), + ), + ) + + // type flag + alertListCmd.Flags().StringVar( + &alertCmdState.Type, + "type", "", + "filter alerts by type", + ) + + // fixable + if cli.isRemediateInstalled() { + alertListCmd.Flags().BoolVar( + &alertCmdState.Fixable, + "fixable", false, + "filter alerts by fixability", + ) + } +} + +func alertListTable(alerts api.Alerts) (out [][]string) { + alerts.SortByID() + alerts.SortBySeverity() + + for _, alert := range alerts { + out = append(out, []string{ + strconv.Itoa(alert.ID), + alert.Type, + alert.Name, + alert.Severity, + alert.StartTime, + alert.EndTime, + alert.Status, + }) + } + + return +} + +func renderAlertListTable(alerts api.Alerts) { + cli.OutputHuman( + renderCustomTable( + []string{"Alert ID", "Type", "Name", "Severity", "Start Time", "End Time", "Status"}, + alertListTable(alerts), + tableFunc(func(t *tablewriter.Table) { + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) +} + +func listAlert(_ *cobra.Command, _ []string) error { + cli.Log.Debugw("listing alerts") + + var ( + err error + start time.Time + end time.Time + msg string = "unable to list alerts" + ) + + // use of if/else intentional here based on logic paths for determining start and end time.Time values + // if cli user has specified a range we use ParseNatural which gives us start and end time.Time values + // otherwise we need to convert alertCmdState start and end strings to time.Time values using parseQueryTime + if alertCmdState.Range != "" { + cli.Log.Debugw("retrieving natural time range") + + start, end, err = lwtime.ParseNatural(alertCmdState.Range) + if err != nil { + return errors.Wrap(err, msg) + } + } else { + // parse start + start, err = parseQueryTime(alertCmdState.Start) + if err != nil { + return errors.Wrap(err, msg) + } + // parse end + end, err = parseQueryTime(alertCmdState.End) + if err != nil { + return errors.Wrap(err, msg) + } + } + + filters := []api.Filter{} + + if alertCmdState.Status != "" { + filters = append(filters, api.Filter{ + Expression: "eq", + Field: string(api.AlertsFilterFieldStatus), + Value: alertCmdState.Status, + }) + } + + if alertCmdState.Type != "" { + filters = append(filters, api.Filter{ + Expression: "eq", + Field: string(api.AlertsFilterFieldType), + Value: alertCmdState.Type, + }) + } + + cli.StartProgress( + fmt.Sprintf( + " Fetching alerts in the time range %s - %s...", + start.Format("2006-Jan-2 15:04:05 MST"), + end.Format("2006-Jan-2 15:04:05 MST"), + ), + ) + var searchRequest = api.SearchFilter{ + TimeFilter: &api.TimeFilter{StartTime: &start, EndTime: &end}, + Filters: filters, + } + + listResponse, err := cli.LwApi.V2.Alerts.SearchAll(searchRequest) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, msg) + } + + // filter severity + alerts := api.Alerts{} + for _, alert := range listResponse.Data { + // filter severity if desired + if lwseverity.ShouldFilter(alert.Severity, alertCmdState.Severity) { + continue + } + alerts = append(alerts, alert) + } + + // filter fixable + if alertCmdState.Fixable { + templateIDs, err := getRemediationTemplateIDs() + if err != nil { + return errors.Wrap(err, "unable to filter by alert fixability") + } + alerts = filterFixableAlerts(alerts, templateIDs) + } + + if cli.JSONOutput() { + return cli.OutputJSON(alerts) + } + + if len(alerts) == 0 { + if alertCmdState.hasFilters() { + cli.OutputHuman(fmt.Sprintf("%s %s\n", + "No alerts match the specified filters within the given time range.", + "Try removing filters or expanding the time range.", + )) + return nil + } + cli.OutputHuman("There are no alerts in the specified time range.\n") + return nil + } + renderAlertListTable(alerts) + + // breadcrumb + cli.OutputHuman("\nUse 'lacework alert show ' to see details for a specific alert.\n") + if alertCmdState.Fixable { + cli.OutputHuman("Use 'lacework remediate alert ' to fix a specific alert.\n") + } + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list_fixable.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list_fixable.go new file mode 100644 index 000000000..3b7787866 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list_fixable.go @@ -0,0 +1,108 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" +) + +const remediateComponentName string = "remediate" + +// isRemediateInstalled returns true if the remediate component is installed +func (c *cliState) isRemediateInstalled() bool { + return c.IsComponentInstalled(remediateComponentName) +} + +// getTemplateIdentifiers runs the remediate component to retrieve a list +// of remediation template identifiers +func getRemediationTemplateIDs() ([]string, error) { + remediate, found := cli.LwComponents.GetComponent(remediateComponentName) + if !found { + return []string{}, errors.New("remediate component not found") + } + + // set up environment variables + envs := []string{ + fmt.Sprintf("LW_COMPONENT_NAME=%s", remediateComponentName), + "LW_JSON=true", + "LW_NONINTERACTIVE=true", + } + for _, e := range cli.envs() { + // don't let LW_JSON / LW_NONINTERACTIVE through here + if strings.HasPrefix(e, "LW_JSON=") || strings.HasPrefix(e, "LW_NONINTERACTIVE=") { + continue + } + envs = append(envs, e) + } + stdout, stderr, err := remediate.RunAndReturn([]string{"ls", "templates"}, nil, envs...) + if err != nil { + cli.Log.Debugw("remediate error details", "stderr", stderr) + return []string{}, err + } + + var templates []map[string]interface{} + err = json.Unmarshal([]byte(stdout), &templates) + if err != nil { + return []string{}, err + } + + templateIDs := []string{} + for _, template := range templates { + v, ok := template["id"] + if !ok { + continue + } + s, ok := v.(string) + if !ok { + continue + } + templateIDs = append(templateIDs, s) + } + return templateIDs, nil +} + +// filterFixableAlerts identifies which alerts have corresponding remediation template IDs +// and returns those which don't +func filterFixableAlerts(alerts api.Alerts, templateIDs []string) api.Alerts { + fixableAlerts := api.Alerts{} + for _, alert := range alerts { + if alert.PolicyID == "" { + continue + } + found := false + // Historically alerts did not consistently populate policyID and + // templates were named arbitrarily. + // If and when policies explicitly reference templates we will no longer need + // any inference logic. + for _, id := range templateIDs { + if id == alert.PolicyID { + fixableAlerts = append(fixableAlerts, alert) + found = true + break + } + } + if found { + continue + } + // Another interesting problem that we have is that policyIDs are dynamic + // For instance, on dev7 policy lwcustom-11 is dev7-lwcustom-11 + // On some other environment it might be someother-lwcustom-11 + dynamicIDRE := regexp.MustCompile(`^\w+-\d+$`) + // Iterate through the templates looking for those with dynamic policy IDs + for _, id := range templateIDs { + if dynamicIDRE.MatchString(id) { + // if the policyID of the alert ends with - + // i.e. if dev7-lwcustom-11 endswith -lwcustom-11 + if strings.HasSuffix(alert.PolicyID, fmt.Sprintf("-%s", id)) { + fixableAlerts = append(fixableAlerts, alert) + break + } + } + } + } + return fixableAlerts +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_profiles.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_profiles.go new file mode 100644 index 000000000..5e1b7472f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_profiles.go @@ -0,0 +1,458 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sort" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + + "github.com/lacework/go-sdk/api" +) + +var ( + // alert-profiles command is used to manage lacework alert profiles + alertProfilesCommand = &cobra.Command{ + Use: "alert-profile", + Aliases: []string{"alert-profiles", "ap"}, + Short: "Manage alert profiles", + Long: `Manage alert profiles to define how your LQL queries get consumed into alerts. + +An alert profile consists of the ID of the new profile, the ID of an existing profile that +the new profile extends, and a list of alert templates.`, + } + + // list command is used to list all lacework alert profiles + alertProfilesListCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all alert profiles", + Long: "List all alert profiles configured in your Lacework account.", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + alertProfiles, err := cli.LwApi.V2.Alert.Profiles.List() + if err != nil { + return errors.Wrap(err, "unable to get alert profiles") + } + if len(alertProfiles.Data) == 0 { + msg := `There are no alert profiles configured in your account. + +To manage alerting, integrate alert profiles using the command: + + lacework alert-profile create + +To integrate alert profiles via the Lacework Console, log in to your account at: + + https://%s.lacework.net + +Then go to Settings > Alert Profiles. +` + cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) + return nil + } + if cli.JSONOutput() { + return cli.OutputJSON(alertProfiles) + } + + var rows [][]string + for _, profile := range alertProfiles.Data { + rows = append(rows, []string{profile.Guid, profile.Extends}) + } + sort.Slice(rows, func(i, j int) bool { + return rows[i][0] < rows[j][0] + }) + + cli.OutputHuman(renderSimpleTable([]string{"ID", "EXTENDS"}, rows)) + return nil + }, + } + // show command is used to retrieve a lacework alert profile by id + alertProfilesShowCommand = &cobra.Command{ + Use: "show ", + Short: "Show an alert profile by ID", + Aliases: []string{"get"}, + Long: "Show a single alert profile by its ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var response api.AlertProfileResponse + err := cli.LwApi.V2.Alert.Profiles.Get(args[0], &response) + if err != nil { + return errors.Wrap(err, "unable to get alert profile") + } + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + + alertProfile := response.Data + var headers [][]string + headers = append(headers, []string{alertProfile.Guid, alertProfile.Extends}) + cli.OutputHuman(renderSimpleTable([]string{"ALERT PROFILE ID", "EXTENDS"}, headers)) + cli.OutputHuman("\n") + cli.OutputHuman(buildAlertProfileDetailsTable(alertProfile)) + + return nil + }, + } + + // delete command is used to remove a lacework alert profile by id + alertProfilesDeleteCommand = &cobra.Command{ + Use: "delete ", + Short: "Delete an alert profile", + Long: "Delete a single alert profile by its ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + err := cli.LwApi.V2.Alert.Profiles.Delete(args[0]) + if err != nil { + return errors.Wrap(err, "unable to delete alert profile") + } + cli.OutputHuman("The alert profile with GUID %s was deleted \n", args[0]) + return nil + }, + } + + // create command is used to create a new lacework alert profile + alertProfilesCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new alert profile", + RunE: func(_ *cobra.Command, args []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + + response, err := promptCreateAlertProfile() + if err != nil { + return errors.Wrap(err, "unable to create alert profile") + } + + cli.OutputHuman(fmt.Sprintf("The alert profile was created with ID %s \n", response.Data.Guid)) + return nil + }, + } + + // update command is used to update an existing lacework alert profile + alertProfilesUpdateCommand = &cobra.Command{ + Use: "update [alert_profile_id]", + Short: "Update alert templates from an existing alert profile", + Args: cobra.MaximumNArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + response, err := promptUpdateAlertProfile(args) + if err != nil { + return err + } + + cli.OutputHuman("The alert profile %s was updated \n", response.Data.Guid) + return nil + }, + } +) + +func init() { + // add the alert-profile command + rootCmd.AddCommand(alertProfilesCommand) + + // add sub-commands to the alert-profile command + alertProfilesCommand.AddCommand(alertProfilesListCommand) + alertProfilesCommand.AddCommand(alertProfilesShowCommand) + alertProfilesCommand.AddCommand(alertProfilesCreateCommand) + alertProfilesCommand.AddCommand(alertProfilesUpdateCommand) + alertProfilesCommand.AddCommand(alertProfilesDeleteCommand) +} + +func buildAlertProfileDetailsTable(profile api.AlertProfile) string { + var details [][]string + + detailsTable := &strings.Builder{} + + for _, alert := range profile.Alerts { + details = append(details, []string{alert.Name, alert.EventName, alert.Description, alert.Subject}) + } + + detailsTable.WriteString(renderOneLineCustomTable("ALERT TEMPLATES", + renderSimpleTable([]string{"NAME", "EVENT NAME", "DESCRIPTION", "SUBJECT"}, details), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ), + ) + + if len(profile.Fields) > 0 { + var fields []string + for _, f := range profile.Fields { + fields = append(fields, f.Name) + } + + detailsTable.WriteString(renderOneLineCustomTable("FIELDS", "", + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + // format field names into even rows + rowWidth := 10 + var j int + for i := 0; i < len(fields); i += rowWidth { + j += rowWidth + if j > len(fields) { + j = len(fields) + } + t.Append([]string{strings.Join(fields[i:j], ", ")}) + } + }), + )) + detailsTable.WriteString("\nUse a field inside an alert template subject or description by enclosing " + + "it in double brackets. For example: '{{FIELD_NAME}}'\n") + } + + return detailsTable.String() +} + +func promptUpdateAlertProfile(args []string) (api.AlertProfileResponse, error) { + var ( + msg = "unable to update alert profile" + profileID string + err error + ) + if len(args) == 0 { + profileID, err = promptSelectProfile() + if err != nil { + return api.AlertProfileResponse{}, errors.Wrap(err, msg) + } + } else { + profileID = args[0] + } + + var existingProfile api.AlertProfileResponse + cli.StartProgress("Retrieving alert profile...") + err = cli.LwApi.V2.Alert.Profiles.Get(profileID, &existingProfile) + cli.StopProgress() + if err != nil { + return api.AlertProfileResponse{}, errors.Wrap(err, msg) + } + + queryYaml, err := yaml.Marshal(existingProfile.Data.Alerts) + if err != nil { + return api.AlertProfileResponse{}, errors.Wrap(err, msg) + } + + prompt := &survey.Editor{ + Message: fmt.Sprintf("Update alert templates for profile %s", profileID), + Default: string(queryYaml), + HideDefault: true, + AppendDefault: true, + FileName: "templates*.yaml", + } + var templatesString string + err = survey.AskOne(prompt, &templatesString) + if err != nil { + return api.AlertProfileResponse{}, errors.Wrap(err, msg) + } + + var templates []api.AlertTemplate + err = yaml.Unmarshal([]byte(templatesString), &templates) + if err != nil { + return api.AlertProfileResponse{}, errors.Wrap(err, msg) + } + + cli.StartProgress(" Updating alert profile...") + response, err := cli.LwApi.V2.Alert.Profiles.Update(profileID, templates) + cli.StopProgress() + return response, err +} + +func promptSelectProfile() (string, error) { + profileResponse, err := cli.LwApi.V2.Alert.Profiles.List() + if err != nil { + return "", err + } + var profileList = filterAlertProfilesByDefault(profileResponse) + + questions := []*survey.Question{ + { + Name: "profile", + Prompt: &survey.Select{ + Message: "Select an alert profile to update:", + Options: profileList, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Profile string `json:"profile"` + }{} + + err = survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return "", err + } + + return answers.Profile, nil +} + +func promptCreateAlertProfile() (api.AlertProfileResponse, error) { + profileResponse, err := cli.LwApi.V2.Alert.Profiles.List() + if err != nil { + return api.AlertProfileResponse{}, err + } + profileList := filterAlertProfiles(profileResponse) + + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Profile Name: "}, + Validate: survey.Required, + }, + { + Name: "extends", + Prompt: &survey.Select{ + Message: "Select an alert profile to extend:", + Options: profileList, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string `json:"name"` + Extends string `json:"extends"` + }{} + + err = survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return api.AlertProfileResponse{}, err + } + + if strings.HasPrefix(answers.Name, "LW_") { + return api.AlertProfileResponse{}, errors.New("profile name prefix 'LW_' is reserved for Lacework-defined profiles") + } + + var templates []api.AlertTemplate + templates = append(templates, promptAddAlertTemplate()) + addTemplates := false + for { + if err := survey.AskOne(&survey.Confirm{ + Message: "Add another alert template?", + }, &addTemplates); err != nil { + return api.AlertProfileResponse{}, err + } + + if addTemplates { + templates = append(templates, promptAddAlertTemplate()) + } else { + break + } + } + alertProfile := api.NewAlertProfile(answers.Name, answers.Extends, templates) + + cli.StartProgress(" Creating alert profile...") + response, err := cli.LwApi.V2.Alert.Profiles.Create(alertProfile) + + cli.StopProgress() + return response, err +} + +func promptAddAlertTemplate() api.AlertTemplate { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Alert Template Name: "}, + Validate: survey.Required, + }, + { + Name: "eventName", + Prompt: &survey.Input{Message: "Alert Template Event Name: "}, + Validate: survey.Required, + }, + { + Name: "description", + Prompt: &survey.Input{Message: "Alert Template Description: "}, + Validate: survey.Required, + }, + { + Name: "subject", + Prompt: &survey.Input{Message: "Alert Template Subject: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string `json:"name"` + EventName string `json:"eventName"` + Description string `json:"description"` + Subject string `json:"subject"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return api.AlertTemplate{} + } + + return api.AlertTemplate{ + Name: answers.Name, + EventName: answers.EventName, + Description: answers.Description, + Subject: answers.Subject, + } +} + +func filterAlertProfilesByDefault(response api.AlertProfilesResponse) []string { + var profiles = make([]string, 0) + for _, p := range response.Data { + if !strings.HasPrefix(p.Guid, "LW_") && len(p.Alerts) >= 1 { + profiles = append(profiles, p.Guid) + } + } + + sort.Slice(profiles, func(i, j int) bool { + return profiles[i] < profiles[j] + }) + + return profiles +} + +func filterAlertProfiles(response api.AlertProfilesResponse) []string { + var profiles = make([]string, 0) + for _, p := range response.Data { + // profiles can only extend from 'LW_' profiles with >= 1 alert template + if strings.HasPrefix(p.Guid, "LW_") && len(p.Alerts) >= 1 { + profiles = append(profiles, p.Guid) + } + } + + sort.Slice(profiles, func(i, j int) bool { + return profiles[i] < profiles[j] + }) + + return profiles +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_rules.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_rules.go new file mode 100644 index 000000000..85bf30338 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_rules.go @@ -0,0 +1,378 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + // alert-rules command is used to manage lacework alert rules + alertRulesCommand = &cobra.Command{ + Use: "alert-rule", + Aliases: []string{"alert-rules", "ar"}, + Short: "Manage alert rules", + Long: `Manage alert rules to route events to the appropriate people or tools. + +An alert rule has three parts: + + 1. Alert channel(s) that should receive the event notification + 2. Event severity and categories to include + 3. Resource group(s) containing the subset of your environment to consider +`, + } + + // list command is used to list all lacework alert rules + alertRulesListCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all alert rules", + Long: "List all alert rules configured in your Lacework account.", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + alertRules, err := cli.LwApi.V2.AlertRules.List() + if err != nil { + return errors.Wrap(err, "unable to get alert rules") + } + if len(alertRules.Data) == 0 { + msg := `There are no alert rules configured in your account. + +Get started by integrating your alert rules to manage alerting using the command: + + lacework alert-rule create + +If you prefer to configure alert rules via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Settings > Alert Rules. +` + cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) + return nil + } + if cli.JSONOutput() { + return cli.OutputJSON(alertRules) + } + + var rows [][]string + for _, rule := range alertRules.Data { + rows = append(rows, []string{rule.Guid, rule.Filter.Name, rule.Filter.Status()}) + } + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "ENABLED"}, rows)) + return nil + }, + } + // show command is used to retrieve a lacework alert rule by resource id + alertRulesShowCommand = &cobra.Command{ + Use: "show ", + Short: "Show an alert rule by ID", + Long: "Show a single alert rule by it's ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var response api.AlertRuleResponse + err := cli.LwApi.V2.AlertRules.Get(args[0], &response) + if err != nil { + return errors.Wrap(err, "unable to get alert rule") + } + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + + alertRule := response.Data + var headers [][]string + headers = append(headers, []string{alertRule.Guid, alertRule.Filter.Name, alertRule.Filter.Status()}) + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "ENABLED"}, headers)) + cli.OutputHuman("\n") + cli.OutputHuman(buildAlertRuleDetailsTable(alertRule)) + + return nil + }, + } + + // delete command is used to remove a lacework alert rule by resource id + alertRulesDeleteCommand = &cobra.Command{ + Use: "delete ", + Short: "Delete a alert rule", + Long: "Delete a single alert rule by it's ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + err := cli.LwApi.V2.AlertRules.Delete(args[0]) + if err != nil { + return errors.Wrap(err, "unable to delete alert rule") + } + cli.OutputHuman(fmt.Sprintf("The alert rule with GUID %s was deleted \n", args[0])) + return nil + }, + } + + // create command is used to create a new lacework alert rule + alertRulesCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new alert rule", + RunE: func(_ *cobra.Command, args []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + + response, err := promptCreateAlertRule() + if err != nil { + return errors.Wrap(err, "unable to create alert rule") + } + + cli.OutputHuman(fmt.Sprintf("The alert rule was created with GUID %s \n", response.Data.Guid)) + return nil + }, + } +) + +func init() { + // add the alert-rule command + rootCmd.AddCommand(alertRulesCommand) + + // add sub-commands to the alert-rule command + alertRulesCommand.AddCommand(alertRulesListCommand) + alertRulesCommand.AddCommand(alertRulesShowCommand) + alertRulesCommand.AddCommand(alertRulesCreateCommand) + alertRulesCommand.AddCommand(alertRulesDeleteCommand) +} + +func buildAlertRuleDetailsTable(rule api.AlertRule) string { + var ( + details [][]string + updatedTime string + ) + severities := api.NewAlertRuleSeveritiesFromIntSlice(rule.Filter.Severity).ToStringSlice() + + if nano, err := strconv.ParseInt(rule.Filter.CreatedOrUpdatedTime, 10, 64); err == nil { + updatedTime = time.Unix(nano/1000, 0).Format(time.RFC3339) + } + details = append(details, []string{"SEVERITIES", strings.Join(severities, ", ")}) + details = append(details, []string{"EVENT CATEGORIES", strings.Join(rule.Filter.AlertSubCategories, ", ")}) + details = append(details, []string{"DESCRIPTION", rule.Filter.Description}) + details = append(details, []string{"UPDATED BY", rule.Filter.CreatedOrUpdatedBy}) + details = append(details, []string{"LAST UPDATED", updatedTime}) + + detailsTable := &strings.Builder{} + detailsTable.WriteString(renderOneLineCustomTable("ALERT RULE DETAILS", + renderCustomTable([]string{}, details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ), + ) + + if len(rule.Channels) > 0 { + channels := [][]string{{strings.Join(rule.Channels, "\n")}} + detailsTable.WriteString(renderCustomTable([]string{"ALERT CHANNELS"}, channels, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + ) + detailsTable.WriteString("\n") + } + + if len(rule.Filter.ResourceGroups) > 0 { + resourceGroups := [][]string{{strings.Join(rule.Filter.ResourceGroups, "\n")}} + detailsTable.WriteString(renderCustomTable([]string{"RESOURCE GROUPS"}, resourceGroups, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + ) + } + + return detailsTable.String() +} + +func promptCreateAlertRule() (api.AlertRuleResponse, error) { + channelList, channelMap := getAlertChannels() + + if len(channelList) < 1 { + return api.AlertRuleResponse{}, errors.New("no Alert Channels found.") + } + + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "description", + Prompt: &survey.Input{Message: "Description: "}, + Validate: survey.Required, + }, + { + Name: "channels", + Prompt: &survey.MultiSelect{ + Message: "Select alert channels:", + Options: channelList, + }, + Validate: survey.Required, + }, + { + Name: "severities", + Prompt: &survey.MultiSelect{ + Message: "Select severities:", + Options: []string{"Critical", "High", "Medium", "Low", "Info"}, + }, + }, + { + Name: "eventCategories", + Prompt: &survey.MultiSelect{ + Message: "Select event categories:", + Options: []string{"Compliance", "App", "Cloud", "File", "Machine", "User", "Platform"}, + }, + }, + } + + answers := struct { + Name string + Description string `survey:"description"` + Channels []string `survey:"channels"` + Severities []string `survey:"severities"` + EventCategories []string `survey:"eventCategories"` + ResourceGroups []string `survey:"resourceGroups"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return api.AlertRuleResponse{}, err + } + + var channels []string + for _, channel := range answers.Channels { + channels = append(channels, channelMap[channel]) + } + + resourceGroups, resourceGroupMap := promptAddResourceGroupsToAlertRule() + groups := make([]string, 0) + for _, group := range resourceGroups { + groups = append(groups, resourceGroupMap[group]) + } + + alertCategories := make([]string, 0) + + alertRule := api.NewAlertRule( + answers.Name, + api.AlertRuleConfig{ + Description: answers.Description, + Channels: channels, + Severities: api.NewAlertRuleSeverities(answers.Severities), + AlertSubCategories: answers.EventCategories, + AlertCategories: alertCategories, + ResourceGroups: groups, + }) + + cli.StartProgress(" Creating alert rule...") + response, err := cli.LwApi.V2.AlertRules.Create(alertRule) + + cli.StopProgress() + return response, err +} + +func getAlertChannels() ([]string, map[string]string) { + response, err := cli.LwApi.V2.AlertChannels.List() + + if err != nil { + return nil, nil + } + var items = make(map[string]string) + var channels = make([]string, 0) + for _, i := range response.Data { + displayName := fmt.Sprintf("%s - %s", i.ID(), i.Name) + channels = append(channels, displayName) + items[displayName] = i.ID() + } + + return channels, items +} + +func getResourceGroups() ([]string, map[string]string) { + cli.StartProgress("") + defer cli.StopProgress() + response, err := cli.LwApi.V2.ResourceGroups.List() + + if err != nil { + return nil, nil + } + var items = make(map[string]string) + var groups = make([]string, 0) + + for _, i := range response.Data { + displayName := fmt.Sprintf("%s - %s", i.ID(), i.Name) + groups = append(groups, displayName) + items[displayName] = i.ID() + } + + return groups, items +} + +func promptAddResourceGroupsToAlertRule() ([]string, map[string]string) { + addResourceGroups := false + err := survey.AskOne(&survey.Confirm{ + Message: "Add Resource Groups to Alert Rule?", + }, &addResourceGroups) + + if err != nil { + return nil, nil + } + + if addResourceGroups { + var groups []string + groupList, groupMap := getResourceGroups() + + err = survey.AskOne(&survey.MultiSelect{ + Message: "Select Resource Groups:", + Options: groupList, + }, &groups) + + if err != nil { + return nil, nil + } + return groups, groupMap + } + return nil, nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show.go new file mode 100644 index 000000000..8d780fb01 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show.go @@ -0,0 +1,109 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strconv" + + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // alertShowCmd represents the alert list command + alertShowCmd = &cobra.Command{ + Use: "show ", + Short: "Show details about a specific alert", + Long: `Show details about a specific alert. + +There are different types of alert details that can be shown to assist +with alert investigation. These types are referred to as alert detail scopes. + +The following alert detail scopes are available: + + * Details (default) + * Investigation + * Events + * RelatedAlerts + * Integrations + * Timeline + +View an alert's timeline details: + + lacework alert show --scope Timeline +`, + Args: cobra.ExactArgs(1), + RunE: showAlert, + } +) + +func init() { + // show command + alertCmd.AddCommand(alertShowCmd) + + // scope flag + alertShowCmd.Flags().StringVar( + &alertCmdState.Scope, + "scope", api.AlertDetailsScope.String(), + "type of alert details to show", + ) +} + +func showAlert(_ *cobra.Command, args []string) error { + cli.Log.Debugw("showing alert", "alert", args[0]) + + id, err := strconv.Atoi(args[0]) + if err != nil { + return errors.New("alert ID must be a number") + } + + switch alertCmdState.Scope { + case api.AlertDetailsScope.String(): + err = showAlertDetails(id) + case api.AlertInvestigationScope.String(): + err = showAlertInvestigation(id) + case api.AlertEventsScope.String(): + err = showAlertEvents(id) + case api.AlertRelatedAlertsScope.String(): + err = showRelatedAlerts(id) + case api.AlertIntegrationsScope.String(): + err = showAlertIntegrations(id) + case api.AlertTimelineScope.String(): + err = showAlertTimeline(id) + default: + err = errors.New(fmt.Sprintf("scope (%s) is not recognized", alertCmdState.Scope)) + } + if err != nil { + return err + } + + // breadcrumb + if !cli.JSONOutput() { + url := alertLinkBuilder(id) + cli.OutputHuman( + fmt.Sprintf( + "\nFor further investigation of this alert navigate to %s\n", + url, + ), + ) + } + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_details.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_details.go new file mode 100644 index 000000000..2e8b8f00f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_details.go @@ -0,0 +1,107 @@ +package cmd + +import ( + "fmt" + + "github.com/lacework/go-sdk/api" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" +) + +func alertDetailsTable(alert api.Alert) ( + category, description, subject, entity [][]string, +) { + category = append(category, []string{ + alert.DerivedFields.Source, + alert.DerivedFields.Category, + alert.DerivedFields.SubCategory, + alert.PolicyID, + }) + + description = append(description, []string{ + alert.Info.Description, + }) + + subject = append(subject, []string{ + alert.Info.Subject, + }) + + entity = append(entity, []string{ + fmt.Sprintf( + "Entity map coming soon. Use 'lacework alert show %d --json' for full details.", + alert.ID, + ), + }) + + return +} + +func renderAlertDetailsTable(alert api.Alert) { + cat, desc, subj, entity := alertDetailsTable(alert) + cli.OutputHuman( + renderCustomTable( + []string{"Source", "Category", "Sub Category", "Policy ID"}, + cat, + tableFunc(func(t *tablewriter.Table) { + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) + cli.OutputHuman("\n") + cli.OutputHuman( + renderCustomTable( + []string{"Description"}, + desc, + tableFunc(func(t *tablewriter.Table) { + t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) + cli.OutputHuman("\n") + cli.OutputHuman( + renderCustomTable( + []string{"Subject"}, + subj, + tableFunc(func(t *tablewriter.Table) { + t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) + cli.OutputHuman("\n") + cli.OutputHuman( + renderCustomTable( + []string{"Entities"}, + entity, + tableFunc(func(t *tablewriter.Table) { + t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) +} + +func showAlertDetails(id int) error { + cli.StartProgress(" Fetching alert details...") + details, err := cli.LwApi.V2.Alerts.GetDetails(id) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to show alert") + } + + if cli.JSONOutput() { + return cli.OutputJSON(details.Data) + } + + alerts := api.Alerts{details.Data.Alert} + renderAlertListTable(alerts) + cli.OutputHuman("\n") + renderAlertDetailsTable(details.Data.Alert) + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_events.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_events.go new file mode 100644 index 000000000..cf2731db7 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_events.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "github.com/pkg/errors" +) + +func showAlertEvents(id int) error { + cli.StartProgress(" Fetching alert events...") + eventsResponse, err := cli.LwApi.V2.Alerts.GetEvents(id) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to show alert") + } + + return cli.OutputJSON(eventsResponse.Data) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_integrations.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_integrations.go new file mode 100644 index 000000000..a216f2547 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_integrations.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "github.com/lacework/go-sdk/api" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" +) + +func alertIntegrationsTable(integrations []api.AlertIntegration) (out [][]string) { + for _, i := range integrations { + out = append(out, []string{ + i.ID, + i.IntgGUID, + i.Type, + i.Channel.Status(), + i.Channel.StateString(), + }) + } + return +} + +func renderAlertIntegrationsTable(integrations []api.AlertIntegration) { + cli.OutputHuman( + renderCustomTable( + []string{"Alert Integration ID", "Alert Channel GUID", "Type", "Status", "State"}, + alertIntegrationsTable(integrations), + tableFunc(func(t *tablewriter.Table) { + t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) +} + +func showAlertIntegrations(id int) error { + cli.StartProgress(" Fetching alert integrations...") + integrationsResponse, err := cli.LwApi.V2.Alerts.GetIntegrations(id) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to show alert") + } + + if cli.JSONOutput() { + return cli.OutputJSON(integrationsResponse.Data) + } + + if len(integrationsResponse.Data) == 0 { + cli.OutputHuman("There are no integration details available for the specified alert.\n") + return nil + } + + renderAlertIntegrationsTable(integrationsResponse.Data) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_investigation.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_investigation.go new file mode 100644 index 000000000..7fe5d9777 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_investigation.go @@ -0,0 +1,52 @@ +package cmd + +import ( + "github.com/lacework/go-sdk/api" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" +) + +func alertInvestigationTable(investigations []api.AlertInvestigation) (out [][]string) { + for _, i := range investigations { + out = append(out, []string{ + i.Question, + i.Answer, + }) + } + return +} + +func renderAlertInvestigationTable(investigations []api.AlertInvestigation) { + cli.OutputHuman( + renderCustomTable( + []string{"Question", "Answer"}, + alertInvestigationTable(investigations), + tableFunc(func(t *tablewriter.Table) { + t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) +} + +func showAlertInvestigation(id int) error { + cli.StartProgress(" Fetching alert investigation...") + investigationResponse, err := cli.LwApi.V2.Alerts.GetInvestigation(id) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to show alert") + } + + if cli.JSONOutput() { + return cli.OutputJSON(investigationResponse.Data) + } + + if len(investigationResponse.Data) == 0 { + cli.OutputHuman("There are no investigation details available for the specified alert.\n") + return nil + } + + renderAlertInvestigationTable(investigationResponse.Data) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_related.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_related.go new file mode 100644 index 000000000..5eff3145c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_related.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "strconv" + + "github.com/lacework/go-sdk/api" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" +) + +func relatedAlertsTable(relatedAlerts api.RelatedAlerts) (out [][]string) { + for _, relatedAlert := range relatedAlerts.SortRankDescending() { + out = append(out, []string{ + relatedAlert.ID, + relatedAlert.Name, + relatedAlert.Severity, + relatedAlert.StartTime, + relatedAlert.EndTime, + strconv.Itoa(relatedAlert.Rank), + }) + } + + return +} + +func renderRelatedAlertsTable(relatedAlerts api.RelatedAlerts) { + cli.OutputHuman( + renderCustomTable( + []string{"Alert ID", "Name", "Severity", "Start Time", "End Time", "Rank"}, + relatedAlertsTable(relatedAlerts), + tableFunc(func(t *tablewriter.Table) { + t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) +} + +func showRelatedAlerts(id int) error { + cli.StartProgress(" Fetching related alerts...") + relatedResponse, err := cli.LwApi.V2.Alerts.GetRelatedAlerts(id) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to show alert") + } + + if cli.JSONOutput() { + return cli.OutputJSON(relatedResponse.Data) + } + + if len(relatedResponse.Data) == 0 { + cli.OutputHuman("There are no related alerts associated with the specified alert.\n") + return nil + } + + renderRelatedAlertsTable(relatedResponse.Data) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_timeline.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_timeline.go new file mode 100644 index 000000000..71b16ce09 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_timeline.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "strconv" + + "github.com/lacework/go-sdk/api" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" +) + +func alertTimelineTable(timelines []api.AlertTimeline) (out [][]string) { + for _, t := range timelines { + out = append(out, []string{ + strconv.Itoa(t.ID), + t.EntryType, + t.Message.Value, + t.EntryAuthorType, + t.User.Name, + }) + } + return +} + +func renderAlertTimelineTable(timelines []api.AlertTimeline) { + cli.OutputHuman( + renderCustomTable( + []string{"Timeline ID", "Entry Type", "Message", "Author Type", "Author"}, + alertTimelineTable(timelines), + tableFunc(func(t *tablewriter.Table) { + t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) +} + +func showAlertTimeline(id int) error { + cli.StartProgress(" Fetching alert timeline...") + timelineResponse, err := cli.LwApi.V2.Alerts.GetTimeline(id) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to show alert") + } + + if cli.JSONOutput() { + return cli.OutputJSON(timelineResponse.Data) + } + + if len(timelineResponse.Data) == 0 { + cli.OutputHuman("There are no timeline entries for the specified alert.\n") + return nil + } + + renderAlertTimelineTable(timelineResponse.Data) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/api.go b/vendor/github.com/lacework/go-sdk/cli/cmd/api.go new file mode 100644 index 000000000..202f1bff4 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/api.go @@ -0,0 +1,133 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/internal/array" +) + +var ( + // list of valid API methods + validApiMethods = []string{"get", "post", "delete", "patch"} + + // data to send for POST/PATCH request + apiData string + + // apiCmd represents the api command + apiCmd = &cobra.Command{ + Use: "api ", + Short: "Helper to call Lacework's API", + Long: `Use this command as a helper to call any available Lacework API v2 endpoint. + +### API v2 + +To list all available Lacework schema types: + + lacework api get /v2/schemas + +To receive a json response of all machines within the given time window: + + lacework api post /api/v2/Entities/Machines/search -d "{}" + +To receive a json response of all agents within the given time window: + + lacework api post /api/v2/AgentInfo/search -d "{}" + +For a complete list of available API v2 endpoints visit: + + https://.lacework.net/api/v2/docs +`, + Args: argsApiValidator, + RunE: runApiCommand, + } +) + +func init() { + // add the api command + rootCmd.AddCommand(apiCmd) + + apiCmd.Flags().StringVarP(&apiData, + "data", "d", "", + "data to send only for post and patch requests", + ) +} + +func runApiCommand(_ *cobra.Command, args []string) error { + switch args[0] { + case "patch": + if apiData == "" { + return fmt.Errorf("missing '--data' parameter patch requests") + } + case "get": + if apiData != "" { + return fmt.Errorf("use '--data' only for post, delete and patch requests") + } + } + + response := new(interface{}) + err := cli.LwApi.RequestDecoder( + strings.ToUpper(args[0]), + cleanupEndpoint(args[1]), + strings.NewReader(apiData), + response, + ) + if err != nil { + return errors.Wrap(err, "unable to send the request") + } + + if err := cli.OutputJSON(*response); err != nil { + return errors.Wrap(err, "unable to format json response") + } + return nil +} + +func argsApiValidator(_ *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("requires 2 argument. (method and path)") + } + if !array.ContainsStr(validApiMethods, args[0]) { + return fmt.Errorf( + "invalid method specified: '%s' (valid methods are %s)", + args[0], validApiMethods, + ) + } + return nil +} + +// cleanupEndpoint will make sure that any provided endpoint is well formatted +// and doesn't contain known fields like /api/v1/foo +func cleanupEndpoint(endpoint string) string { + splitEndpoint := strings.Split(endpoint, "/") + + if len(splitEndpoint) > 0 && splitEndpoint[0] == "api" { + return strings.Join(splitEndpoint[1:], "/") + } + + if len(splitEndpoint) > 1 && splitEndpoint[1] == "api" { + return strings.Join(splitEndpoint[2:], "/") + } + + return strings.TrimPrefix(endpoint, "/") +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/aws.go new file mode 100644 index 000000000..1a528e66d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/aws.go @@ -0,0 +1,224 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "context" + "sync" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/gammazero/workerpool" + "github.com/lacework/go-sdk/lwrunner" +) + +func awsDescribeInstances(filterSSH bool) ([]*lwrunner.AWSRunner, error) { + cli.StartProgress("Finding target EC2 instances...") + defer cli.StopProgress() + + regions, err := awsDescribeRegions() + if err != nil { + return nil, err + } + + allRunners := []*lwrunner.AWSRunner{} + for _, region := range regions { + regionRunners, err := awsRegionDescribeInstances(*region.RegionName, filterSSH) + if err != nil { + return nil, err + } + allRunners = append(allRunners, regionRunners...) + } + + return allRunners, nil +} + +// awsDescribeRegions queries the AWS API to list all the regions that +// are enabled for the user's AWS account. Use the "include_regions" +// command-line flag to only get regions in this list. +func awsDescribeRegions() ([]types.Region, error) { + // Describe all regions that are enabled for the account + var filters []types.Filter + if len(agentCmdState.InstallIncludeRegions) > 0 { + filters = []types.Filter{ + { + Name: aws.String("region-name"), + Values: agentCmdState.InstallIncludeRegions, + }, + } + } + input := &ec2.DescribeRegionsInput{ + Filters: filters, + } + + cfg, err := config.LoadDefaultConfig(context.Background(), + config.WithSharedConfigProfile(agentCmdState.InstallAWSProfile), + ) + if err != nil { + return nil, err + } + svc := ec2.New(ec2.Options{ + Credentials: cfg.Credentials, + Region: cfg.Region, + }) + + output, err := svc.DescribeRegions(context.Background(), input) + if err != nil { + return nil, err + } + return output.Regions, nil +} + +func awsRegionDescribeInstances(region string, filterSSH bool) ([]*lwrunner.AWSRunner, error) { + var ( + tagKey = agentCmdState.InstallTagKey + tag = agentCmdState.InstallTag + ) + cfg, err := config.LoadDefaultConfig(context.Background(), + config.WithSharedConfigProfile(agentCmdState.InstallAWSProfile), + ) + if err != nil { + return nil, err + } + svc := ec2.New(ec2.Options{ + Credentials: cfg.Credentials, + Region: region, + }) + + var filters []types.Filter + + // Filter for instances that are running + filters = append(filters, types.Filter{ + Name: aws.String("instance-state-name"), + Values: []string{ + "running", + }, + }) + + // Filter for instances where a tag key exists + if tagKey != "" { + cli.Log.Debugw("looking for tagKey", "tagKey", tagKey, "region", region) + filters = append(filters, types.Filter{ + Name: aws.String("tag-key"), + Values: []string{ + tagKey, + }, + }) + } + + // Filter for instances where certain tags exist + if len(tag) > 0 { + cli.Log.Debugw("looking for tags", "tag length", len(tag), "tags", tag, "region", region) + filters = append(filters, types.Filter{ + Name: aws.String("tag:" + tag[0]), + Values: tag[1:], + }) + } + + input := &ec2.DescribeInstancesInput{ + Filters: filters, + } + result, err := svc.DescribeInstances(context.Background(), input) + if err != nil { + cli.Log.Debugw("error with describing instances", "input", input) + return nil, err + } + + runners := []*lwrunner.AWSRunner{} + producerWg := new(sync.WaitGroup) + wp := workerpool.New(agentCmdState.InstallMaxParallelism) + runnerCh := make(chan *lwrunner.AWSRunner) + + // We have multiple producers of runners and a single consumer. + // This goroutine acts as the consumer and reads from a channel into + // a slice. Pass a pointer to this slice and wait for this goroutine + // to finish before returning the memory pointed to. + consumerWg := new(sync.WaitGroup) + consumerWg.Add(1) + go func(runners *[]*lwrunner.AWSRunner) { + for runner := range runnerCh { + *runners = append(*runners, runner) + } + consumerWg.Done() + }(&runners) + + cli.Log.Debugw("iterating over runners", "region", region) + for _, reservation := range result.Reservations { + for _, instance := range reservation.Instances { + if instance.PublicIpAddress != nil && instance.State.Name == "running" { + cli.Log.Debugw("found runner", + "public ip address", *instance.PublicIpAddress, + "instance state name", instance.State.Name, + ) + + if err != nil { + cli.Log.Debugw("error identifying runner", "error", err, "instance_id", *instance.InstanceId) + continue + } + + producerWg.Add(1) + + // In order to use `wp.Submit()`, the input func() must not take any arguments. + // Copy the runner info to dedicated variable in the goroutine + instanceCopyWg := new(sync.WaitGroup) + instanceCopyWg.Add(1) + + wp.Submit(func() { + defer producerWg.Done() + + threadInstance := instance + instanceCopyWg.Done() + cli.Log.Debugw("found runner", + "public ip address", *threadInstance.PublicIpAddress, + "instance state name", threadInstance.State.Name, + ) + + runner, err := lwrunner.NewAWSRunner( + *threadInstance.ImageId, + agentCmdState.InstallSshUser, + *threadInstance.PublicIpAddress, + region, + *threadInstance.Placement.AvailabilityZone, + *threadInstance.InstanceId, + filterSSH, + verifyHostCallback, + cfg, + ) + if err != nil { + cli.Log.Debugw("error identifying runner", "error", err, "instance_id", *threadInstance.InstanceId) + } else { + runnerCh <- runner + } + }) + instanceCopyWg.Wait() + } + } + } + + // Wait for the producers to finish, then close the producer thread pool, + // then close the channel they're writing to, then wait for the consumer to finish + producerWg.Wait() + wp.StopWait() + close(runnerCh) + consumerWg.Wait() + + return runners, nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/awsiam.go b/vendor/github.com/lacework/go-sdk/cli/cmd/awsiam.go new file mode 100644 index 000000000..de7b263fa --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/awsiam.go @@ -0,0 +1,320 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/google/uuid" + "github.com/lacework/go-sdk/lwrunner" +) + +// setupSSMAccess sets up an IAM role for SSM and attaches it to +// the machine's instance profile. Takes role name as argument; +// pass the empty string to create a new role. +// Then creates SSM document. +func setupSSMAccess(cfg aws.Config, roleName string, token string) (types.Role, types.InstanceProfile, error) { + c := iam.New(iam.Options{ + Credentials: cfg.Credentials, + Region: cfg.Region, + }) + + cli.Log.Debugw("setting up role", "passed roleName", roleName) + role, err := setupSSMRole(c, roleName) + if err != nil { + return role, types.InstanceProfile{}, err + } + + err = attachSSMPoliciesToRole(c, role) + if err != nil { + return role, types.InstanceProfile{}, err + } + + // Create instance profile and add the role to it + instanceProfile, err := setupInstanceProfile(c, role) + if err != nil { + return role, instanceProfile, err + } + + return role, instanceProfile, nil +} + +// teardownSSMAccess destroys all the infra created during the execution of this program. +// Specifically, this function: +// - Removes the role from the instance profile +// - Deletes the instance profile +// - Detaches all managed policies from the role (assumes no inline policies attached) +// - Deletes the role +func teardownSSMAccess( + cfg aws.Config, role types.Role, instanceProfile types.InstanceProfile, byoRoleName string, +) error { + c := iam.New(iam.Options{ + Credentials: cfg.Credentials, + Region: cfg.Region, + }) + + if taggedLaceworkResource(instanceProfile.Tags) { + cli.Log.Debugw("removing role from instance profile", "role", role, "instance profile", instanceProfile) + _, err := c.RemoveRoleFromInstanceProfile( + context.Background(), + &iam.RemoveRoleFromInstanceProfileInput{ + InstanceProfileName: instanceProfile.InstanceProfileName, + RoleName: role.RoleName, + }, + ) + if err != nil { + return err + } + + cli.Log.Debugw("deleting instance profile", "instance profile", instanceProfile) + _, err = c.DeleteInstanceProfile( + context.Background(), + &iam.DeleteInstanceProfileInput{ + InstanceProfileName: instanceProfile.InstanceProfileName, + }, + ) + if err != nil { + return err + } + } + + if byoRoleName != "" || !taggedLaceworkResource(role.Tags) { + cli.Log.Debugw("Lacework didn't create this role, will not delete it", + "byoRoleName", byoRoleName, + "role", role, + ) + return nil + } + + cli.Log.Debug("listing managed policies attached to this role (assuming no inline policies") + listOutput, err := c.ListAttachedRolePolicies( + context.Background(), + &iam.ListAttachedRolePoliciesInput{ + RoleName: role.RoleName, + }, + ) + if err != nil { + return err + } + + // Detach managed policies + for _, attachedPolicy := range listOutput.AttachedPolicies { + cli.Log.Debugw("detaching policy", "policy", attachedPolicy, "role", role) + _, err := c.DetachRolePolicy( + context.Background(), + &iam.DetachRolePolicyInput{ + PolicyArn: attachedPolicy.PolicyArn, + RoleName: role.RoleName, + }, + ) + if err != nil { + return err + } + } + + cli.Log.Debugw("deleting role", "role", role) + _, err = c.DeleteRole( + context.Background(), + &iam.DeleteRoleInput{ + RoleName: role.RoleName, + }, + ) + if err != nil { + return err + } + + return nil +} + +// taggedLaceworkResource is a helper function that takes the tag set of +// a suspected Lacework resource, iterates through the tags, and determines +// if the resource belongs to Lacework. Returns `true, nil` if the resource +// belongs to Lacework and `false, nil` if the resource does not belong to +// Lacework. +func taggedLaceworkResource(tags []types.Tag) bool { + for _, tag := range tags { + if *tag.Key == laceworkAutomationTagKey { + return true + } + } + + return false +} + +const laceworkAutomationTagKey = "LaceworkAutomation" + +// setupSSMRole sets up an IAM role for SSM to assume. +// If `roleName` is not the empty string, then use that role +// instead of creating a new one. +func setupSSMRole(c *iam.Client, roleName string) (types.Role, error) { + if roleName != "" { + return getRoleFromName(c, roleName) + } else { + cli.Log.Debug("user did not provide a role, creating one now") + return createSSMRole(c) + } +} + +type iamGetRoleFromNameAPI interface { + GetRole(ctx context.Context, params *iam.GetRoleInput, optFns ...func(*iam.Options)) (*iam.GetRoleOutput, error) +} + +func getRoleFromName(c iamGetRoleFromNameAPI, roleName string) (types.Role, error) { + cli.Log.Debug("fetching info about role", roleName) + output, err := c.GetRole( + context.Background(), + &iam.GetRoleInput{ + RoleName: aws.String(roleName), + }, + ) + if err != nil { + return types.Role{}, err + } + + return *output.Role, nil +} + +type iamCreateSSMRoleAPI interface { + CreateRole( + ctx context.Context, params *iam.CreateRoleInput, optFns ...func(*iam.Options), + ) (*iam.CreateRoleOutput, error) +} + +// createSSMRole makes a call to the AWS API to create an IAM role. +// Returns information about the newly created role and any errors. +func createSSMRole(c iamCreateSSMRoleAPI) (types.Role, error) { + const roleNameBase string = "Lacework-Agent-SSM-Install-Role-" + roleName := roleNameBase + uuid.New().String()[:5] + + const trustPolicyDocument = `{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { "Service": "ec2.amazonaws.com" }, + "Action": "sts:AssumeRole" + } + ] +}` + + output, err := c.CreateRole( + context.Background(), + &iam.CreateRoleInput{ + AssumeRolePolicyDocument: aws.String(trustPolicyDocument), + RoleName: aws.String(roleName), + Description: aws.String( + `Ephemeral role to install Lacework agents using SSM; created by the Lacework CLI. +Safe to delete if found`, + ), + Tags: []types.Tag{ + { + Key: aws.String("Name"), + Value: aws.String(roleName), + }, + { + Key: aws.String(laceworkAutomationTagKey), + Value: aws.String("agent-ssm-install"), + }, + }, + }, + ) + if err != nil { + return types.Role{}, err + } + cli.Log.Debugw("freshly created role", "role", *output.Role) + + return *output.Role, nil +} + +// attachSSMPoliciesToRole takes a role, calls the IAM API to attach +// policies required for SSM to the role, and returns the role along +// with any errors. +func attachSSMPoliciesToRole(c *iam.Client, role types.Role) error { + cli.Log.Debug("attaching policy to role") + _, err := c.AttachRolePolicy( + context.Background(), + &iam.AttachRolePolicyInput{ + PolicyArn: aws.String(lwrunner.SSMInstancePolicy), + RoleName: role.RoleName, + }, + ) + + return err +} + +func setupInstanceProfile(c *iam.Client, role types.Role) (types.InstanceProfile, error) { + instanceProfile, err := createInstanceProfile(c) + if err != nil { + return instanceProfile, err + } + + err = addRoleToInstanceProfile(c, role, instanceProfile) + if err != nil { + return types.InstanceProfile{}, err + } + + return instanceProfile, nil +} + +func createInstanceProfile(c *iam.Client) (types.InstanceProfile, error) { + const instanceProfileNameBase string = "Lacework-Agent-SSM-Install-Instance-Profile-" + instanceProfileName := instanceProfileNameBase + uuid.New().String()[:5] + + createOutput, err := c.CreateInstanceProfile( + context.Background(), + &iam.CreateInstanceProfileInput{ + InstanceProfileName: aws.String(instanceProfileName), + Tags: []types.Tag{ + { + Key: aws.String("Name"), + Value: aws.String(instanceProfileName), + }, + { + Key: aws.String(laceworkAutomationTagKey), + Value: aws.String("agent-ssm-install"), + }, + }, + }, + ) + if err != nil { + return types.InstanceProfile{}, err + } + + cli.Log.Debug("sleeping for 15sec to wait for instance profile eventual consistency") + time.Sleep(15 * time.Second) + + return *createOutput.InstanceProfile, err +} + +func addRoleToInstanceProfile(c *iam.Client, role types.Role, instanceProfile types.InstanceProfile) error { + cli.Log.Debugw("adding role to instance profile", "role", role, "instance profile", instanceProfile) + _, err := c.AddRoleToInstanceProfile( + context.Background(), + &iam.AddRoleToInstanceProfileInput{ + InstanceProfileName: instanceProfile.InstanceProfileName, + RoleName: role.RoleName, + }, + ) + + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cache.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cache.go new file mode 100644 index 000000000..4b81f8a7a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/cache.go @@ -0,0 +1,317 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "path" + "strings" + "time" + + "github.com/lacework/go-sdk/internal/cache" + "github.com/lacework/go-sdk/internal/format" + "github.com/mitchellh/hashstructure/v2" + "github.com/peterbourgon/diskv/v3" + "github.com/pkg/errors" +) + +const MaxCacheSize = 1024 * 1024 * 1024 + +// InitCache initializes the Lacework CLI cache to store data on disk, +// this functions accepts an specific path to store the cache or, by +// default, it will use the default location. +// +// Simple CRUD example: +// +// ```go +// cli.Cache.WriteString("data", "something useful") // Create +// myData := cli.Cache.Read("data") // Read +// cli.Cache.Write("data", []byte("cool update")) // Update +// cli.Cache.Erase("data") // Delete +// ``` +func (c *cliState) InitCache(d ...string) { + if len(d) == 0 { + dir, err := cache.CacheDir() + if err == nil { + d = []string{dir} + } + } + + cache := strings.Join(d, "/") + c.Cache = diskv.New(diskv.Options{ + BasePath: path.Join(cache, "cache"), + AdvancedTransform: CacheTransform, + InverseTransform: InverseCacheTransform, + CacheSizeMax: MaxCacheSize, + }) +} + +func CacheTransform(key string) *diskv.PathKey { + // Global cache + // + // The Lacework CLI will have times where we need to cache global things + // such as the daily version checks. For those cases, we will use the + // global cache that can be accessed like: + // + // cli.Cache.Read("global/version") + // + if strings.HasPrefix(key, "global/") { + keys := strings.Split(key, "/") + pathToKey := []string{} + if len(keys) > 2 { + pathToKey = keys[1 : len(keys)-1] + } + return &diskv.PathKey{ + Path: pathToKey, + FileName: keys[len(keys)-1], + } + } + + // Scoped cache + // + // If the Lacework CLI is not using the global cache, then we will land + // in the scoped cache, this cache is individual per profile, that is, + // a convination of /account/subaccount/key_id/{file}. This is the default + // cache location when doing CRUD actions like: + // + // cli.Cache.WriteString("static_data", "{ ... some static data ... }") + // + subaccount := cli.Subaccount + if subaccount == "" { + subaccount = "standalone" + } + pathToKey := []string{cli.Account, subaccount, cli.KeyID} + + // if the key contains "/" we need to split the path + if strings.Contains(key, "/") { + keys := strings.Split(key, "/") + pathToKey = append(pathToKey, keys[0:len(keys)-1]...) + key = keys[len(keys)-1] + } + + return &diskv.PathKey{ + Path: pathToKey, + FileName: key, + } +} + +func InverseCacheTransform(pathKey *diskv.PathKey) string { + if strings.HasPrefix(pathKey.FileName, "global/") { + keys := strings.Split(pathKey.FileName, "/") + return strings.Join(pathKey.Path, "/") + keys[1] + } + return strings.Join(pathKey.Path, "/") + pathKey.FileName +} + +func (c *cliState) EraseCachedToken() error { + if c.noCache { + return nil + } + + c.Log.Debugw("token expired, removing from cache", + "feature", "cache", + ) + + return c.Cache.Erase("token") +} + +func (c *cliState) ReadCachedToken() { + if c.noCache { + return + } + + if tokenJSON, err := c.Cache.Read("token"); err == nil { + if err := json.Unmarshal(tokenJSON, &c.tokenCache); err == nil { + c.Log.Debugw("token loaded from cache", + "feature", "cache", + "token", format.Secret(4, c.tokenCache.Token), + ) + c.Token = c.tokenCache.Token + } + } +} + +// return true if the cached token is expired or will expire within +// the eminent duration (i.e. 10 seconds) +func (c *cliState) cachedTokenExpiryEminent() bool { + eminentDuration := -10 * time.Second + // only consider the tokenCache expiry time if we actually have a cached token + return c.tokenCache.Token != "" && c.tokenCache.ExpiresAt.Before(time.Now().Add(eminentDuration)) +} + +func (c *cliState) WriteCachedToken() error { + if c.noCache { + return nil + } + // if we don't have a token or the cached token expiry is eminent + // then attempt to refresh it... + if c.Token == "" || c.cachedTokenExpiryEminent() { + response, err := c.LwApi.GenerateToken() + if err != nil { + return errors.New("Failed to generate token. Validate your credentials are properly configured and not expired.") + } + + c.Log.Debugw("saving token", + "feature", "cache", + "token", format.Secret(4, response.Token), + "expires_at", response.ExpiresAt, + ) + err = c.Cache.Write("token", structToString(response)) + if err != nil { + c.Log.Warnw("unable to write token in cache", + "feature", "cache", + "error", err.Error(), + ) + } + c.Token = response.Token + c.tokenCache.Token = response.Token + c.tokenCache.ExpiresAt = response.ExpiresAt + } + return nil +} + +// structToString takes any arbitrary type and converts it into a string +func structToString(v interface{}) []byte { + out, err := json.Marshal(v) + if err != nil { + return []byte{} + } + return out +} + +// cliAsset is a simple private struct that acks as an envelope for storing +// assets that has a expiration time into the local cache. The Data field +// is an interface on purpose to allow developers store any kind of asset, +// from primitives such as strings, ints, bools, to JSON objects +type cliAsset struct { + Data interface{} `json:"data"` + ExpiresAt time.Time `json:"expires_at"` +} + +// writeAssetToCache stores an asset with an expiration time and returns errors +// +// Simple Example: Having a struct named vulnReport +// +// ```go +// cli.WriteAssetToCache("my-report", time.Now().Add(time.Hour * 1), vulnReport{Foo: "bar"}) +// ``` +// writeAssetToCache stores an asset with an expiration time +// +// Simple Example: Having a struct named vulnReport +// +// ```go +// cli.WriteAssetToCache("my-report", time.Now().Add(time.Hour * 1), vulnReport{Foo: "bar"}) +// ``` +func (c *cliState) writeAssetToCache(key string, expiresAt time.Time, data interface{}) error { + if c.noCache { + return nil + } + + if expiresAt.Before(time.Now()) { + return nil // avoid writing assets that are already expired + } + + c.Log.Debugw("saving asset", + "feature", "cache", + "path", key, + "data", data, + "expires_at", expiresAt, + ) + return c.Cache.Write(key, structToString(cliAsset{data, expiresAt})) +} + +// WriteAssetToCache wraps WriteAssetToCacheErroring and squashes errors +func (c *cliState) WriteAssetToCache(key string, expiresAt time.Time, data interface{}) { + err := c.writeAssetToCache(key, expiresAt, data) + if err != nil { + c.Log.Warnw("unable to write asset in cache", + "feature", "cache", + "error", err.Error(), + ) + } +} + +// ReadCachedAsset tries to reads an asset with an expiration time, if the +// asset has expired, it returns "true", otherwise it returns "false" +// +// Simple Example: Having a struct named vulnReport +// +// ```go +// var report vulnReport +// +// if expired := cli.ReadCachedAsset("my-report", &report); !expired { +// fmt.Printf("My report: %v\n", report) +// } +// +// ``` +func (c *cliState) ReadCachedAsset(key string, data interface{}) bool { + if c.noCache { + return true // if the cache is disabled, all assets are treated like expired + } + + if dataJSON, err := c.Cache.Read(key); err == nil { + var asset cliAsset + if err := json.Unmarshal(dataJSON, &asset); err == nil { + c.Log.Debugw("asset loaded from cache", + "feature", "cache", + "path", key, + "expires_at", asset.ExpiresAt, + ) + + // check if the cache expired + if time.Now().After(asset.ExpiresAt) { + c.Log.Debugw("asset expired, removing from cache", + "feature", "cache", + "path", key, + "time_now", time.Now(), + "expires_at", asset.ExpiresAt, + ) + if err := c.Cache.Erase(key); err != nil { + c.Log.Warnw("unable to erase asset from cache", + "feature", "cache", "path", key, + ) + } + return true + } + + if err := json.Unmarshal(structToString(asset.Data), &data); err == nil { + // we successfully retrieved the asset, which has not expired, + // and we cast it to the proper type + return false + } + c.Log.Warnw("unable to cast asset data", + "feature", "cache", + "error", err.Error(), + ) + } + } + + return true +} + +// hash creates a unique hash value for arbitrary values in Go. +// +// Useful to generate unique cache keys of filters applied to commands. +func hash(v interface{}) uint64 { + h, err := hashstructure.Hash(v, hashstructure.FormatV2, nil) + if err != nil { + cli.Log.Warnw("unable to generate Hash()", "error", err.Error()) + } + return h +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cdk.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cdk.go new file mode 100644 index 000000000..684d1299d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/cdk.go @@ -0,0 +1,177 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "context" + "fmt" + "net" + "os" + + cdk "github.com/lacework/go-sdk/cli/cdk/go/proto/v1" + "github.com/pkg/errors" + "google.golang.org/grpc" +) + +// default gRPC target if not specified via LW_CDK_TARGET +const defaultGrpcPort int = 1123 + +// envs are all the environment variables passed to CDK components +func (c *cliState) envs() []string { + return []string{ + fmt.Sprintf("LW_PROFILE=%s", c.Profile), + fmt.Sprintf("LW_ACCOUNT=%s", c.Account), + fmt.Sprintf("LW_SUBACCOUNT=%s", c.Subaccount), + fmt.Sprintf("LW_API_KEY=%s", c.KeyID), + fmt.Sprintf("LW_API_SECRET=%s", c.Secret), + fmt.Sprintf("LW_API_TOKEN=%s", c.Token), + fmt.Sprintf("LW_ORGANIZATION=%v", c.OrgLevel), + fmt.Sprintf("LW_NONINTERACTIVE=%v", c.nonInteractive), + fmt.Sprintf("LW_NOCACHE=%v", c.noCache), + fmt.Sprintf("LW_NOCOLOR=%s", os.Getenv("NO_COLOR")), + fmt.Sprintf("LW_LOG=%s", c.Log.Level().CapitalString()), + fmt.Sprintf("LW_JSON=%v", c.jsonOutput), + fmt.Sprintf("LW_CDK_TARGET=%s", c.GrpcTarget()), + fmt.Sprintf("LW_API_SERVER_URL=%s", c.LwApi.URL()), + fmt.Sprintf("LW_CLI_VERSION=%s", Version), + } +} + +// GrpcTarget returns the gRPC target that the CDK architecture will use +// to allow components to communicate back to the Lacework CLI +func (c *cliState) GrpcTarget() string { + if target := os.Getenv("LW_CDK_TARGET"); target != "" { + return target + } + return fmt.Sprintf("localhost:%v", c.cdkServerPort) +} + +func (c *cliState) ReadCache(ctx context.Context, in *cdk.ReadCacheRequest) (*cdk.ReadCacheResponse, error) { + if in.Key == "" { + return nil, errors.New("cache key must be supplied") + } + + var data []byte + if !c.ReadCachedAsset(in.Key, &data) { // not expired + return &cdk.ReadCacheResponse{Hit: true, Data: data}, nil + } + return &cdk.ReadCacheResponse{ + Hit: false, + Data: nil, + }, nil +} + +func (c *cliState) WriteCache(ctx context.Context, in *cdk.WriteCacheRequest) (*cdk.WriteCacheResult, error) { + if in.Key == "" { + return nil, errors.New("cache key must be supplied") + } + + err := c.writeAssetToCache(in.Key, in.Expires.AsTime(), in.Data) + if err != nil { + msg := err.Error() + return &cdk.WriteCacheResult{Error: true, Message: msg}, nil + } + return &cdk.WriteCacheResult{Error: false, Message: ""}, nil +} + +// Ping implements CDK.Ping +func (c *cliState) Ping(ctx context.Context, in *cdk.PingRequest) (*cdk.PongReply, error) { + c.Log.Debugw("message", "from", "CDK/Ping", "component_name", in.GetComponentName()) + return &cdk.PongReply{Message: fmt.Sprintf("Pong %s", in.GetComponentName())}, nil +} + +// Honeyvent implements CDK.Honeyvent +func (c *cliState) Honeyvent(ctx context.Context, in *cdk.HoneyventRequest) (*cdk.Reply, error) { + c.Log.Debugw("message", "from", "CDK/Honeyvent", "feature", in.GetFeature()) + + // Set event feature, if provided + if f := in.GetFeature(); f != "" { + c.Event.Feature = f + } + + // Add feature fields + for key, value := range in.GetFeatureData() { + c.Event.AddFeatureField(key, value) + } + + // Set any error, if any + if err := in.GetError(); err != "" { + c.Event.Error = err + } + + // Set duration in millisecond, if provided + if durationMs := in.GetDurationMs(); durationMs != 0 { + c.Event.DurationMs = durationMs + } + + // Send the Honeyvent + c.SendHoneyvent() + + return &cdk.Reply{}, nil +} + +// Serve will start the CDK gRPC server +func (c *cliState) Serve() error { + // Start the gRPC server for components to communicate back + const maxAttempts = 20 + + if target := os.Getenv("LW_CDK_TARGET"); target != "" { + return c.serve(target) + } + + // Try a range of port numbers in case the default one is not available + var err error + for i := 0; i < maxAttempts; i++ { + err = c.serve(fmt.Sprintf("localhost:%v", defaultGrpcPort+i)) + if err == nil { + c.cdkServerPort = defaultGrpcPort + i + return err + } + } + return errors.Wrap(err, fmt.Sprintf("unable to start gRPC server (attempts: %d)", maxAttempts)) +} + +func (c *cliState) serve(target string) error { + c.Stop() // make sure server is not running + + lis, err := net.Listen("tcp", target) + if err != nil { + return errors.Wrap(err, "failed to listen") + } + + c.cdkServer = grpc.NewServer() // guardrails-disable-line + cdk.RegisterCoreServer(c.cdkServer, c) + + c.Log.Infow("gRPC server started", "address", lis.Addr()) + if err := c.cdkServer.Serve(lis); err != nil { + return errors.Wrap(err, "failed to serve") + } + + return nil +} + +// Stop will stop the CDK gRPC server gracefully. It stops the server from +// accepting new connections and RPCs and blocks until all the pending RPCs +// are finished. +func (c *cliState) Stop() { + if c.cdkServer != nil { + c.Log.Info("stopping gRPC server") + c.cdkServer.GracefulStop() + } +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cli_state.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cli_state.go new file mode 100644 index 000000000..ec7499932 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/cli_state.go @@ -0,0 +1,517 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "math/rand" + "net/http" + "os" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/briandowns/spinner" + "github.com/fatih/color" + prettyjson "github.com/hokaccha/go-prettyjson" + "github.com/mattn/go-isatty" + "github.com/peterbourgon/diskv/v3" + "github.com/pkg/errors" + "github.com/spf13/viper" + "go.uber.org/zap" + "google.golang.org/grpc" + + "github.com/lacework/go-sdk/api" + cdk "github.com/lacework/go-sdk/cli/cdk/go/proto/v1" + "github.com/lacework/go-sdk/internal/format" + "github.com/lacework/go-sdk/lwcomponent" + "github.com/lacework/go-sdk/lwconfig" + "github.com/lacework/go-sdk/lwlogger" +) + +// cliState holds the state of the entire Lacework CLI +type cliState struct { + Profile string + Account string + Subaccount string + KeyID string + Secret string + Token string + OrgLevel bool + CfgVersion int + + LwApi *api.Client + JsonF *prettyjson.Formatter + Log *zap.SugaredLogger + Event *api.Honeyvent + Cache *diskv.Diskv + LwComponents *lwcomponent.State + + id string + workers sync.WaitGroup + spinner *spinner.Spinner + jsonOutput bool + yamlOutput bool + csvOutput bool + nonInteractive bool + noCache bool + lqlOperator string + profileDetails map[string]interface{} + tokenCache api.TokenData + installedCmd bool + componentParser componentArgParser + + // Implements core proto service + cdk.UnimplementedCoreServer + + // Allows only one gRPC Server + cdkServer *grpc.Server + cdkServerPort int +} + +// NewDefaultState creates a new cliState with some defaults +func NewDefaultState() *cliState { + c := &cliState{ + id: newID(), + Profile: "default", + lqlOperator: "rlike", // @afiune we use rlike to allow user to pass regex + CfgVersion: 2, + Log: lwlogger.New("").Sugar(), + JsonF: &prettyjson.Formatter{ + KeyColor: color.New(color.FgCyan, color.Bold), + StringColor: color.New(color.FgGreen, color.Bold), + BoolColor: color.New(color.FgYellow, color.Bold), + NumberColor: color.New(color.FgRed, color.Bold), + NullColor: color.New(color.FgWhite, color.Bold), + Indent: 2, + Newline: "\n", + }, + nonInteractive: !isatty.IsTerminal(os.Stdout.Fd()), + cdkServerPort: defaultGrpcPort, + } + + // initialize honeycomb library and honeyvent + c.InitHoneyvent() + + return c +} + +// SetProfile sets the provided profile into the cliState and loads the entire +// state of the Lacework CLI by calling 'LoadState()' +func (c *cliState) SetProfile(profile string) error { + if profile == "" { + return errors.New("Specify a profile.") + } + + c.Profile = profile + c.Log.Debugw("custom profile", "profile", profile) + return c.LoadState() +} + +// LoadState loads the state of the cli in the following order, loads the +// configured profile out from the viper loaded config, if the profile is +// set to the default and it is not found, we assume that the user is running +// the CLI with parameters or environment variables, so we proceed to load +// those. Though, if the profile is NOT the default, we error out with some +// breadcrumbs to help the user configure the CLI. After loading the profile, +// this function verifies parameters and env variables coming from viper +func (c *cliState) LoadState() error { + defer func() { + // update global honeyvent with loaded state + c.Event.Account = c.Account + c.Event.Subaccount = c.Subaccount + c.Event.Profile = c.Profile + c.Event.ApiKey = c.KeyID + c.Event.CfgVersion = c.CfgVersion + }() + + c.profileDetails = viper.GetStringMap(c.Profile) + if len(c.profileDetails) == 0 { + if c.Profile != "default" { + return fmt.Errorf( + "The profile '%s' could not be found.\n\nTry running 'lacework configure --profile %s'.", + c.Profile, c.Profile, + ) + } else { + c.Log.Debugw("unable to load state from config") + c.loadStateFromViper() + return nil + } + } + + c.Token = c.extractValueString("api_token") + c.KeyID = c.extractValueString("api_key") + c.Secret = c.extractValueString("api_secret") + c.Account = c.extractValueString("account") + c.Subaccount = c.extractValueString("subaccount") + version := c.extractValueInt("version") + if version > 2 { + c.CfgVersion = version + } + + c.Log.Debugw("state loaded", + "profile", c.Profile, + "account", c.Account, + "subaccount", c.Subaccount, + "api_token", format.Secret(4, c.Token), + "api_key", c.KeyID, + "api_secret", format.Secret(4, c.Secret), + "config_version", c.CfgVersion, + ) + + c.loadStateFromViper() + return nil +} + +// LoadProfiles loads all the profiles from the configuration file +func (c *cliState) LoadProfiles() (lwconfig.Profiles, error) { + confPath := viper.ConfigFileUsed() + + if confPath == "" { + return nil, errors.New("unable to load profiles. No configuration file found.") + } + + return lwconfig.LoadProfilesFrom(confPath) +} + +// VerifySettings checks if the CLI state has the necessary settings to run, +// if not, it throws an error with breadcrumbs to help the user configure the CLI +func (c *cliState) VerifySettings() error { + c.Log.Debugw("verifying config", "version", c.CfgVersion) + + // Token from cache + if c.Token != "" && c.Account != "" { + return nil + } + + if c.Profile == "" || + c.Account == "" || + c.Secret == "" || + c.KeyID == "" { + return fmt.Errorf( + "there is one or more settings missing.\n\nTry running 'lacework configure'.", + ) + } + + return nil +} + +// NewClient creates and stores a new Lacework API client to be used by the CLI +func (c *cliState) NewClient() error { + // @afiune load token from cache only if the token has not been already + // provided by env variables or flags. Example: lacework --api_token foo + if c.Token == "" { + c.ReadCachedToken() + } + + err := c.VerifySettings() + if err != nil { + return err + } + + apiOpts := []api.Option{ + api.WithLogLevel(c.Log.Level().CapitalString()), + api.WithSubaccount(c.Subaccount), + api.WithApiKeys(c.KeyID, c.Secret), + api.WithTimeout(time.Second * 125), + api.WithHeader("User-Agent", fmt.Sprintf("Command-Line/%s", Version)), + } + + if c.OrgLevel { + c.Log.Debug("accessing organization level data sets") + apiOpts = append(apiOpts, api.WithOrgAccess()) + } + + if c.tokenCache.Token != "" { + apiOpts = append(apiOpts, + api.WithTokenAndExpiration(c.Token, c.tokenCache.ExpiresAt)) + } else if c.Token != "" { + apiOpts = append(apiOpts, api.WithToken(c.Token)) + } else if os.Getenv("LW_API_TOKEN") != "" { + apiOpts = append(apiOpts, api.WithToken(os.Getenv("LW_API_TOKEN"))) + } + + apiOpts = append(apiOpts, + api.WithLifecycleCallbacks(api.LifecycleCallbacks{ + TokenExpiredCallback: cli.EraseCachedToken, + RequestCallback: func(httpCode int, _ http.Header) error { + if httpCode == 403 { + return c.Cache.Erase("token") + } + return nil + }, + })) + + if os.Getenv("LW_API_SERVER_URL") != "" { + apiOpts = append(apiOpts, api.WithURL(os.Getenv("LW_API_SERVER_URL"))) + } + + c.LoadLQLOperator() + + client, err := api.NewClient(c.Account, apiOpts...) + if err != nil { + return errors.Wrap(err, "unable to generate api client") + } + + c.LwApi = client + + // cache token + return c.WriteCachedToken() +} + +// LoadLQLOperator reads the reserverd environment variable to change the +// default LQL operator in the CLI for filter flags. Available operators; +// +// The `eq` operator allows you to specify a value that the field values +// of the result must be equal to. The `ne` operator means not equal to. +// +// The `in` operator allows you to specify multiple values in the values +// field of the filters. The field values of the result must match one of +// the values. The `not_in` operator is the opposite of `in`. +// +// The `like` operator allows you to specify a pattern that the field +// values of the result must match. The `not_like` operator is the +// opposite of `like`. +// +// The `ilike` operator works similar to like but it makes the match case +// insensitive. The `not_ilike` operator is the opposite of `ilike`. +// +// (default) The `rlike` operator matches the specified pattern represented +// by regular expressions. The `not_rlike` operator is the opposite of `rlike`. +// (more info on `RLIKE` see https://docs.snowflake.com/en/sql-reference/functions/rlike.html). +// +// The `gt` operator allows you to specify a value that the field values of +// the result must be greater than. The `lt` (less-than) operator is the +// opposite of `gt`. +// +// The `ge` operator allows you to specify a value that the field values of +// the result must be greater than or equal to. The `le` (less-than-or-equal-to) +// operator is the opposite of `ge`. + +func (c *cliState) LoadLQLOperator() { + if os.Getenv("LW_LQL_OPERATOR") != "" { + c.lqlOperator = os.Getenv("LW_LQL_OPERATOR") + } +} + +// InteractiveMode returns true if the cli is running in interactive mode +func (c *cliState) InteractiveMode() bool { + return !c.nonInteractive && !c.csvOutput +} + +// NonInteractive turns off interactive mode, that is, no progress bars and spinners +func (c *cliState) NonInteractive() { + c.Log.Info("turning off interactive mode") + c.nonInteractive = true +} + +// Interactive turns on interactive mode, that is, progress bars and spinners +func (c *cliState) Interactive() { + c.Log.Info("turning on interactive mode") + c.nonInteractive = false +} + +// NoCache turns off the Lacework CLI caching mechanism, so nothing will be cached +func (c *cliState) NoCache() { + c.Log.Info("turning off caching mechanism") + c.noCache = true +} + +// StartProgress starts a new progress spinner with the provider suffix and stores it +// into the cli state, make sure to run StopSpinner when you are done processing +func (c *cliState) StartProgress(suffix string) { + if c.nonInteractive { + c.Log.Debugw("skipping spinner", + "noninteractive", c.nonInteractive, + "action", "start_progress", + ) + return + } + + // humans like spinners (^.^) + if c.HumanOutput() { + // make sure there is not a spinner already running + c.StopProgress() + + // verify that the suffix starts with a space + if !strings.HasPrefix(suffix, " ") { + suffix = fmt.Sprintf(" %s", suffix) + } + + c.Log.Debugw("starting spinner", "suffix", suffix) + c.spinner = spinner.New(spinner.CharSets[9], 100*time.Millisecond) + c.spinner.Suffix = suffix + c.spinner.Start() + } +} + +// StopProgress stops the running progress spinner, if any +func (c *cliState) StopProgress() { + if c.nonInteractive { + c.Log.Debugw("skipping spinner", + "noninteractive", c.nonInteractive, + "action", "stop_progress", + ) + return + } + + // humans like spinners (^.^) + if c.HumanOutput() { + if c.spinner != nil { + c.Log.Debug("stopping spinner") + c.spinner.Stop() + c.spinner = nil + } + } +} + +// EnableJSONOutput enables the cli to display JSON output +func (c *cliState) EnableJSONOutput() { + c.Log.Info("switch output to json format") + c.jsonOutput = true +} + +// EnableYAMLOutput enables the cli to display YAML output +func (c *cliState) EnableYAMLOutput() { + c.Log.Info("switch output to yaml format") + c.yamlOutput = true +} + +// EnableJSONOutput enables the cli to display human readable output +func (c *cliState) EnableHumanOutput() { + c.Log.Info("switch output to human format") + c.jsonOutput = false +} + +// EnableCSVOutput enables the cli to display CSV output +func (c *cliState) EnableCSVOutput() { + c.Log.Info("switch output to csv format") + c.csvOutput = true +} + +// JSONOutput returns true if the cli is configured to display JSON output +func (c *cliState) JSONOutput() bool { + return c.jsonOutput +} + +// YAMLOutput returns true if the cli is configured to display YAML output +func (c *cliState) YAMLOutput() bool { + return c.yamlOutput +} + +// HumanOutput returns true if the cli is configured to display human readable output +func (c *cliState) HumanOutput() bool { + return !c.jsonOutput && !c.csvOutput && !c.yamlOutput +} + +// CSVOutput returns true if the cli is configured to display csv output +func (c *cliState) CSVOutput() bool { + return c.csvOutput +} + +// loadStateFromViper loads parameters and environment variables +// coming from viper into the CLI state +func (c *cliState) loadStateFromViper() { + if v := viper.GetString("api_token"); v != "" { + c.Token = v + c.Log.Debugw("state updated", "api_token", format.Secret(4, c.Token)) + } + + if v := viper.GetString("api_key"); v != "" { + c.KeyID = v + c.Log.Debugw("state updated", "api_key", c.KeyID) + } + + if v := viper.GetString("api_secret"); v != "" { + c.Secret = v + c.Log.Debugw("state updated", "api_secret", format.Secret(4, c.Secret)) + } + + if v := viper.GetString("account"); v != "" { + c.Account = v + c.Log.Debugw("state updated", "account", c.Account) + } + + if v := viper.GetString("subaccount"); v != "" { + c.Subaccount = v + c.Log.Debugw("state updated", "subaccount", c.Subaccount) + } + + if viper.GetBool("organization") { + c.OrgLevel = true + c.Log.Debugw("state updated", "organization", "true") + } +} + +func (c *cliState) extractValueString(key string) string { + if val, ok := c.profileDetails[key]; ok { + if str, ok := val.(string); ok { + return str + } + c.Log.Warnw("config value type mismatch", + "expected_type", "string", + "actual_type", reflect.TypeOf(val), + "file", viper.ConfigFileUsed(), + "profile", c.Profile, + "key", key, + "value", val, + ) + return "" + } + c.Log.Warnw("unable to find key from config", + "file", viper.ConfigFileUsed(), + "profile", c.Profile, + "key", key, + ) + return "" +} + +func (c *cliState) extractValueInt(key string) int { + if val, ok := c.profileDetails[key]; ok { + if i, ok := val.(int64); ok { + return int(i) + } + c.Log.Warnw("config value type mismatch", + "expected_type", "int", + "actual_type", reflect.TypeOf(val), + "file", viper.ConfigFileUsed(), + "profile", c.Profile, + "key", key, + "value", val, + ) + return 0 + } + c.Log.Warnw("unable to find key from config", + "file", viper.ConfigFileUsed(), + "profile", c.Profile, + "key", key, + ) + return 0 +} + +// newID generates a new client id, this id is useful for logging purposes +// when there are more than one client running on the same machine +// TODO @afiune move this into its own go package (look at api/client.go) +func newID() string { + now := time.Now().UTC().UnixNano() + seed := rand.New(rand.NewSource(now)) + return strconv.FormatInt(seed.Int63(), 16) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cli_unix.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cli_unix.go new file mode 100644 index 000000000..252c35a7a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/cli_unix.go @@ -0,0 +1,101 @@ +//go:build !windows + +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "os" + + "github.com/AlecAivazis/survey/v2" + "github.com/fatih/color" +) + +// used by configure.go +var configureListCmdSetProfileEnv = `export LW_PROFILE="my-profile"` + +// promptIconsFuncs configures the prompt icons for Unix systems +var promptIconsFunc = func(icons *survey.IconSet) { + icons.Question.Text = "â–¸" +} + +// customPromptIconsFunc configures the prompt icons with custom string for Unix systems +var customPromptIconsFunc = func(s string) func(icons *survey.IconSet) { + return func(icons *survey.IconSet) { + icons.Question.Text = fmt.Sprintf("â–¸ %s", s) + } +} + +// A variety of colorized icons used throughout the code +var ( + successIcon = color.HiGreenString("✓") + failureIcon = color.HiRedString("✖") //nolint +) + +// Env variables found in GCP, AWS and Azure cloudshell. +// Used to determine if cli is running on cloudshell. +const ( + gcpCloudEnv = "CLOUD_SHELL" + awsCloudEnv = "AWS_EXECUTION_ENV" + AzureCloudEnv = "POWERSHELL_DISTRIBUTION_CHANNEL" +) + +// UpdateCommand returns the command that a user should run to update the cli +// to the latest available version (unix specific command) +func (c *cliState) UpdateCommand() string { + if os.Getenv(HomebrewInstall) != "" { + return ` + brew upgrade lacework-cli +` + } + + if isCloudShell() { + return ` + curl https://raw.githubusercontent.com/lacework/go-sdk/main/cli/install.sh | bash -s -- -d $HOME/bin +` + } + return ` + curl https://raw.githubusercontent.com/lacework/go-sdk/main/cli/install.sh | bash +` +} + +// isCloudShell uses env variables specific to GCP, AWS and Azure +// to determine if the Lacework CLI is running on cloudshell +func isCloudShell() bool { + return isAwsCloudShell() || isGcpCloudShell() || isAzureCloudShell() +} + +// isAzureCloudShell uses the native env variable POWERSHELL_DISTRIBUTION_CHANNEL="CloudShell" +// to determine if the Lacework CLI is running on Azure cloudshell +func isAzureCloudShell() bool { + return os.Getenv(AzureCloudEnv) == "CloudShell" +} + +// isGcpCloudShell uses the native env variable CLOUD_SHELL=true +// to determine if the Lacework CLI is running on GCP cloudshell +func isGcpCloudShell() bool { + return os.Getenv(gcpCloudEnv) == "true" +} + +// isAwsCloudShell uses the native env variable AWS_EXECUTION_ENV="Cloudshell" +// to determine if the Lacework CLI is running on AWS cloudshell +func isAwsCloudShell() bool { + return os.Getenv(awsCloudEnv) == "Cloudshell" +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cli_windows.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cli_windows.go new file mode 100644 index 000000000..7ace9160f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/cli_windows.go @@ -0,0 +1,63 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "os" + + "github.com/AlecAivazis/survey/v2" + "github.com/fatih/color" +) + +// used by configure.go +var configureListCmdSetProfileEnv = `$env:LW_PROFILE = 'my-profile'` + +// promptIconsFuncs configures the prompt icons for Windows systems +var promptIconsFunc = func(icons *survey.IconSet) { + icons.Question.Text = ">" +} + +// customPromptIconsFunc configures the prompt icons with custom string for Windows systems +var customPromptIconsFunc = func(s string) func(icons *survey.IconSet) { + return func(icons *survey.IconSet) { + icons.Question.Text = fmt.Sprintf("> %s", s) + } +} + +// A variety of colorized icons used throughout the code +var ( + successIcon = color.HiGreenString("√") + failureIcon = color.HiRedString("×") //nolint +) + +// UpdateCommand returns the command that a user should run to update the cli +// to the latest available version (windows specific command) +func (c *cliState) UpdateCommand() string { + if os.Getenv(ChocolateyInstall) != "" { + return ` + choco upgrade lacework-cli +` + } + + return ` + Set-ExecutionPolicy Bypass -Scope Process -Force; + iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/lacework/go-sdk/main/cli/install.ps1')) +` +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cloud_account.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cloud_account.go new file mode 100644 index 000000000..7246a1dfe --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/cloud_account.go @@ -0,0 +1,339 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/internal/format" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // top-level cloud-account command + cloudAccountCommand = &cobra.Command{ + Use: "cloud-account", + Aliases: []string{"cloud-accounts", "cloud", "ca"}, + Short: "Manage cloud accounts", + Long: "Manage cloud account integrations with Lacework", + } + + // used by cloud account list to list only a single type of cloud account + cloudAccountType string + + // cloudAccountsListCmd represents the list sub-command inside the cloud accounts command + cloudAccountListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all available cloud account integrations", + Args: cobra.NoArgs, + RunE: cloudAccountList, + } + + // cloudAccountShowCmd represents the show sub-command inside the cloud accounts command + cloudAccountShowCmd = &cobra.Command{ + Use: "show", + Aliases: []string{"get"}, + Short: "Show a single cloud account integration", + Args: cobra.ExactArgs(1), + RunE: cloudAccountShow, + } + + // cloudAccountCreateCmd represents the show sub-command inside the cloud accounts command + cloudAccountCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new cloud account integration", + Args: cobra.NoArgs, + RunE: cloudAccountCreate, + } + + // cloudAccountDeleteCmd represents the delete sub-command inside the cloud accounts command + cloudAccountDeleteCmd = &cobra.Command{ + Use: "delete", + Aliases: []string{"rm"}, + Short: "Delete a cloud account integration", + Args: cobra.ExactArgs(1), + RunE: cloudAccountDelete, + } + + // cloudAccountMigrateCmd represents the migrate sub-command inside the cloud accounts command + cloudAccountMigrateCmd = &cobra.Command{ + Use: "migrate", + Short: "Mark a GCPv1 (storage-based) cloud account integration for migration", + Args: cobra.ExactArgs(1), + RunE: cloudAccountMigrate, + } +) + +func init() { + // add the cloud-account command + rootCmd.AddCommand(cloudAccountCommand) + cloudAccountCommand.AddCommand(cloudAccountListCmd) + cloudAccountCommand.AddCommand(cloudAccountShowCmd) + cloudAccountCommand.AddCommand(cloudAccountDeleteCmd) + cloudAccountCommand.AddCommand(cloudAccountCreateCmd) + cloudAccountCommand.AddCommand(cloudAccountMigrateCmd) + + // add type flag to cloud accounts list command + cloudAccountListCmd.Flags().StringVarP(&cloudAccountType, + "type", "t", "", "list all cloud accounts of a specific type", + ) +} + +func cloudAccountsToTable(cloudAccounts []api.CloudAccountRaw) [][]string { + var out [][]string + for _, cadata := range cloudAccounts { + out = append(out, []string{ + cadata.IntgGuid, + cadata.Name, + cadata.Type, + cadata.Status(), + cadata.StateString(), + }) + } + return out +} + +func cloudAccountList(_ *cobra.Command, _ []string) error { + var ( + cloudAccounts api.CloudAccountsResponse + err error + ) + + if cloudAccountType != "" { + caType, found := api.FindCloudAccountType(cloudAccountType) + if !found { + return errors.Errorf("unknown cloud account type '%s'", cloudAccountType) + } + cloudAccounts, err = cli.LwApi.V2.CloudAccounts.ListByType(caType) + } else { + cloudAccounts, err = cli.LwApi.V2.CloudAccounts.List() + } + if err != nil { + return errors.Wrap(err, "unable to get cloud accounts") + } + + if cli.JSONOutput() { + return cli.OutputJSON(cloudAccounts.Data) + } + + if len(cloudAccounts.Data) == 0 { + cli.OutputHuman("No cloud accounts found.\n") + return nil + } + + cli.OutputHuman( + renderSimpleTable( + []string{"Cloud Account GUID", "Name", "Type", "Status", "State"}, + cloudAccountsToTable(cloudAccounts.Data), + ), + ) + return nil +} + +func cloudAccountDelete(_ *cobra.Command, args []string) error { + cli.StartProgress(" Deleting cloud account...") + err := cli.LwApi.V2.CloudAccounts.Delete(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to delete cloud account") + } + cli.OutputHuman("The cloud account %s was deleted.\n", args[0]) + return nil +} + +func cloudAccountMigrate(_ *cobra.Command, args []string) error { + cli.StartProgress(" Initiating migration for cloud account...") + err := cli.LwApi.V2.CloudAccounts.Migrate(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to initiate migration for cloud-account.") + } + cli.OutputHuman("The cloud account %s was marked for migration.\n", args[0]) + return nil +} + +func cloudAccountShow(_ *cobra.Command, args []string) error { + var ( + cloudAccount api.CloudAccountResponse + out [][]string + ) + cli.StartProgress(" Fetching cloud account...") + err := cli.LwApi.V2.CloudAccounts.Get(args[0], &cloudAccount) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to retrieve cloud account") + } + + out = append(out, []string{cloudAccount.Data.IntgGuid, + cloudAccount.Data.Name, + cloudAccount.Data.Type, + cloudAccount.Data.Status(), + cloudAccount.Data.StateString()}) + + if cli.JSONOutput() { + return cli.OutputJSON(cloudAccount.Data) + } + + cli.OutputHuman(renderSimpleTable([]string{"Cloud Account GUID", "Name", "Type", "Status", "State"}, out)) + cli.OutputHuman("\n") + cli.OutputHuman(buildDetailsTable(cloudAccount.Data)) + return nil +} + +func buildDetailsTable(integration api.V2RawType) string { + var details [][]string + + if caMap, ok := integration.GetData().(map[string]interface{}); ok { + for k, v := range caMap { + switch val := v.(type) { + case int: + details = append(details, []string{strings.ToUpper(format.SpaceUpperCase(k)), strconv.Itoa(val)}) + case float64: + details = append(details, + []string{strings.ToUpper(format.SpaceUpperCase(k)), strconv.FormatFloat(val, 'f', -1, 64)}) + case string: + details = append(details, []string{strings.ToUpper(format.SpaceUpperCase(k)), val}) + case map[string]any: + for i, j := range val { + if v, ok := j.(string); ok { + details = append(details, []string{strings.ToUpper(format.SpaceUpperCase(i)), v}) + } else { + cli.Log.Warn("unable to build table details, unknown type", "type", i, "key", k) + } + } + case []any: + var values []string + for _, i := range val { + if _, ok := i.(string); ok { + values = append(values, i.(string)) + } else if _, ok := i.(map[string]interface{}); ok { + for m, n := range i.(map[string]interface{}) { + values = append(values, fmt.Sprintf("%s:%s", m, n)) + } + } else { + cli.Log.Warn("unable to build table details, unknown type", "type", i, "key", k) + } + } + details = append(details, []string{strings.ToUpper(format.SpaceUpperCase(k)), strings.Join(values, ",")}) + } + } + } + + // get server token for container registry type only + if c, ok := integration.(api.ContainerRegistryRaw); ok { + if c.ServerToken != nil { + details = append(details, []string{"SERVER_TOKEN", c.ServerToken.ServerToken}) + details = append(details, []string{"SERVER_TOKEN_URI", c.ServerToken.Uri}) + } + } + + //common + details = append(details, []string{"UPDATED AT", integration.GetCommon().CreatedOrUpdatedTime}) + details = append(details, []string{"UPDATED BY", integration.GetCommon().CreatedOrUpdatedBy}) + if integration.GetCommon().State != nil { + details = append(details, []string{ + "STATE UPDATED AT", integration.GetCommon().State.LastUpdatedTime.Format(time.RFC3339)}) + details = append(details, []string{ + "LAST SUCCESSFUL STATE", integration.GetCommon().State.LastSuccessfulTime.Format(time.RFC3339)}) + + //output state details as a json string + jsonState, err := json.Marshal(integration.GetCommon().State.Details) + if err == nil { + detailsJSON, err := cli.FormatJSONString(string(jsonState)) + if err == nil { + details = append(details, []string{"STATE DETAILS", detailsJSON}) + } + } + } + + array.Sort2D(details) + return renderOneLineCustomTable("DETAILS", + renderCustomTable([]string{}, details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ) + +} + +func cloudAccountCreate(_ *cobra.Command, _ []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + + err := promptCreateCloudAccount() + if err != nil { + return errors.Wrap(err, "unable to create cloud account") + } + + cli.OutputHuman("The cloud account was created.\n") + return nil +} + +func promptCreateCloudAccount() error { + var ( + cloudAccount = "" + prompt = &survey.Select{ + Message: "Choose a cloud account type to create: ", + Options: []string{ + "AWS Config", + "AWS CloudTrail", + "AWS Config (US GovCloud)", + "AWS CloudTrail (US GovCloud)", + "GCP Config", + "GCP Audit Log", + "GCP Audit Log PubSub", + "Azure Config", + "Azure Activity Log", + "OCI Config", + }, + } + err = survey.AskOne(prompt, &cloudAccount) + ) + if err != nil { + return err + } + + switch cloudAccount { + case "AWS Config": + return createAwsConfigIntegration() + case "AWS CloudTrail": + return createAwsCloudTrailIntegration() + case "AWS GovCloud Config": + return createAwsGovCloudConfigIntegration() + case "AWS GovCloud CloudTrail": + return createAwsGovCloudCTIntegration() + case "GCP Config": + return createGcpConfigIntegration() + case "GCP Audit Log": + return createGcpAuditLogIntegration() + case "GCP Audit Log PubSub": + return createGcpPubSubAuditLogIntegration() + case "Azure Config": + return createAzureConfigIntegration() + case "Azure Activity Log": + return createAzureActivityLogIntegration() + case "Azure Active Directory Activity Log": + return createAzureAdAlIntegration() + case "OCI Config": + return createOciConfigIntegration() + default: + return errors.New("unknown cloud account type") + } +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance.go b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance.go new file mode 100644 index 000000000..1ccd84052 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance.go @@ -0,0 +1,572 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" + + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/lwseverity" +) + +var ( + compCmdState = struct { + // download report in PDF format + Pdf bool + + // output report in CSV format + Csv bool + + // display extended details about a compliance report + Details bool + + // Filter the recommendations table by category + Category []string + + // Filter the recommendations table by service + Service []string + + // Filter the recommendations table by severity + Severity string + + // Filter the recommendations table by status + Status string + + // output resources affected by recommendationID + RecommendationID string + }{} + + // complianceCmd represents the compliance command + complianceCmd = &cobra.Command{ + Use: "compliance", + Aliases: []string{"comp"}, + Short: "Manage compliance reports", + Long: `Manage compliance reports for Google, Azure, or AWS cloud providers. + +Lacework cloud security platform provides continuous Compliance monitoring against +cloud security best practices and compliance standards as CIS, PCI DSS, SoC II and +HIPAA benchmark standards. + +Get started by integrating one or more cloud accounts using the command: + + lacework cloud-account create + +If you prefer to configure the integration via the WebUI, log in to your account at: + + https://.lacework.net + +Then navigate to Settings > Integrations > Cloud Accounts. + +Use the following command to list all available integrations in your account: + + lacework cloud-account list +`, + } + + // complianceAzureCmd represents the azure sub-command inside the compliance command + complianceAzureCmd = &cobra.Command{ + Use: "azure", + Aliases: []string{"az"}, + Short: "Compliance for Azure Cloud", + Long: `Manage compliance reports for Azure Cloud. + +To list all Azure tenants configured in your account: + + lacework compliance azure list-tenants + +To list all Azure subscriptions from a tenant, use the command: + + lacework compliance azure list-subscriptions + +To get the latest Azure compliance assessment report, use the command: + + lacework compliance azure get-report + +These reports run on a regular schedule, typically once a day. +`, + } + + // complianceGcpCmd represents the gcp sub-command inside the compliance command + complianceGcpCmd = &cobra.Command{ + Use: "google", + Aliases: []string{"gcp"}, + Short: "Compliance for Google Cloud", + Long: `Manage compliance reports for Google Cloud. + +To list all GCP organizations and projects configured in your account: + + lacework compliance gcp list + +To list all GCP projects from an organization, use the command: + + lacework compliance gcp list-projects + +To get the latest GCP compliance assessment report, use the command: + + lacework compliance gcp get-report + +These reports run on a regular schedule, typically once a day. +`, + } + + // complianceAwsCmd represents the aws sub-command inside the compliance command + complianceAwsCmd = &cobra.Command{ + Use: "aws", + Short: "Compliance for AWS", + Long: `Manage compliance reports for Amazon Web Services (AWS). + +To list all AWS accounts configured in your account: + + lacework compliance aws list-accounts + +To get the latest AWS compliance assessment report: + + lacework compliance aws get-report + +These reports run on a regular schedule, typically once a day. +`, + } +) + +func init() { + // add the compliance command + rootCmd.AddCommand(complianceCmd) + + // add sub-commands to the compliance command + complianceCmd.AddCommand(complianceAzureCmd) + complianceCmd.AddCommand(complianceAwsCmd) + complianceCmd.AddCommand(complianceGcpCmd) +} + +func complianceReportSummaryTable(summaries []api.ReportSummary) [][]string { + if len(summaries) == 0 { + return [][]string{} + } + summary := summaries[0] + return [][]string{ + {"Critical", fmt.Sprint(summary.NumSeverity1NonCompliance)}, + {"High", fmt.Sprint(summary.NumSeverity2NonCompliance)}, + {"Medium", fmt.Sprint(summary.NumSeverity3NonCompliance)}, + {"Low", fmt.Sprint(summary.NumSeverity4NonCompliance)}, + {"Info", fmt.Sprint(summary.NumSeverity5NonCompliance)}, + } +} + +func complianceReportRecommendationsTable(recommendations []api.RecommendationV2) [][]string { + out := [][]string{} + for _, recommend := range recommendations { + out = append(out, []string{ + recommend.RecID, + recommend.Title, + recommend.Status, + recommend.SeverityString(), + recommend.Service, + fmt.Sprint(len(recommend.Violations)), + fmt.Sprint(recommend.AssessedResourceCount), + }) + } + + sort.Slice(out, func(i, j int) bool { + return api.SeverityOrder(out[i][3]) < api.SeverityOrder(out[j][3]) + }) + + return out +} + +type complianceCSVReportDetails struct { + // For clouds with tenant models, supply tenant ID + TenantID string + + // For clouds with tenant models, supply tenant name/alias + TenantName string + + // Supply the account id for the cloud enviornment + AccountID string + + // Supply the account name/alias for the cloud enviornment, if available + AccountName string + + // The type of report being rendered + ReportType string + + // The time of the report execution + ReportTime time.Time + + // Recommendations + Recommendations []api.RecommendationV2 +} + +func (c complianceCSVReportDetails) GetAccountDetails() []string { + accountAlias := c.AccountID + if c.AccountName != "" { + accountAlias = fmt.Sprintf("%s(%s)", c.AccountName, c.AccountID) + } + + tenantAlias := c.TenantID + if c.TenantName != "" { + tenantAlias = fmt.Sprintf("%s(%s)", c.TenantName, c.TenantID) + } + out := []string{} + if tenantAlias != "" { + out = append(out, tenantAlias) + } + + if accountAlias != "" { + out = append(out, accountAlias) + } + return out +} + +func (c complianceCSVReportDetails) GetReportMetaData() []string { + return append([]string{c.ReportType, c.ReportTime.Format(time.RFC3339)}, c.GetAccountDetails()...) +} + +func (c complianceCSVReportDetails) SortRecommendations() { + sort.Slice(c.Recommendations, func(i, j int) bool { + return c.Recommendations[i].Category < c.Recommendations[j].Category + }) + +} + +func complianceCSVReportRecommendationsTable(details *complianceCSVReportDetails) [][]string { + details.SortRecommendations() + out := [][]string{} + + for _, recommendation := range details.Recommendations { + // GROW-1266: Do not add if status flag filters suppressed + if compCmdState.Status == "" || compCmdState.Status == "suppressed" { + for _, suppression := range recommendation.Suppressions { + out = append(out, + append(details.GetReportMetaData(), + recommendation.Category, + recommendation.RecID, + recommendation.Title, + "Suppressed", + recommendation.SeverityString(), + suppression, + "", + "")) + } + } + + // @afiune The platform today only returns a list of resources that are in violation, that + // means, "non-compliant" resources. We currently do not return the list of resources that + // are "compliant", "requires-manual-assessment" or "could-not-assess" - For those cases + // our CSV output will only show the number of resources. (GROW-2316) + if len(recommendation.Violations) == 0 { + out = append(out, + append(details.GetReportMetaData(), + recommendation.Category, + recommendation.RecID, + recommendation.Title, + recommendation.Status, + recommendation.SeverityString(), + fmt.Sprintf("%d", recommendation.ResourceCount), + "", + "")) + continue + } + + // @afiune + for _, violation := range recommendation.Violations { + out = append(out, + append(details.GetReportMetaData(), + recommendation.Category, + recommendation.RecID, + recommendation.Title, + recommendation.Status, + recommendation.SeverityString(), + violation.Resource, + violation.Region, + strings.Join(violation.Reasons, ","))) + } + } + + sort.Slice(out, func(i, j int) bool { + return api.SeverityOrder(out[i][3]) < api.SeverityOrder(out[j][3]) + }) + + return out +} + +func buildComplianceReportTable( + detailsTable, summaryTable, recommendationsTable [][]string, filteredOutput string, +) string { + mainReport := &strings.Builder{} + mainReport.WriteString( + renderCustomTable( + []string{ + "Compliance Report Details", + "Non-Compliant Recommendations", + }, + [][]string{[]string{ + renderCustomTable([]string{}, detailsTable, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator("") + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + renderCustomTable([]string{"Severity", "Count"}, summaryTable, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + }}, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + t.SetColumnSeparator(" ") + }), + ), + ) + + if compCmdState.Details || complianceFiltersEnabled() { + mainReport.WriteString( + renderCustomTable( + []string{"ID", "Recommendation", "Status", "Severity", + "Service", "Affected", "Assessed"}, + recommendationsTable, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetRowLine(true) + t.SetColumnSeparator(" ") + }), + ), + ) + if filteredOutput != "" { + mainReport.WriteString(filteredOutput) + } + mainReport.WriteString("\n") + + if compCmdState.Status == "" { + mainReport.WriteString( + "Try adding '--status non-compliant' to show only non-compliant recommendations.", + ) + } else if compCmdState.Severity == "" { + mainReport.WriteString( + "Try adding '--severity high' to show only high and critical recommendations.", + ) + } else { + mainReport.WriteString( + "Try adding [recommendation_id] to show affected resources.", + ) + } + mainReport.WriteString("\n") + } else { + mainReport.WriteString( + "Try adding '--details' to increase details shown about the compliance report.\n", + ) + } + return mainReport.String() +} + +func filterRecommendations(recommendations []api.RecommendationV2) ([]api.RecommendationV2, string) { + var filtered []api.RecommendationV2 + for _, r := range recommendations { + if matchRecommendationsFilters(r) { + filtered = append(filtered, r) + } + } + if len(filtered) == 0 { + return filtered, "There are no recommendations with the specified filter(s).\n" + } + + cli.Log.Debugw("filtered recommendations", "recommendations", filtered) + return filtered, fmt.Sprintf("%v of %v recommendations showing \n", len(filtered), len(recommendations)) +} + +func matchRecommendationsFilters(r api.RecommendationV2) bool { + var results []bool + + // severity returns specified threshold and above + if compCmdState.Severity != "" { + sevThreshold, _ := lwseverity.Normalize(compCmdState.Severity) + results = append(results, r.Severity <= sevThreshold) + } + + if len(compCmdState.Category) > 0 { + var categories []string + for _, c := range compCmdState.Category { + categories = append(categories, strings.ReplaceAll(c, "-", " ")) + } + results = append(results, array.ContainsStrCaseInsensitive(categories, r.Category)) + } + + if len(compCmdState.Service) > 0 { + results = append(results, array.ContainsStrCaseInsensitive(compCmdState.Service, r.Service)) + } + + if compCmdState.Status != "" { + results = append(results, r.Status == statusToProperTypes(compCmdState.Status)) + } + + return !array.ContainsBool(results, false) +} + +func complianceFiltersEnabled() bool { + return len(compCmdState.Category) > 0 || + compCmdState.Status != "" || + compCmdState.Severity != "" || + len(compCmdState.Service) > 0 +} + +func statusToProperTypes(status string) string { + switch strings.ToLower(status) { + case "non-compliant", "noncompliant": + return "NonCompliant" + case "compliant": + return "Compliant" + case "could-not-assess", "couldnotassess": + return "CouldNotAssess" + case "suppressed": + return "Suppressed" + case "requires-manual-assessment", "requiresmanualassessment": + return "RequiresManualAssessment" + default: + return "Unknown" + } +} + +func outputResourcesByRecommendationID(report api.CloudComplianceReportV2) error { + recommendation, found := report.GetComplianceRecommendation(compCmdState.RecommendationID) + if !found || recommendation == nil { + return errors.Errorf("recommendation id '%s' not found.", compCmdState.RecommendationID) + } + violations := recommendation.Violations + affectedResources := len(recommendation.Violations) + + if cli.JSONOutput() { + return cli.OutputJSON(recommendation) + } + + cli.OutputHuman( + renderOneLineCustomTable("RECOMMENDATION DETAILS", + renderCustomTable([]string{}, + [][]string{ + {"ID", compCmdState.RecommendationID}, + {"SEVERITY", recommendation.SeverityString()}, + {"SERVICE", recommendation.Service}, + {"CATEGORY", recommendation.Category}, + {"STATUS", recommendation.Status}, + {"ASSESSED RESOURCES", strconv.Itoa(recommendation.AssessedResourceCount)}, + {"AFFECTED RESOURCES", strconv.Itoa(affectedResources)}, + }, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + )) + + if affectedResources == 0 { + cli.OutputHuman("\nNo resources found affected by '%s'\n", compCmdState.RecommendationID) + return nil + } + + cli.OutputHuman( + renderSimpleTable( + []string{"AFFECTED RESOURCE", "REGION", "REASON"}, + violationsToTable(violations), + ), + ) + return nil +} + +func violationsToTable(violations []api.ComplianceViolationV2) (resourceTable [][]string) { + for _, v := range violations { + resourceTable = append(resourceTable, []string{v.Resource, v.Region, strings.Join(v.Reasons, ",")}) + } + return +} + +// nolint +func getReportTypes(reportSubType string) (validTypes []string, err error) { + cacheKey := fmt.Sprintf("reports/definitions/%s", reportSubType) + expired := cli.ReadCachedAsset(cacheKey, &validTypes) + + if expired { + cli.StartProgress("fetching valid report types...") + reportDefinitions, err := cli.LwApi.V2.ReportDefinitions.List() + cli.StopProgress() + + if err != nil { + return nil, err + } + + for _, report := range reportDefinitions.Data { + if report.SubReportType == reportSubType { + validTypes = append(validTypes, report.ReportNotificationType) + } + } + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), validTypes) + } + + return validTypes, err +} + +func prettyPrintReportTypes(reportTypes []string) string { + var sb strings.Builder + for i, r := range reportTypes { + if i%5 == 0 { + sb.WriteString("\n") + } + sb.WriteString(fmt.Sprintf("'%s',", r)) + } + return sb.String() +} + +func validReportName(cloud string, name string) error { + var validReportNames []string + definitions, err := cli.LwApi.V2.ReportDefinitions.List() + if err != nil { + return errors.Wrap(err, "unable to list report definitions") + } + + for _, d := range definitions.Data { + if d.SubReportType == cloud { + validReportNames = append(validReportNames, d.ReportName) + } + } + + if array.ContainsStr(validReportNames, name) { + return nil + } + + return errors.Errorf( + "'%s' is not a valid report name.\n"+ + "Run 'lacework report-definition list --subtype %s' for a list of valid report names", + name, cloud, + ) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_aws.go new file mode 100644 index 000000000..d9181a96c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_aws.go @@ -0,0 +1,761 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/lwseverity" +) + +var ( + compAwsCmdState = struct { + Type string + ReportName string + }{ReportName: api.ComplianceReportDefaultAws} + + // complianceAwsListAccountsCmd represents the list-accounts inside the aws command + complianceAwsListAccountsCmd = &cobra.Command{ + Use: "list-accounts", + Aliases: []string{"list", "ls"}, + Short: "List all AWS accounts configured", + Long: `List all AWS accounts configured in your account.`, + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + cli.StartProgress("Fetching list of configured AWS accounts...") + awsAccounts, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AwsCfgCloudAccount) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get aws compliance integrations") + } + + return cliListAwsAccounts(awsAccounts) + }, + } + + // complianceAwsGetReportCmd represents the get-report sub-command inside the aws command + complianceAwsGetReportCmd = &cobra.Command{ + Use: "get-report [recommendation_id]", + Aliases: []string{"get", "show"}, + PreRunE: func(cmd *cobra.Command, args []string) error { + if compCmdState.Csv { + cli.EnableCSVOutput() + } + if len(args) > 1 { + compCmdState.RecommendationID = args[1] + } + + // ensure we cannot have both --type and --report_name flags + if cmd.Flags().Changed("type") && cmd.Flags().Changed("report_name") { + return errors.New("'--type' and '--report_name' flags cannot be used together") + } + + // validate report_name + if cmd.Flags().Changed("report_name") { + return validReportName(api.ReportDefinitionSubTypeAws.String(), compAwsCmdState.ReportName) + } + + if cmd.Flags().Changed("type") && !array.ContainsStr(api.AwsReportTypes(), compAwsCmdState.Type) { + return errors.Errorf("supported report types are: %s", strings.Join(api.AwsReportTypes(), ", ")) + } + + return nil + }, + Short: "Get the latest AWS compliance report", + Long: `Get the latest compliance assessment report from the provided AWS account, these +reports run on a regular schedule, typically once a day. The available report formats +are human-readable (default), json and pdf. + +To list all AWS accounts configured in your account: + + lacework compliance aws list-accounts + +To show recommendation details and affected resources for a recommendation id: + + lacework compliance aws get-report [recommendation_id] + +To retrieve a specific report by its report name: + + lacework compliance aws get-report --report_name 'AWS CSA CCM 4.0.5' +`, + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + var ( + // clean the AWS account ID if it was provided + // with an Alias in between parentheses + awsAccountID, _ = splitIDAndAlias(args[0]) + config = api.AwsReportConfig{ + AccountID: awsAccountID, + // Default config is report_name + Parameter: api.ReportFilterName, + Value: compAwsCmdState.ReportName, + } + ) + + // if --type flag is used, set the report config to type + if cmd.Flags().Changed("type") { + reportType, err := api.NewAwsReportType(compAwsCmdState.Type) + if err != nil { + return errors.Errorf("invalid report type %q", compAwsCmdState.Type) + } + + config.Parameter = api.ReportFilterType + config.Value = reportType.String() + } + + if compCmdState.Pdf { + pdfName := fmt.Sprintf( + "%s_Report_%s_%s_%s.pdf", + config.Value, + config.AccountID, + cli.Account, time.Now().Format("20060102150405"), + ) + + cli.StartProgress("Downloading compliance report...") + err := cli.LwApi.V2.Reports.Aws.DownloadPDF(pdfName, config) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get aws pdf compliance report") + } + + cli.OutputHuman("The AWS compliance report was downloaded at '%s'\n", pdfName) + return nil + } + + if compCmdState.Severity != "" { + if !lwseverity.IsValid(compCmdState.Severity) { + return errors.Errorf("the severity %s is not valid, use one of %s", + compCmdState.Severity, lwseverity.ValidSeverities.String(), + ) + } + } + if compCmdState.Status != "" { + if !array.ContainsStr(api.ValidComplianceStatus, compCmdState.Status) { + return errors.Errorf("the status %s is not valid, use one of %s", + compCmdState.Status, strings.Join(api.ValidComplianceStatus, ", "), + ) + } + } + + var ( + report api.AwsReport + cacheKey = fmt.Sprintf("compliance/aws/v2/%s/%s", config.AccountID, config.Value) + ) + expired := cli.ReadCachedAsset(cacheKey, &report) + if expired { + cli.StartProgress("Getting compliance report...") + response, err := cli.LwApi.V2.Reports.Aws.Get(config) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get aws compliance report") + } + + if len(response.Data) == 0 { + return errors.New("no data found in the report") + } + + report = response.Data[0] + + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), report) + } + + filteredOutput := "" + + if complianceFiltersEnabled() { + report.Recommendations, filteredOutput = filterRecommendations(report.Recommendations) + cli.Log.Infow("recommendations", + "count", len(report.Recommendations), + "filtered", len(filteredOutput), + ) + } + + if cli.JSONOutput() && compCmdState.RecommendationID == "" { + return cli.OutputJSON(report) + } + + if cli.CSVOutput() { + recommendations := complianceCSVReportRecommendationsTable( + &complianceCSVReportDetails{ + AccountName: report.AccountID, + AccountID: report.AccountID, + ReportType: report.ReportType, + ReportTime: report.ReportTime, + Recommendations: report.Recommendations, + }, + ) + cli.Log.Infow("csv recommendations", "count", len(recommendations)) + return cli.OutputCSV( + []string{"Report_Type", "Report_Time", "Account", + "Section", "ID", "Recommendation", "Status", + "Severity", "Resource", "Region", "Reason"}, + recommendations, + ) + } + + // If RecommendationID is provided, output resources matching that id + if compCmdState.RecommendationID != "" { + return outputResourcesByRecommendationID(report) + } + + recommendations := complianceReportRecommendationsTable(report.Recommendations) + cli.OutputHuman("\n") + cli.OutputHuman( + buildComplianceReportTable( + complianceAwsReportDetailsTable(&report), + complianceReportSummaryTable(report.Summary), + recommendations, + filteredOutput, + ), + ) + return nil + }, + } + + // complianceAwsDisableReportCmd represents the disable-report sub-command inside the aws command + // experimental feature + complianceAwsDisableReportCmd = &cobra.Command{ + Use: "disable-report ", + Hidden: true, + Aliases: []string{"disable"}, + Short: "Disable all recommendations for a given report type", + Long: `Disable all recommendations for a given report type. +Supported report types are CIS_1_1 + +To show the current status of recommendations in a report run: + lacework compliance aws status CIS_1_1 + +To disable all recommendations for CIS_1_1 report run: + lacework compliance aws disable CIS_1_1 +`, + PreRunE: func(_ *cobra.Command, args []string) error { + switch args[0] { + case "CIS", "CIS_1_1", "AWS_CIS_S3": + args[0] = "CIS_1_1" + return nil + default: + return errors.New("CIS_1_1 is the only supported report type") + } + }, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + // prompt for changes + proceed, err := complianceAwsDisableReportDisplayChanges() + if err != nil { + return errors.Wrap(err, "unable to confirm disable") + } + if !proceed { + return nil + } + + schema, err := fetchCachedAwsComplianceReportSchema(args[0]) + if err != nil { + return errors.Wrap(err, "unable to get aws compliance report schema") + } + + // set state of all recommendations in this report to disabled + patchReq := api.NewRecommendationV2State(schema, false) + cli.StartProgress("disabling recommendations...") + response, err := cli.LwApi.V2.Recommendations.Aws.Patch(patchReq) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to patch aws recommendations") + } + + var cacheKey = fmt.Sprintf("compliance/aws/schema/%s", "CIS_1_1") + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) + cli.OutputHuman("All recommendations for report %s have been disabled\n", args[0]) + return nil + }, + } + + // complianceAwsEnableReportCmd represents the enable-report sub-command inside the aws command + // experimental feature + complianceAwsEnableReportCmd = &cobra.Command{ + Use: "enable-report ", + Hidden: true, + Aliases: []string{"enable"}, + Short: "Enable all recommendations for a given report type", + Long: `Enable all recommendations for a given report type. +Supported report types are CIS_1_1 + +To show the current status of recommendations in a report run: + lacework compliance aws status CIS_1_1 + +To enable all recommendations for CIS_1_1 report run: + lacework compliance aws enable CIS_1_1 +`, + PreRunE: func(_ *cobra.Command, args []string) error { + switch args[0] { + case "CIS", "CIS_1_1", "AWS_CIS_S3": + args[0] = "CIS_1_1" + return nil + default: + return errors.New("CIS_1_1 is the only supported report type") + } + }, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + + schema, err := fetchCachedAwsComplianceReportSchema(args[0]) + if err != nil { + return errors.Wrap(err, "unable to get aws compliance report schema") + } + + // set state of all recommendations in this report to enabled + patchReq := api.NewRecommendationV2State(schema, true) + cli.StartProgress("enabling recommendations...") + response, err := cli.LwApi.V2.Recommendations.Aws.Patch(patchReq) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to patch aws recommendations") + } + + var cacheKey = fmt.Sprintf("compliance/aws/schema/%s", args[0]) + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) + cli.OutputHuman("All recommendations for report %s have been enabled\n", args[0]) + return nil + }, + } + + // complianceAwsReportStatusCmd represents the report-status sub-command inside the aws command + // experimental feature + complianceAwsReportStatusCmd = &cobra.Command{ + Use: "report-status ", + Hidden: true, + Aliases: []string{"status"}, + Short: "Show the status of recommendations for a given report type", + Long: `Show the status of recommendations for a given report type. +Supported report types are CIS_1_1 + +To show the current status of recommendations in a report run: + lacework compliance aws status CIS_1_1 + +The output from status with the --json flag can be used in the body of PATCH api/v1/external/recommendations/aws + lacework compliance aws status CIS_1_1 --json +`, + PreRunE: func(_ *cobra.Command, args []string) error { + switch args[0] { + case "CIS", "CIS_1_1", "AWS_CIS_S3": + args[0] = "CIS_1_1" + return nil + default: + return errors.New("CIS_1_1 is the only supported report type") + } + }, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var rows [][]string + report, err := fetchCachedAwsComplianceReportSchema(args[0]) + if err != nil { + return errors.Wrap(err, "unable to get Aws compliance report schema") + } + + if cli.JSONOutput() { + return cli.OutputJSON(api.NewRecommendationV2(report)) + } + + for _, r := range report { + rows = append(rows, []string{r.ID, strconv.FormatBool(r.State)}) + } + + cli.OutputHuman(renderOneLineCustomTable(args[0], + renderCustomTable([]string{}, rows, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + )) + return nil + }, + } + + // complianceAwsSearchCmd represents the search inside the aws command + complianceAwsSearchCmd = &cobra.Command{ + Use: "search ", + Short: "Search for all known violations of a given resource arn", + Long: `Search for all known violations of a given resource arn.`, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress(fmt.Sprintf("Searching accounts for resource '%s'...", args[0])) + var ( + now = time.Now().UTC() + before = now.AddDate(0, 0, -7) // last 7 days + awsInventorySearchResponse api.InventoryAwsResponse + filter = api.InventorySearch{ + SearchFilter: api.SearchFilter{ + Filters: []api.Filter{{ + Expression: "eq", + Field: "urn", + Value: args[0], + }}, + TimeFilter: &api.TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + }, + Dataset: api.AwsInventoryDataset, + Csp: api.AwsInventoryType, + } + ) + err := api.WindowedSearchFirst( + cli.LwApi.V2.Inventory.Search, api.V2ApiMaxSearchWindowDays, + api.V2ApiMaxSearchHistoryDays, &awsInventorySearchResponse, &filter, + ) + cli.StopProgress() + + if len(awsInventorySearchResponse.Data) == 0 { + cli.OutputHuman("Resource '%s' not found.\n\nTo learn how to configure Lacework with AWS visit "+ + "https://docs.lacework.com/onboarding/category/integrate-lacework-with-aws \n", args[0]) + return nil + } + cli.StopProgress() + if err != nil { + return err + } + + cli.StartProgress(fmt.Sprintf("Searching for compliance violations for '%s'...", args[0])) + var ( + awsComplianceEvaluationSearchResponse api.ComplianceEvaluationAwsResponse + complianceFilter = api.ComplianceEvaluationSearch{ + SearchFilter: api.SearchFilter{ + Filters: []api.Filter{{ + Expression: "eq", + Field: "resource", + Value: args[0], + }}, + TimeFilter: &api.TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + }, + Dataset: api.AwsComplianceEvaluationDataset, + } + ) + + err = api.WindowedSearchFirst( + cli.LwApi.V2.ComplianceEvaluations.Search, api.V2ApiMaxSearchWindowDays, + api.V2ApiMaxSearchHistoryDays, &awsComplianceEvaluationSearchResponse, &complianceFilter, + ) + cli.StopProgress() + if err != nil { + return err + } + + var recommendationIDs []string + var uniqueRecommendations []api.ComplianceEvaluationAws + + for _, recommend := range awsComplianceEvaluationSearchResponse.Data { + if !array.ContainsStr(recommendationIDs, recommend.Id) { + recommendationIDs = append(recommendationIDs, recommend.Id) + uniqueRecommendations = append(uniqueRecommendations, recommend) + } + } + + if len(uniqueRecommendations) == 0 { + cli.OutputHuman("No violations found. Time for %s\n", randomEmoji()) + return nil + } + + // output table + var out [][]string + for _, recommend := range uniqueRecommendations { + out = append(out, []string{ + recommend.Id, + recommend.Account.AccountId, + recommend.Reason, + recommend.Severity, + recommend.Status, + }) + } + + cli.OutputHuman( + renderSimpleTable( + []string{"RECOMMENDATION ID", "ACCOUNT ID", "REASON", "SEVERITY", "STATUS"}, + out, + ), + ) + + return nil + }, + } + + // complianceAwsScanCmd represents the inventory scan inside the aws command + complianceAwsScanCmd = &cobra.Command{ + Use: "scan", + Short: "Scan triggers a new resource inventory scan", + Long: `Scan triggers a new resource inventory scan.`, + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress("Triggering Aws inventory scan") + response, err := cli.LwApi.V2.Inventory.Scan(api.AwsInventoryType) + cli.StopProgress() + + if err != nil { + return err + } + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + + cli.OutputHuman(renderSimpleTable([]string{}, [][]string{ + {"STATUS", response.Data.Status}, + {"DETAILS", response.Data.Details}, + })) + return nil + }, + } +) + +func init() { + // add sub-commands to the aws command + complianceAwsCmd.AddCommand(complianceAwsGetReportCmd) + complianceAwsCmd.AddCommand(complianceAwsListAccountsCmd) + complianceAwsCmd.AddCommand(complianceAwsSearchCmd) + complianceAwsCmd.AddCommand(complianceAwsScanCmd) + + // Experimental Commands + complianceAwsCmd.AddCommand(complianceAwsReportStatusCmd) + complianceAwsCmd.AddCommand(complianceAwsDisableReportCmd) + complianceAwsCmd.AddCommand(complianceAwsEnableReportCmd) + + complianceAwsGetReportCmd.Flags().BoolVar(&compCmdState.Details, "details", false, + "increase details about the compliance report", + ) + + complianceAwsGetReportCmd.Flags().BoolVar(&compCmdState.Pdf, "pdf", false, + "download report in PDF format", + ) + + complianceAwsGetReportCmd.Flags().BoolVar(&compCmdState.Csv, "csv", false, + "output report in CSV format", + ) + // AWS report types: AWS_NIST_CSF, AWS_NIST_800-53_rev5, AWS_HIPAA, NIST_800-53_Rev4, LW_AWS_SEC_ADD_1_0, + //AWS_SOC_Rev2, AWS_PCI_DSS_3.2.1, AWS_CIS_S3, ISO_2700, SOC, AWS_CSA_CCM_4_0_5, PCI, AWS_Cyber_Essentials_2_2, + //AWS_ISO_27001:2013, AWS_CIS_14, AWS_CMMC_1.02, HIPAA, AWS_SOC_2, AWS_CIS_1_4_ISO_IEC_27002_2022, NIST_800-171_Rev2, + //AWS_NIST_800-171_rev2 + complianceAwsGetReportCmd.Flags().StringVar(&compAwsCmdState.Type, "type", "AWS_CIS_14", + fmt.Sprintf(`report type to display, run 'lacework report-definitions list' for more information. +valid types:%s`, prettyPrintReportTypes(api.AwsReportTypes()))) + + // mark report type flag as deprecated + errcheckWARN(complianceAwsGetReportCmd.Flags().MarkDeprecated("type", "use --report_name flag instead")) + + // Run 'lacework report-definition --subtype AWS' for a full list of AWS report names + complianceAwsGetReportCmd.Flags().StringVar(&compAwsCmdState.ReportName, "report_name", + api.ComplianceReportDefaultAws, + "report name to display, run 'lacework report-definitions list' for more information.") + + complianceAwsGetReportCmd.Flags().StringSliceVar(&compCmdState.Category, "category", []string{}, + "filter report details by category (identity-and-access-management, s3, logging...)", + ) + + complianceAwsGetReportCmd.Flags().StringSliceVar(&compCmdState.Service, "service", []string{}, + "filter report details by service (aws:s3, aws:iam, aws:cloudtrail, ...)", + ) + + complianceAwsGetReportCmd.Flags().StringVar(&compCmdState.Severity, "severity", "", + fmt.Sprintf("filter report details by severity threshold (%s)", + lwseverity.ValidSeverities.String()), + ) + + complianceAwsGetReportCmd.Flags().StringVar(&compCmdState.Status, "status", "", + fmt.Sprintf("filter report details by status (%s)", + strings.Join(api.ValidComplianceStatus, ", ")), + ) +} + +// Simple helper to prompt for approval after disable request +func complianceAwsDisableReportCmdPrompt() (int, error) { + message := `WARNING! +Disabling all recommendations for CIS_1_1 will disable the following reports and its corresponding compliance alerts: +AWS CIS Benchmark and S3 Report +AWS HIPAA Report +AWS ISO 27001:2013 Report +AWS NIST 800-171 Report +AWS NIST 800-53 Report +AWS PCI DSS Report +AWS SOC 2 Report +AWS SOC 2 Report Rev2 + +Would you like to proceed? +` + options := []string{ + "Proceed with disable", + "Quit", + } + + var answer int + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: message, + Options: options, + }, + Response: &answer, + }) + + return answer, err +} + +func complianceAwsDisableReportDisplayChanges() (bool, error) { + answer, err := complianceAwsDisableReportCmdPrompt() + if err != nil { + return false, err + } + return answer == 0, nil +} + +func complianceAwsReportDetailsTable(report *api.AwsReport) [][]string { + return [][]string{ + {"Report Type", report.ReportType}, + {"Report Title", report.ReportTitle}, + {"Account ID", report.AccountID}, + {"Account Alias", report.AccountAlias}, + {"Report Time", report.ReportTime.UTC().Format(time.RFC3339)}, + } +} + +type awsAccount struct { + AccountID string `json:"account_id"` + Status string `json:"status"` +} + +func cliListAwsAccounts(awsIntegrations api.CloudAccountsResponse) error { + awsAccounts := make([]awsAccount, 0) + errCount := 0 + jsonOut := struct { + Accounts []awsAccount `json:"aws_accounts"` + }{Accounts: awsAccounts} + + if len(awsIntegrations.Data) == 0 { + if cli.JSONOutput() { + return cli.OutputJSON(jsonOut) + } + + msg := `There are no AWS accounts configured in your account. + +Get started by integrating your AWS accounts to analyze configuration compliance using the command: + + lacework cloud-account aws create + +If you prefer to configure the integration via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Settings > Integrations > Cloud Accounts. +` + cli.OutputHuman(msg, cli.Account) + return nil + } + + for _, i := range awsIntegrations.Data { + var ( + account string + accountData api.AwsCfgData + ) + + awsJson, err := json.Marshal(i.Data) + if err != nil { + continue + } + err = json.Unmarshal(awsJson, &accountData) + if err != nil { + continue + } + + if accountData.AwsAccountID == "" { + errCount++ + cli.Log.Debugf(fmt.Sprintf("unable to find account id for cloud account %s\n", i.IntgGuid)) + continue + } + account = accountData.AwsAccountID + + if containsDuplicateAccountID(awsAccounts, account) { + cli.Log.Warnw("duplicate aws account", "integration_guid", i.IntgGuid, "account", account) + continue + } + awsAccounts = append(awsAccounts, awsAccount{ + AccountID: account, + Status: i.Status(), + }) + } + + if cli.JSONOutput() { + jsonOut.Accounts = awsAccounts + return cli.OutputJSON(jsonOut) + } + + var rows [][]string + for _, acc := range awsAccounts { + rows = append(rows, []string{acc.AccountID, acc.Status}) + } + + cli.OutputHuman(renderSimpleTable([]string{"AWS Account", "Status"}, rows)) + if errCount > 0 { + cli.OutputHuman(fmt.Sprintf("\n unable to find Aws Account ID's for %d integration(s)\n", errCount)) + } + return nil +} + +func containsDuplicateAccountID(awsAccount []awsAccount, accountID string) bool { + for _, value := range awsAccount { + if accountID == value.AccountID { + return true + } + } + return false +} + +func fetchCachedAwsComplianceReportSchema(reportType string) (response []api.RecV2, err error) { + var cacheKey = fmt.Sprintf("compliance/aws/schema/%s", reportType) + expired := cli.ReadCachedAsset(cacheKey, &response) + if expired { + cli.StartProgress("Fetching compliance report schema...") + response, err = cli.LwApi.V2.Recommendations.Aws.GetReport(reportType) + cli.StopProgress() + if err != nil { + return nil, errors.Wrap(err, "unable to get aws compliance report schema") + } + if len(response) == 0 { + return nil, errors.New("no data found in the report") + } + + // write previous state to cache, allowing for revert to previous state + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response) + } + return +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_azure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_azure.go new file mode 100644 index 000000000..c2fd18dc6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_azure.go @@ -0,0 +1,765 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "sort" + "strconv" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/lwseverity" +) + +var ( + compAzCmdState = struct { + Type string + ReportName string + }{ReportName: api.ComplianceReportDefaultAzure} + + // complianceAzureListSubsCmd represents the list-subscriptions sub-command inside the azure command + complianceAzureListSubsCmd = &cobra.Command{ + Use: "list-subscriptions", + Aliases: []string{"list-subs"}, + Short: "List subscriptions ``", + Long: `List all Azure subscriptions for Tenant. + +Use the following command to list all Azure Tenants configured in your account: + + lacework compliance az list`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + var ( + response, err = cli.LwApi.V2.Configs.Azure.ListSubscriptions(args[0]) + cliCompAzureSubscriptions []cliComplianceAzureInfo + ) + if err != nil { + return errors.Wrap(err, "unable to list azure subscriptions") + } + + if len(response.Data) == 0 { + cli.OutputHuman("There are no azure subscriptions found for tenant %s\n", args[0]) + return nil + } + + for _, az := range response.Data { + cliCompAzureSubscriptions = append( + cliCompAzureSubscriptions, + splitAzureSubscriptionsApiResponse(az), + ) + } + + if cli.JSONOutput() { + return cli.OutputJSON(cliCompAzureSubscriptions) + } + + rows := [][]string{} + for _, subscriptionList := range cliCompAzureSubscriptions { + for _, subscription := range subscriptionList.Subscriptions { + rows = append(rows, []string{subscription.ID, subscription.Alias}) + } + } + + cli.OutputHuman(renderSimpleTable( + []string{"Subscription ID", "Subscription Alias"}, rows), + ) + return nil + }, + } + + // complianceAzureListTenantsCmd represents the list-tenants sub-command inside the azure command + complianceAzureListTenantsCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"list-tenants", "ls"}, + Short: "List Azure tenants and subscriptions", + Long: `List all Azure tenants and subscriptions configured in your account.`, + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + cli.StartProgress("Fetching list of configured Azure tenants...") + response, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AzureCfgCloudAccount) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get azure integrations") + } + + if len(response.Data) == 0 { + cli.OutputHuman("There are no azure cloud accounts configured in your account\n") + return nil + } + + return cliListTenantsAndSubscriptions(response) + }, + } + + // complianceAzureGetReportCmd represents the get-report sub-command inside the azure command + complianceAzureGetReportCmd = &cobra.Command{ + Use: "get-report ", + Aliases: []string{"get", "show"}, + PreRunE: func(cmd *cobra.Command, args []string) error { + if compCmdState.Csv { + cli.EnableCSVOutput() + } + + if len(args) > 2 { + compCmdState.RecommendationID = args[2] + } + + // ensure we cannot have both --type and --report_name flags + if cmd.Flags().Changed("type") && cmd.Flags().Changed("report_name") { + return errors.New("'--type' and '--report_name' flags cannot be used together") + } + + // validate report_name + if cmd.Flags().Changed("report_name") { + return validReportName(api.ReportDefinitionSubTypeAzure.String(), compAzCmdState.ReportName) + } + + if cmd.Flags().Changed("type") && !array.ContainsStr(api.AzureReportTypes(), compAzCmdState.Type) { + return errors.Errorf("supported report types are: %s", strings.Join(api.AzureReportTypes(), ", ")) + } + return nil + }, + Short: "Get the latest Azure compliance report", + Long: `Get the latest Azure compliance assessment report, these reports run on a regular schedule, +typically once a day. The available report formats are human-readable (default), json and pdf. + +To list all Azure tenants and subscriptions configured in your account: + + lacework compliance azure list + +To show recommendation details and affected resources for a recommendation id: + + lacework compliance azure get-report [recommendation_id] + +To retrieve a specific report by its report name: + + lacework compliance azure get-report --report_name 'Azure CIS 1.3.1 Report' +`, + Args: cobra.RangeArgs(2, 3), + RunE: func(cmd *cobra.Command, args []string) error { + var ( + // clean tenantID and subscriptionID if they were provided + // with an Alias in between parentheses + tenantID, _ = splitIDAndAlias(args[0]) + subscriptionID, _ = splitIDAndAlias(args[1]) + config = api.AzureReportConfig{ + TenantID: tenantID, + SubscriptionID: subscriptionID, + // Default config is report_name + Value: compAzCmdState.ReportName, + Parameter: api.ReportFilterName, + } + ) + + // if --type flag is used, set the report config to type + if cmd.Flags().Changed("type") { + reportType, err := api.NewAzureReportType(compAzCmdState.Type) + if err != nil { + return errors.Errorf("invalid report type %q", compAzCmdState.Type) + } + config.Parameter = api.ReportFilterType + config.Value = reportType.String() + } + + if compCmdState.Pdf { + pdfName := fmt.Sprintf( + "%s_Report_%s_%s_%s_%s.pdf", + config.Value, + config.TenantID, + config.SubscriptionID, + cli.Account, time.Now().Format("20060102150405"), + ) + + cli.StartProgress("Downloading compliance report...") + err := cli.LwApi.V2.Reports.Azure.DownloadPDF(pdfName, config) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get azure pdf compliance report") + } + + cli.OutputHuman("The Azure compliance report was downloaded at '%s'\n", pdfName) + return nil + } + + if compCmdState.Severity != "" { + if !lwseverity.IsValid(compCmdState.Severity) { + return errors.Errorf("the severity %s is not valid, use one of %s", + compCmdState.Severity, lwseverity.ValidSeverities.String(), + ) + } + } + if compCmdState.Status != "" { + if !array.ContainsStr(api.ValidComplianceStatus, compCmdState.Status) { + return errors.Errorf("the status %s is not valid, use one of %s", + compCmdState.Status, strings.Join(api.ValidComplianceStatus, ", "), + ) + } + } + + var ( + report api.AzureReport + cacheKey = fmt.Sprintf("compliance/azure/v2/%s/%s/%s", + config.TenantID, config.SubscriptionID, config.Value) + ) + expired := cli.ReadCachedAsset(cacheKey, &report) + if expired { + cli.StartProgress("Getting compliance report...") + response, err := cli.LwApi.V2.Reports.Azure.Get(config) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get azure compliance report") + } + + if len(response.Data) == 0 { + return errors.New("no data found in the report") + } + + report = response.Data[0] + + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), report) + } + + filteredOutput := "" + + if complianceFiltersEnabled() { + report.Recommendations, filteredOutput = filterRecommendations(report.Recommendations) + } + + if cli.JSONOutput() && compCmdState.RecommendationID == "" { + return cli.OutputJSON(report) + } + + if cli.CSVOutput() { + recommendations := complianceCSVReportRecommendationsTable( + &complianceCSVReportDetails{ + AccountName: report.SubscriptionName, + AccountID: report.SubscriptionID, + TenantName: report.TenantName, + TenantID: report.TenantID, + ReportType: report.ReportType, + ReportTime: report.ReportTime, + Recommendations: report.Recommendations, + }, + ) + + return cli.OutputCSV( + []string{"Report_Type", "Report_Time", "Tenant", + "Subscription", "Section", "ID", "Recommendation", + "Status", "Severity", "Resource", "Region", "Reason"}, + recommendations, + ) + } + + // If RecommendationID is provided, output resources matching that id + if compCmdState.RecommendationID != "" { + return outputResourcesByRecommendationID(report) + } + + recommendations := complianceReportRecommendationsTable(report.Recommendations) + cli.OutputHuman("\n") + cli.OutputHuman( + buildComplianceReportTable( + complianceAzureReportDetailsTable(&report), + complianceReportSummaryTable(report.Summary), + recommendations, + filteredOutput, + ), + ) + return nil + }, + } + + // complianceAzureDisableReportCmd represents the disable-report sub-command inside the azure command + // experimental feature + complianceAzureDisableReportCmd = &cobra.Command{ + Use: "disable-report ", + Hidden: true, + Aliases: []string{"disable"}, + Short: "Disable all recommendations for a given report type", + Long: `Disable all recommendations for a given report type. +Supported report types are: CIS_1_0, CIS_1_3_1 + +To show the current status of recommendations in a report run: + lacework compliance azure status CIS_1_3_1 + +To disable all recommendations for CIS_1_3_1 report run: + lacework compliance azure disable CIS_1_3_1 +`, + PreRunE: func(_ *cobra.Command, args []string) error { + switch args[0] { + case "CIS", "CIS_1_0", "AZURE_CIS": + args[0] = "CIS_1_0" + return nil + case "CIS_1_3_1", "AZURE_CIS_131": + args[0] = "CIS_1_3_1" + return nil + default: + return errors.New("supported report types are: CIS_1_0, CIS_1_3_1") + } + }, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + // prompt for changes + proceed, err := complianceAzureDisableReportDisplayChanges(args[0]) + if err != nil { + return errors.Wrap(err, "unable to confirm disable") + } + if !proceed { + return nil + } + + schema, err := fetchCachedAzureComplianceReportSchema(args[0]) + if err != nil { + return errors.Wrap(err, "unable to fetch azure compliance report schema") + } + + // set state of all recommendations in this report to disabled + patchReq := api.NewRecommendationV2State(schema, false) + cli.StartProgress("disabling recommendations...") + response, err := cli.LwApi.V2.Recommendations.Azure.Patch(patchReq) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to patch azure recommendations") + } + + var cacheKey = fmt.Sprintf("compliance/azure/schema/%s", args[0]) + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) + cli.OutputHuman("All recommendations for report %s have been disabled\n", args[0]) + return nil + }, + } + + // complianceAzureEnableReportCmd represents the enable-report sub-command inside the azure command + // experimental feature + complianceAzureEnableReportCmd = &cobra.Command{ + Use: "enable-report ", + Hidden: true, + Aliases: []string{"enable"}, + Short: "Enable all recommendations for a given report type", + Long: `Enable all recommendations for a given report type. +Supported report types are: CIS_1_0, CIS_1_3_1 + +To show the current status of recommendations in a report run: + lacework compliance azure status CIS_1_3_1 + +To enable all recommendations for CIS_1_3_1 report run: + lacework compliance azure enable CIS_1_3_1 +`, + PreRunE: func(_ *cobra.Command, args []string) error { + switch args[0] { + case "CIS", "CIS_1_0", "AZURE_CIS": + args[0] = "CIS_1_0" + return nil + case "CIS_1_3_1", "AZURE_CIS_131": + args[0] = "CIS_1_3_1" + return nil + default: + return errors.New("supported report types are: CIS_1_0, CIS_1_3_1") + } + }, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + + schema, err := fetchCachedAzureComplianceReportSchema(args[0]) + if err != nil { + return errors.Wrap(err, "unable to fetch azure compliance report schema") + } + + // set state of all recommendations in this report to enabled + patchReq := api.NewRecommendationV2State(schema, true) + cli.StartProgress("enabling recommendations...") + response, err := cli.LwApi.V2.Recommendations.Azure.Patch(patchReq) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to patch azure recommendations") + } + + var cacheKey = fmt.Sprintf("compliance/azure/schema/%s", args[0]) + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) + cli.OutputHuman("All recommendations for report %s have been enabled\n", args[0]) + return nil + }, + } + + // complianceAzureReportStatusCmd represents the report-status sub-command inside the azure command + // experimental feature + complianceAzureReportStatusCmd = &cobra.Command{ + Use: "report-status ", + Hidden: true, + Aliases: []string{"status"}, + Short: "Show the status of recommendations for a given report type", + Long: `Show the status of recommendations for a given report type. +Supported report types are: CIS_1_0, CIS_1_3_1 + +To show the current status of recommendations in a report run: + lacework compliance azure status CIS_1_3_1 + +The output from status with the --json flag can be used in the body of PATCH api/v1/external/recommendations/azure + lacework compliance azure status CIS_1_3_1 --json +`, + PreRunE: func(_ *cobra.Command, args []string) error { + switch args[0] { + case "CIS", "CIS_1_0", "AZURE_CIS": + args[0] = "CIS_1_0" + return nil + case "CIS_1_3_1", "AZURE_CIS_131": + args[0] = "CIS_1_3_1" + return nil + default: + return errors.New("supported report types are: CIS_1_0, CIS_1_3_1") + } + }, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var rows [][]string + report, err := fetchCachedAzureComplianceReportSchema(args[0]) + if err != nil { + return errors.Wrap(err, "unable to fetch azure compliance report schema") + } + + if cli.JSONOutput() { + return cli.OutputJSON(api.NewRecommendationV2(report)) + } + + for _, r := range report { + rows = append(rows, []string{r.ID, strconv.FormatBool(r.State)}) + } + + cli.OutputHuman(renderOneLineCustomTable(args[0], + renderCustomTable([]string{}, rows, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + )) + return nil + }, + } + // complianceAzureScanCmd represents the inventory scan inside the azure command + complianceAzureScanCmd = &cobra.Command{ + Use: "scan", + Short: "Scan triggers a new resource inventory scan", + Long: `Scan triggers a new resource inventory scan.`, + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress("Triggering Azure inventory scan") + response, err := cli.LwApi.V2.Inventory.Scan(api.AzureInventoryType) + cli.StopProgress() + + if err != nil { + return err + } + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + + cli.OutputHuman(renderSimpleTable([]string{}, [][]string{ + {"STATUS", response.Data.Status}, + {"DETAILS", response.Data.Details}, + })) + return nil + }, + } +) + +func init() { + // add sub-commands to the azure command + complianceAzureCmd.AddCommand(complianceAzureListSubsCmd) + complianceAzureCmd.AddCommand(complianceAzureListTenantsCmd) + complianceAzureCmd.AddCommand(complianceAzureGetReportCmd) + complianceAzureCmd.AddCommand(complianceAzureScanCmd) + + // Experimental Commands + complianceAzureCmd.AddCommand(complianceAzureReportStatusCmd) + complianceAzureCmd.AddCommand(complianceAzureDisableReportCmd) + complianceAzureCmd.AddCommand(complianceAzureEnableReportCmd) + + complianceAzureGetReportCmd.Flags().BoolVar(&compCmdState.Details, "details", false, + "increase details about the compliance report", + ) + complianceAzureGetReportCmd.Flags().BoolVar(&compCmdState.Pdf, "pdf", false, + "download report in PDF format", + ) + + // Output the report in CSV format + complianceAzureGetReportCmd.Flags().BoolVar(&compCmdState.Csv, "csv", false, + "output report in CSV format", + ) + + // Azure report types: AZURE_CIS_131, AZURE_NIST_800_171_REV2, AZURE_NIST_800_53_REV5, AZURE_NIST_CSF, + //AZURE_PCI, AZURE_SOC_Rev2, AZURE_ISO_27001, AZURE_SOC, AZURE_HIPAA, AZURE_CIS, AZURE_PCI_Rev2 + complianceAzureGetReportCmd.Flags().StringVar(&compAzCmdState.Type, "type", "AZURE_CIS_131", + fmt.Sprintf(`report type to display, run 'lacework report-definitions list' for more information. +valid types:%s`, prettyPrintReportTypes(api.AzureReportTypes())), + ) + + // mark report type flag as deprecated + errcheckWARN(complianceAzureGetReportCmd.Flags().MarkDeprecated("type", "use --report_name flag instead")) + + // Run 'lacework report-definition --subtype Azure' for a full list of Azure report names + complianceAzureGetReportCmd.Flags().StringVar(&compAzCmdState.ReportName, "report_name", + api.ComplianceReportDefaultAzure, + "report name to display, run 'lacework report-definitions list' for more information.") + + complianceAzureGetReportCmd.Flags().StringSliceVar(&compCmdState.Category, "category", []string{}, + "filter report details by category (networking, storage, ...)", + ) + + complianceAzureGetReportCmd.Flags().StringSliceVar(&compCmdState.Service, "service", []string{}, + "filter report details by service (azure:ms:storage, azure:ms:sql, azure:ms:network, ...)", + ) + + complianceAzureGetReportCmd.Flags().StringVar(&compCmdState.Severity, "severity", "", + fmt.Sprintf("filter report details by severity threshold (%s)", + lwseverity.ValidSeverities.String()), + ) + + complianceAzureGetReportCmd.Flags().StringVar(&compCmdState.Status, "status", "", + fmt.Sprintf("filter report details by status (%s)", + strings.Join(api.ValidComplianceStatus, ", ")), + ) +} + +// Simple helper to prompt for approval after disable request +func complianceAzureDisableReportCmdPrompt(arg string) (int, error) { + var message string + switch arg { + case "CIS", "CIS_1_0", "AZURE_CIS": + message = `WARNING! +Disabling all recommendations for CIS_1_0 will disable the following reports and its corresponding compliance alerts: + AZURE CIS Benchmark + PCI Benchmark + SOC 2 Report + + Would you like to proceed? + ` + case "CIS_1_3_1", "AZURE_CIS_131": + message = `WARNING! +Disabling all recommendations for CIS_1_3_1 will disable the following reports and its corresponding compliance alerts: + AZURE CIS Benchmark 1.3.1 + PCI Benchmark Rev2 + SOC 2 Report Rev2 + HIPAA Report + ISO27001 Report (+ couple CIS 1.0 controls) + NIST 800-171 rev2 Report + NIST 800-53 rev5 Report + NIST CSF rev2 Report + + Would you like to proceed? + ` + } + + options := []string{ + "Proceed with disable", + "Quit", + } + + var answer int + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: message, + Options: options, + }, + Response: &answer, + }) + + return answer, err +} + +func complianceAzureDisableReportDisplayChanges(arg string) (bool, error) { + answer, err := complianceAzureDisableReportCmdPrompt(arg) + if err != nil { + return false, err + } + return answer == 0, nil +} + +func complianceAzureReportDetailsTable(report *api.AzureReport) [][]string { + return [][]string{ + {"Report Type", report.ReportType}, + {"Report Title", report.ReportTitle}, + {"Tenant ID", report.TenantID}, + {"Tenant Name", report.TenantName}, + {"Subscription ID", report.SubscriptionID}, + {"Subscription Name", report.SubscriptionName}, + {"Report Time", report.ReportTime.UTC().Format(time.RFC3339)}, + } +} + +func splitAzureSubscriptionsApiResponse(azInfo api.AzureConfigData) cliComplianceAzureInfo { + var ( + tenantID, tenantAlias = splitIDAndAlias(azInfo.Tenant) + cliAzureInfo = cliComplianceAzureInfo{ + Tenant: cliComplianceIDAlias{tenantID, tenantAlias}, + Subscriptions: make([]cliComplianceIDAlias, 0), + } + ) + + for _, subscription := range azInfo.Subscriptions { + id, alias := splitIDAndAlias(subscription) + cliAzureInfo.Subscriptions = append(cliAzureInfo.Subscriptions, cliComplianceIDAlias{id, alias}) + } + + return cliAzureInfo +} + +type cliComplianceAzureInfo struct { + Tenant cliComplianceIDAlias `json:"tenant"` + Subscriptions []cliComplianceIDAlias `json:"subscriptions"` +} + +func cliListTenantsAndSubscriptions(azureIntegrations api.CloudAccountsResponse) error { + jsonOut := struct { + Subscriptions []azureSubscription `json:"azure_subscriptions"` + }{Subscriptions: make([]azureSubscription, 0)} + + if len(azureIntegrations.Data) == 0 { + if cli.JSONOutput() { + return cli.OutputJSON(jsonOut) + } + + msg := `There are no Azure Tenants configured in your account. + +Get started by integrating your Azure Tenants to analyze configuration compliance using the command: + + lacework cloud-account create + +If you prefer to configure the integration via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Settings > Integrations > Cloud Accounts. +` + cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) + return nil + } + + if cli.JSONOutput() { + jsonOut.Subscriptions = extractAzureSubscriptions(azureIntegrations) + return cli.OutputJSON(jsonOut) + } + + var rows [][]string + for _, az := range extractAzureSubscriptions(azureIntegrations) { + rows = append(rows, []string{az.TenantID, az.SubscriptionID, az.Status}) + } + + cli.OutputHuman(renderSimpleTable([]string{"Azure Tenant", "Azure Subscription", "Status"}, rows)) + return nil +} + +type azureSubscription struct { + TenantID string `json:"tenant_id"` + SubscriptionID string `json:"subscription_id"` + Status string `json:"status"` +} + +func extractAzureSubscriptions(response api.CloudAccountsResponse) []azureSubscription { + var azureSubscriptions []azureSubscription + var azureData api.AzureCfgData + if len(response.Data) == 0 { + return azureSubscriptions + } + + for _, az := range response.Data { + azJson, err := json.Marshal(az.Data) + if err != nil { + continue + } + + err = json.Unmarshal(azJson, &azureData) + if err != nil { + continue + } + // fetch the subscription ids from tenant id + azureSubscriptions = append(azureSubscriptions, getAzureSubscriptions(azureData.TenantID, az.Status())...) + } + + sort.Slice(azureSubscriptions, func(i, j int) bool { + switch strings.Compare(azureSubscriptions[i].TenantID, azureSubscriptions[j].TenantID) { + case -1: + return true + case 1: + return false + } + return azureSubscriptions[i].SubscriptionID < azureSubscriptions[j].SubscriptionID + }) + + return azureSubscriptions +} + +func getAzureSubscriptions(tenantID, status string) []azureSubscription { + var subs []azureSubscription + cli.StartProgress(fmt.Sprintf("Fetching subscriptions from tenant (%s)...", tenantID)) + subsResponse, err := cli.LwApi.V2.Configs.Azure.ListSubscriptions(tenantID) + cli.StopProgress() + if err != nil { + cli.Log.Warnw("unable to list azure subscriptions", "tenant_id", tenantID, "error", err.Error()) + return subs + } + for _, subsRes := range subsResponse.Data { + for _, subRes := range subsRes.Subscriptions { + subscriptionID, _ := splitIDAndAlias(subRes) + subs = append(subs, azureSubscription{ + TenantID: tenantID, + SubscriptionID: subscriptionID, + Status: status, + }) + } + } + return subs +} + +func fetchCachedAzureComplianceReportSchema(reportType string) (response []api.RecV2, err error) { + var cacheKey = fmt.Sprintf("compliance/azure/schema/%s", reportType) + + expired := cli.ReadCachedAsset(cacheKey, &response) + if expired { + cli.StartProgress("Fetching compliance report schema...") + response, err = cli.LwApi.V2.Recommendations.Azure.GetReport(reportType) + cli.StopProgress() + if err != nil { + return nil, errors.Wrap(err, "unable to get Azure compliance report schema") + } + + if len(response) == 0 { + return nil, errors.New("no data found in the report") + } + + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response) + } + return +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_gcp.go new file mode 100644 index 000000000..f950045bd --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_gcp.go @@ -0,0 +1,845 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/olekukonko/tablewriter" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/lwseverity" +) + +var ( + compGcpCmdState = struct { + Type string + ReportName string + }{ReportName: api.ComplianceReportDefaultGcp} + + // complianceGcpListCmd represents the list sub-command inside the gcp command + complianceGcpListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List gcp projects and organizations", + Long: `List all GCP projects and organization IDs.`, + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress("Fetching list of configured GCP projects...") + response, err := cli.LwApi.V2.CloudAccounts.ListByType(api.GcpCfgCloudAccount) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to list gcp projects/organizations") + } + + cli.StartProgress("Fetching GCP config data...") + gcpData, err := cli.LwApi.V2.Configs.Gcp.List() + cli.StopProgress() + if err != nil { + return err + } + + return cliListGcpProjectsAndOrgs(response, gcpData) + }, + } + + // complianceGcpListProjCmd represents the list-projects sub-command inside the gcp command + complianceGcpListProjCmd = &cobra.Command{ + Use: "list-projects ", + Aliases: []string{"list-proj"}, + Short: "List projects from an organization", + Long: `List all GCP projects from the provided organization ID. + +Use the following command to list all GCP integrations in your account: + + lacework cloud-account list --type GcpCfg + +Then, select one GUID from an integration and visualize its details using the command: + + lacework cloud-account show +`, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var ( + orgID, _ = splitIDAndAlias(args[0]) + response, err = cli.LwApi.V2.Configs.Gcp.ListProjects(orgID) + ) + if err != nil { + return errors.Wrap(err, "unable to list gcp projects") + } + + if len(response.Data) == 0 { + return errors.New("no data found for the provided organization") + } + + // ALLY-431 Workaround to split the Project ID and Project Alias + // ultimately, we need to fix this in the API response + cliCompGcpProjects := splitGcpProjectsApiResponse(response.Data[0]) + + if cli.JSONOutput() { + return cli.OutputJSON(cliCompGcpProjects) + } + + rows := [][]string{} + for _, project := range cliCompGcpProjects.Projects { + rows = append(rows, []string{project.ID, project.Alias}) + } + cli.OutputHuman(renderSimpleTable([]string{"Project ID", "Project Alias"}, rows)) + return nil + }, + } + + // complianceGcpGetReportCmd represents the get-report sub-command inside the gcp command + complianceGcpGetReportCmd = &cobra.Command{ + Use: "get-report ", + Aliases: []string{"get", "show"}, + PreRunE: func(cmd *cobra.Command, args []string) error { + if compCmdState.Csv { + cli.EnableCSVOutput() + } + + if len(args) > 2 { + compCmdState.RecommendationID = args[2] + } + + // ensure we cannot have both --type and --report_name flags + if cmd.Flags().Changed("type") && cmd.Flags().Changed("report_name") { + return errors.New("'--type' and '--report_name' flags cannot be used together") + } + + // validate report_name + if cmd.Flags().Changed("report_name") { + return validReportName(api.ReportDefinitionSubTypeGcp.String(), compGcpCmdState.ReportName) + } + + if cmd.Flags().Changed("type") && !array.ContainsStr(api.GcpReportTypes(), compGcpCmdState.Type) { + return errors.Errorf("supported report types are: %s", strings.Join(api.GcpReportTypes(), ", ")) + } + + return nil + }, + Short: "Get the latest GCP compliance report", + Long: `Get the latest compliance assessment report, these reports run on a regular schedule, +typically once a day. The available report formats are human-readable (default), json and pdf. + +To list all GCP projects and organizations configured in your account: + + lacework compliance gcp list + +To show recommendation details and affected resources for a recommendation id: + + lacework compliance gcp get-report [recommendation_id] + +To retrieve a specific report by its report name: + + lacework compliance gcp get-report --report_name 'GCP Cybersecurity Maturity' +`, + Args: cobra.RangeArgs(2, 3), + RunE: func(cmd *cobra.Command, args []string) error { + var ( + // clean projectID and orgID if they were provided + // with an Alias in between parentheses + orgID, _ = splitIDAndAlias(args[0]) + projectID, _ = splitIDAndAlias(args[1]) + config = api.GcpReportConfig{ + OrganizationID: orgID, + ProjectID: projectID, + // Default config is report_name + Value: compGcpCmdState.ReportName, + Parameter: api.ReportFilterName, + } + ) + + // if --type flag is used, set the report config to type + if cmd.Flags().Changed("type") { + reportType, err := api.NewGcpReportType(compGcpCmdState.Type) + if err != nil { + return errors.Errorf("invalid report type %q", compGcpCmdState.Type) + } + config.Parameter = api.ReportFilterType + config.Value = reportType.String() + } + + if compCmdState.Pdf { + pdfName := fmt.Sprintf( + "%s_Report_%s_%s_%s_%s.pdf", + config.Value, + config.OrganizationID, + config.ProjectID, + cli.Account, time.Now().Format("20060102150405"), + ) + + cli.StartProgress(" Downloading compliance report...") + err := cli.LwApi.V2.Reports.Gcp.DownloadPDF(pdfName, config) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get gcp pdf compliance report") + } + + cli.OutputHuman("The GCP compliance report was downloaded at '%s'\n", pdfName) + return nil + } + + if compCmdState.Severity != "" { + if !lwseverity.IsValid(compCmdState.Severity) { + return errors.Errorf("the severity %s is not valid, use one of %s", + compCmdState.Severity, lwseverity.ValidSeverities.String(), + ) + } + } + if compCmdState.Status != "" { + if !array.ContainsStr(api.ValidComplianceStatus, compCmdState.Status) { + return errors.Errorf("the status %s is not valid, use one of %s", + compCmdState.Status, strings.Join(api.ValidComplianceStatus, ", "), + ) + } + } + + // diagonals are file separators and therefore we need to clean the organization + // ID if it is "n/a" or we will create two directories "n/a/..." + orgIDForCache := config.OrganizationID + if config.OrganizationID == "n/a" { + orgIDForCache = "not_applicable" + } + + var ( + report api.GcpReport + cacheKey = fmt.Sprintf("compliance/google/v2/%s/%s/%s", + orgIDForCache, config.ProjectID, config.Value) + ) + expired := cli.ReadCachedAsset(cacheKey, &report) + if expired { + cli.StartProgress(" Getting compliance report...") + response, err := cli.LwApi.V2.Reports.Gcp.Get(config) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get gcp compliance report") + } + + if len(response.Data) == 0 { + return errors.New("no data found in the report") + } + + report = response.Data[0] + + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), report) + } + + filteredOutput := "" + + if complianceFiltersEnabled() { + report.Recommendations, filteredOutput = filterRecommendations(report.Recommendations) + } + + if cli.JSONOutput() && compCmdState.RecommendationID == "" { + return cli.OutputJSON(report) + } + + if cli.CSVOutput() { + recommendations := complianceCSVReportRecommendationsTable( + &complianceCSVReportDetails{ + TenantName: report.OrganizationName, + TenantID: report.OrganizationID, + AccountName: report.ProjectName, + AccountID: report.ProjectID, + ReportType: report.ReportType, + ReportTime: report.ReportTime, + Recommendations: report.Recommendations, + }, + ) + + return cli.OutputCSV( + []string{"Report_Type", "Report_Time", "Organization", + "Project", "Section", "ID", "Recommendation", "Status", + "Severity", "Resource", "Region", "Reason"}, + recommendations, + ) + } + + // If RecommendationID is provided, output resources matching that id + if compCmdState.RecommendationID != "" { + return outputResourcesByRecommendationID(report) + } + + recommendations := complianceReportRecommendationsTable(report.Recommendations) + cli.OutputHuman("\n") + cli.OutputHuman( + buildComplianceReportTable( + complianceGcpReportDetailsTable(&report), + complianceReportSummaryTable(report.Summary), + recommendations, + filteredOutput, + ), + ) + return nil + }, + } + + // complianceGcpDisableReportCmd represents the disable-report sub-command inside the gcp command + // experimental feature + complianceGcpDisableReportCmd = &cobra.Command{ + Use: "disable-report ", + Hidden: true, + Aliases: []string{"disable"}, + Short: "Disable all recommendations for a given report type", + Long: `Disable all recommendations for a given report type. +Supported report types are: CIS_1_0, CIS_1_2 + +To show the current status of recommendations in a report run: + lacework compliance gcp status CIS_1_2 + +To disable all recommendations for CIS_1_2 report run: + lacework compliance gcp disable CIS_1_2 +`, + PreRunE: func(_ *cobra.Command, args []string) error { + switch args[0] { + case "CIS", "CIS_1_0", "GCP_CIS": + args[0] = "CIS_1_0" + return nil + case "CIS_1_2", "GCP_CIS12": + args[0] = "CIS_1_2" + return nil + default: + return errors.New("supported report types are: CIS_1_0, CIS_1_2") + } + }, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + // prompt for changes + proceed, err := complianceGcpDisableReportDisplayChanges(args[0]) + if err != nil { + return errors.Wrap(err, "unable to confirm disable") + } + if !proceed { + return nil + } + + schema, err := fetchCachedGcpComplianceReportSchema(args[0]) + if err != nil { + return errors.Wrap(err, "unable to fetch gcp compliance report schema") + } + + // set state of all recommendations in this report to disabled + patchReq := api.NewRecommendationV2State(schema, false) + cli.StartProgress("disabling recommendations...") + response, err := cli.LwApi.V2.Recommendations.Gcp.Patch(patchReq) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to patch gcp recommendations") + } + + var cacheKey = fmt.Sprintf("compliance/gcp/schema/%s", args[0]) + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) + cli.OutputHuman("All recommendations for report %s have been disabled\n", args[0]) + return nil + }, + } + + // complianceGcpEnableReportCmd represents the enable-report sub-command inside the gcp command + // experimental feature + complianceGcpEnableReportCmd = &cobra.Command{ + Use: "enable-report ", + Hidden: true, + Aliases: []string{"enable"}, + Short: "Enable all recommendations for a given report type", + Long: `Enable all recommendations for a given report type. +Supported report types are: CIS_1_0, CIS_1_2 + +To show the current status of recommendations in a report run: + lacework compliance gcp status CIS_1_2 + +To enable all recommendations for CIS_1_2 report run: + lacework compliance gcp enable CIS_1_2 +`, + PreRunE: func(_ *cobra.Command, args []string) error { + switch args[0] { + case "CIS", "CIS_1_0", "GCP_CIS": + args[0] = "CIS_1_0" + return nil + case "CIS_1_2", "GCP_CIS12": + args[0] = "CIS_1_2" + return nil + default: + return errors.New("supported report types are: CIS_1_0, CIS_1_2") + } + }, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + + schema, err := fetchCachedGcpComplianceReportSchema(args[0]) + if err != nil { + return errors.Wrap(err, "unable to fetch gcp compliance report schema") + } + + // set state of all recommendations in this report to enabled + patchReq := api.NewRecommendationV2State(schema, true) + cli.StartProgress("enabling recommendations...") + response, err := cli.LwApi.V2.Recommendations.Gcp.Patch(patchReq) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to patch gcp recommendations") + } + + var cacheKey = fmt.Sprintf("compliance/gcp/schema/%s", args[0]) + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) + cli.OutputHuman("All recommendations for report %s have been enabled\n", args[0]) + return nil + }, + } + + // complianceGcpReportStatusCmd represents the report-status sub-command inside the gcp command + // experimental feature + complianceGcpReportStatusCmd = &cobra.Command{ + Use: "report-status ", + Hidden: true, + Aliases: []string{"status"}, + Short: "Show the status of recommendations for a given report type", + Long: `Show the status of recommendations for a given report type. +Supported report types are: CIS_1_0, CIS_1_2 + +To show the current status of recommendations in a report run: + lacework compliance gcp status CIS_1_2 + +The output from status with the --json flag can be used in the body of PATCH api/v1/external/recommendations/gcp + lacework compliance gcp status CIS_1_2 --json +`, + PreRunE: func(_ *cobra.Command, args []string) error { + switch args[0] { + case "CIS", "CIS_1_0", "GCP_CIS": + args[0] = "CIS_1_0" + return nil + case "CIS_1_2", "GCP_CIS12": + args[0] = "CIS_1_2" + return nil + default: + return errors.New("supported report types are: CIS_1_0, CIS_1_2") + } + }, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var rows [][]string + report, err := fetchCachedGcpComplianceReportSchema(args[0]) + if err != nil { + return errors.Wrap(err, "unable to fetch gcp compliance report schema") + } + + if cli.JSONOutput() { + return cli.OutputJSON(api.NewRecommendationV2(report)) + } + + for _, r := range report { + rows = append(rows, []string{r.ID, strconv.FormatBool(r.State)}) + } + + cli.OutputHuman(renderOneLineCustomTable(args[0], + renderCustomTable([]string{}, rows, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + )) + return nil + }, + } + + // complianceGcpScanCmd represents the inventory scan inside the gcp command + complianceGcpScanCmd = &cobra.Command{ + Use: "scan", + Short: "Scan triggers a new resource inventory scan", + Long: `Scan triggers a new resource inventory scan.`, + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress("Triggering Gcp inventory scan") + response, err := cli.LwApi.V2.Inventory.Scan(api.GcpInventoryType) + cli.StopProgress() + + if err != nil { + return err + } + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + + cli.OutputHuman(renderSimpleTable([]string{}, [][]string{ + {"STATUS", response.Data.Status}, + {"DETAILS", response.Data.Details}, + })) + return nil + }, + } +) + +func init() { + // add sub-commands to the gcp command + complianceGcpCmd.AddCommand(complianceGcpListCmd) + complianceGcpCmd.AddCommand(complianceGcpListProjCmd) + complianceGcpCmd.AddCommand(complianceGcpGetReportCmd) + complianceGcpCmd.AddCommand(complianceGcpScanCmd) + + // Experimental Commands + complianceGcpCmd.AddCommand(complianceGcpReportStatusCmd) + complianceGcpCmd.AddCommand(complianceGcpDisableReportCmd) + complianceGcpCmd.AddCommand(complianceGcpEnableReportCmd) + + complianceGcpGetReportCmd.Flags().BoolVar(&compCmdState.Details, "details", false, + "increase details about the compliance report", + ) + complianceGcpGetReportCmd.Flags().BoolVar(&compCmdState.Pdf, "pdf", false, + "download report in PDF format", + ) + + // Output the report in CSV format + complianceGcpGetReportCmd.Flags().BoolVar(&compCmdState.Csv, "csv", false, + "output report in CSV format", + ) + + // GCP report types: GCP_ISO_27001_2013, GCP_NIST_800_171_REV2, GCP_CMMC_1_02, GCP_PCI_DSS_3_2_1, GCP_PCI_Rev2, + //GCP_NIST_CSF, GCP_CIS13, GCP_HIPAA_2013, GCP_CIS12, GCP_CIS, GCP_SOC_2, GCP_ISO_27001, GCP_NIST_800_53_REV4, + //GCP_CIS_1_3_0_NIST_800_53_rev5, GCP_CIS_1_3_0_NIST_CSF, GCP_HIPAA, GCP_HIPAA_Rev2, GCP_CIS_1_3_0_NIST_800_171_rev2, + //GCP_SOC, GCP_K8S, GCP_SOC_Rev2, GCP_PCI + complianceGcpGetReportCmd.Flags().StringVar(&compGcpCmdState.Type, "type", "GCP_CIS13", + fmt.Sprintf(`report type to display, run 'lacework report-definitions list' for more information. +valid types:%s`, prettyPrintReportTypes(api.GcpReportTypes())), + ) + + // mark report type flag as deprecated + errcheckWARN(complianceGcpGetReportCmd.Flags().MarkDeprecated("type", "use --report_name flag instead")) + + // Run 'lacework report-definition --subtype GCP' for a full list of GCP report names + complianceGcpGetReportCmd.Flags().StringVar(&compGcpCmdState.ReportName, "report_name", + api.ComplianceReportDefaultGcp, + "report name to display, run 'lacework report-definitions list' for more information.") + + complianceGcpGetReportCmd.Flags().StringSliceVar(&compCmdState.Category, "category", []string{}, + "filter report details by category (storage, networking, identity-and-access-management, ...)", + ) + + complianceGcpGetReportCmd.Flags().StringSliceVar(&compCmdState.Service, "service", []string{}, + "filter report details by service (gcp:storage:bucket, gcp:kms:cryptoKey, gcp:project, ...)", + ) + + complianceGcpGetReportCmd.Flags().StringVar(&compCmdState.Severity, "severity", "", + fmt.Sprintf("filter report details by severity threshold (%s)", + lwseverity.ValidSeverities.String()), + ) + + complianceGcpGetReportCmd.Flags().StringVar(&compCmdState.Status, "status", "", + fmt.Sprintf("filter report details by status (%s)", + strings.Join(api.ValidComplianceStatus, ", ")), + ) +} + +// Simple helper to prompt for approval after disable request +func complianceGcpDisableReportCmdPrompt(arg string) (int, error) { + var message string + + switch arg { + case "CIS", "CIS_1_0", "GCP_CIS": + message = `WARNING! +Disabling all recommendations for CIS_1_0 will disable the following reports and its corresponding compliance alerts: + GCP CIS Benchmark + PCI Benchmark + SOC 2 Report + + Would you like to proceed? + ` + case "CIS_1_2", "GCP_CIS12": + message = `WARNING! +Disabling all recommendations for CIS_1_2 will disable the following reports and its corresponding compliance alerts: + GCP CIS Benchmark 1.2 + HIPAA Report Rev2 + PCI Benchmark Rev2 + SOC 2 Report Rev2 + ISO27001 Report + NIST 800-171 rev2 Report + NIST 800-53 rev4 Report + NIST CSF rev2 Report + + Would you like to proceed? + ` + } + + options := []string{ + "Proceed with disable", + "Quit", + } + + var answer int + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: message, + Options: options, + }, + Response: &answer, + }) + + return answer, err +} + +func complianceGcpDisableReportDisplayChanges(arg string) (bool, error) { + answer, err := complianceGcpDisableReportCmdPrompt(arg) + if err != nil { + return false, err + } + return answer == 0, nil +} + +func complianceGcpReportDetailsTable(report *api.GcpReport) [][]string { + return [][]string{ + {"Report Type", report.ReportType}, + {"Report Title", report.ReportTitle}, + {"Organization ID", report.OrganizationID}, + {"Organization Name", report.OrganizationName}, + {"Project ID", report.ProjectID}, + {"Project Name", report.ProjectName}, + {"Report Time", report.ReportTime.UTC().Format(time.RFC3339)}, + } +} + +// ALLY-431 Workaround to split the Project ID and Project Alias +// ultimately, we need to fix this in the API response +func splitGcpProjectsApiResponse(gcpInfo api.GcpConfigData) cliComplianceGcpInfo { + var ( + orgID, orgAlias = splitIDAndAlias(gcpInfo.Organization) + cliGcpInfo = cliComplianceGcpInfo{ + Organization: cliComplianceIDAlias{orgID, orgAlias}, + Projects: make([]cliComplianceIDAlias, 0), + } + ) + + for _, project := range gcpInfo.Projects { + id, alias := splitIDAndAlias(project) + cliGcpInfo.Projects = append(cliGcpInfo.Projects, cliComplianceIDAlias{id, alias}) + } + + return cliGcpInfo +} + +// @afiune we use named return in this function to be explicit about what is it +// that the function is returning, id and alias respectively +func splitIDAndAlias(text string) (id string, alias string) { + // Getting alias from text + aliasRegex := regexp.MustCompile(`\((.*?)\)`) + aliasBytes := aliasRegex.Find([]byte(text)) + if len(aliasBytes) == 0 { + // if we couldn't get the alias from the provided text + // it means that the entire text is the id + id = text + return + } + alias = string(aliasBytes) + alias = strings.Trim(alias, "(") + alias = strings.Trim(alias, ")") + + // Getting id from text + idRegex := regexp.MustCompile(`^(.*?)\(`) + idBytes := idRegex.Find([]byte(text)) + id = string(idBytes) + id = strings.Trim(id, "(") + id = strings.TrimSpace(id) + + cli.Log.Infow("splitted", "text", text, "id", id, "alias", alias) + return +} + +func getGcpAccounts(orgID, status string) []gcpProject { + var accounts []gcpProject + + cli.StartProgress(fmt.Sprintf("Fetching compliance information about %s organization...", orgID)) + projectsResponse, err := cli.LwApi.V2.Configs.Gcp.ListProjects(orgID) + cli.StopProgress() + if err != nil { + cli.Log.Warnw("unable to list gcp projects", "org_id", orgID, "error", err.Error()) + return accounts + } + for _, projects := range projectsResponse.Data { + for _, project := range projects.Projects { + projectID, _ := splitIDAndAlias(project) + accounts = append(accounts, gcpProject{ + OrganizationID: orgID, + ProjectID: projectID, + Status: status, + }) + } + } + return accounts +} + +type cliComplianceGcpInfo struct { + Organization cliComplianceIDAlias `json:"organization"` + Projects []cliComplianceIDAlias `json:"projects"` +} + +type cliComplianceIDAlias struct { + ID string `json:"id"` + Alias string `json:"alias"` +} + +type gcpProject struct { + ProjectID string `json:"project_id"` + OrganizationID string `json:"organization_id"` + Status string `json:"status"` +} + +func extractGcpProjects(response api.CloudAccountsResponse) []gcpProject { + var gcpAccounts []gcpProject + var gcpData api.GcpCfgData + + for _, gcp := range response.Data { + + gcpJson, err := json.Marshal(gcp.Data) + if err != nil { + continue + } + + err = json.Unmarshal(gcpJson, &gcpData) + if err != nil { + continue + } + + // if organization account, fetch the project ids + if gcpData.IDType == "ORGANIZATION" { + gcpAccounts = append(gcpAccounts, getGcpAccounts(gcpData.ID, gcp.Status())...) + } else if containsDuplicateProjectID(gcpAccounts, gcpData.ID) { + cli.Log.Warnw("duplicate gcp project", "integration_guid", gcp.IntgGuid, "project", gcpData.ID) + continue + } else { + gcpIntegration := gcpProject{ + OrganizationID: "n/a", + ProjectID: gcpData.ID, + Status: gcp.Status(), + } + gcpAccounts = append(gcpAccounts, gcpIntegration) + } + } + + sort.Slice(gcpAccounts, func(i, j int) bool { + switch strings.Compare(gcpAccounts[i].OrganizationID, gcpAccounts[j].OrganizationID) { + case -1: + return true + case 1: + return false + } + return gcpAccounts[i].ProjectID < gcpAccounts[j].ProjectID + }) + + return gcpAccounts +} + +func containsDuplicateProjectID(gcpAccounts []gcpProject, projectID string) bool { + for _, value := range gcpAccounts { + if projectID == value.ProjectID { + return true + } + } + return false +} + +func cliListGcpProjectsAndOrgs(response api.CloudAccountsResponse, gcpData api.GcpConfigsResponse) error { + jsonOut := struct { + Projects []gcpProject `json:"gcp_projects"` + }{Projects: make([]gcpProject, 0)} + + if len(response.Data) == 0 { + if cli.JSONOutput() { + return cli.OutputJSON(jsonOut) + } + + msg := `There are no GCP integrations configured in your account. + +Get started by integrating your GCP to analyze configuration compliance using the command: + + lacework cloud-account create + +If you prefer to configure the integration via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Settings > Integrations > Cloud Accounts. +` + cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) + return nil + } + + if cli.JSONOutput() { + jsonOut.Projects = extractGcpProjects(response) + return cli.OutputJSON(jsonOut) + } + + var rows [][]string + for _, gcp := range extractGcpProjects(response) { + var orgID = gcp.OrganizationID + // if orgID is missing, match org with configs response + if orgID == "" || orgID == "n/a" { + for _, g := range gcpData.Data { + for _, project := range g.Projects { + // split projectID from alias + projectID := strings.Split(project, " (")[0] + if projectID == gcp.ProjectID { + orgID = g.Organization + } + } + } + } + + rows = append(rows, []string{orgID, gcp.ProjectID, gcp.Status}) + } + + cli.OutputHuman(renderSimpleTable([]string{"Organization ID", "Project ID", "Status"}, rows)) + return nil +} + +func fetchCachedGcpComplianceReportSchema(reportType string) (response []api.RecV2, err error) { + var cacheKey = fmt.Sprintf("compliance/gcp/schema/%s", reportType) + + expired := cli.ReadCachedAsset(cacheKey, &response) + if expired { + cli.StartProgress("Fetching compliance report schema...") + response, err = cli.LwApi.V2.Recommendations.Gcp.GetReport(reportType) + cli.StopProgress() + if err != nil { + return nil, errors.Wrap(err, "unable to get GCP compliance report schema") + } + + if len(response) == 0 { + return nil, errors.New("no data found in the report") + } + + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response) + } + return +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/component.go b/vendor/github.com/lacework/go-sdk/cli/cmd/component.go new file mode 100644 index 000000000..c1c2e0b91 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/component.go @@ -0,0 +1,1295 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/Masterminds/semver" + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/lwcomponent" +) + +const ( + componentTypeAnnotation string = "component" + componentsCacheKey string = "components" +) + +var ( + // componentsCmd represents the components command + componentsCmd = &cobra.Command{ + Use: "component", + Aliases: []string{"components"}, + Short: "Manage components", + Long: `Manage components to extend your experience with the Lacework platform`, + } + + // componentsListCmd represents the list sub-command inside the components command + componentsListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all components", + Long: `List all available components and their current state`, + RunE: runComponentsList, + } + + // componentsShowCmd represents the show sub-command inside the components command + componentsShowCmd = &cobra.Command{ + Use: "show ", + Short: "Show details about a component", + Long: "Show details about a component", + Args: cobra.ExactArgs(1), + RunE: runComponentsShow, + } + + // componentsInstallCmd represents the install sub-command inside the components command + componentsInstallCmd = &cobra.Command{ + Use: "install ", + Short: "Install a new component", + Long: `Install a new component`, + Args: cobra.ExactArgs(1), + RunE: runComponentsInstall, + } + + // componentsUpdateCmd represents the update sub-command inside the components command + componentsUpdateCmd = &cobra.Command{ + Use: "update ", + Aliases: []string{"upgrade"}, + Short: "Update an existing component", + Long: `Update an existing component`, + Args: cobra.ExactArgs(1), + RunE: runComponentsUpdate, + } + + // componentsUninstallCmd represents the uninstall sub-command inside the components command + componentsUninstallCmd = &cobra.Command{ + Use: "uninstall ", + Aliases: []string{"delete", "remove", "rm"}, + Short: "Uninstall an existing component", + Long: `Uninstall an existing component`, + Args: cobra.ExactArgs(1), + RunE: runComponentsDelete, + } + + // componentsDevModeCmd represents the dev sub-command inside the components command + componentsDevModeCmd = &cobra.Command{ + Use: "dev ", + Hidden: true, + Short: "Enter development mode of a new or existing component", + Args: cobra.ExactArgs(1), + RunE: runComponentsDevMode, + } + + versionArg string +) + +func init() { + // add the components command + rootCmd.AddCommand(componentsCmd) + + componentsInstallCmd.PersistentFlags().StringVar(&versionArg, "version", "", + "require a specific version to be installed (default is latest)") + componentsUpdateCmd.PersistentFlags().StringVar(&versionArg, "version", "", + "update to a specific version (default is latest)") + + // add sub-commands to the components command + componentsCmd.AddCommand(componentsListCmd) + componentsCmd.AddCommand(componentsShowCmd) + componentsCmd.AddCommand(componentsInstallCmd) + componentsCmd.AddCommand(componentsUpdateCmd) + componentsCmd.AddCommand(componentsUninstallCmd) + componentsCmd.AddCommand(componentsDevModeCmd) + + // load components dynamically + cli.PrototypeLoadComponents() + + // v1 components + cli.LoadComponents() +} + +// hasInstalledCommands is used inside the cobra template for generating the usage +// of commands, it returns true if there are installed commands via the CDK +func hasInstalledCommands() bool { + return cli.installedCmd +} + +// isComponent is used inside the cobra template for generating the usage of +// commands, it needs the annotations of the command and it will return true +// if the command was installed from the CDK +func isComponent(annotations map[string]string) bool { + t, found := annotations["type"] + if found && t == componentTypeAnnotation { + return true + } + return false +} + +// IsComponentInstalled returns true if component is +// valid and installed +func (c *cliState) IsComponentInstalled(name string) bool { + var err error + c.LwComponents, err = lwcomponent.LocalState() + if err != nil || c.LwComponents == nil { + return false + } + + component, found := c.LwComponents.GetComponent(name) + if found && component.IsInstalled() { + return true + } + return false +} + +// Load v1 components +func (c *cliState) LoadComponents() { + components, err := lwcomponent.LoadLocalComponents() + if err != nil { + c.Log.Debugw("unable to load components", "error", err) + return + } + + for _, component := range components { + exists := false + + for _, cmd := range rootCmd.Commands() { + if cmd.Use == component.Name { + exists = true + break + } + } + + // Skip components that were added by the prototype code + if exists { + continue + } + + version := component.InstalledVersion() + + if version != nil { + componentCmd := &cobra.Command{ + Use: component.Name, + Short: component.Description, + Annotations: map[string]string{"type": componentTypeAnnotation}, + Version: version.String(), + SilenceUsage: true, + DisableFlagParsing: true, + DisableFlagsInUseLine: true, + RunE: func(cmd *cobra.Command, args []string) error { + return v1ComponentCommand(c, cmd) + }, + } + + rootCmd.AddCommand(componentCmd) + } + } +} + +// Grpc server used for components to communicate back to the CLI +func startGrpcServer(c *cliState) { + if err := c.Serve(); err != nil { + c.Log.Errorw("couldn't serve gRPC server", "error", err) + } +} + +func v1ComponentCommand(c *cliState, cmd *cobra.Command) error { + // Parse component -v/--version flag + versionVal, _ := cmd.Flags().GetBool("version") + if versionVal { + cmd.Printf("%s version %s\n", cmd.Use, cmd.Version) + return nil + } + + go startGrpcServer(c) + + catalog, err := LoadCatalog(cmd.Use, false) + if err != nil { + return errors.Wrap(err, "unable to load component Catalog") + } + + component, err := catalog.GetComponent(cmd.Use) + if err != nil { + return err + } + + if !component.Exec.Executable() { + return errors.New("component is not executable") + } + + c.Log.Debugw("running component", "component", cmd.Use, + "args", c.componentParser.componentArgs, + "cli_flags", c.componentParser.cliArgs) + + envs := []string{ + fmt.Sprintf("LW_COMPONENT_NAME=%s", cmd.Use), + } + + envs = append(envs, c.envs()...) + + err = component.Exec.ExecuteInline(c.componentParser.componentArgs, envs...) + if err != nil { + return err + } + + shouldPrint, err := dailyComponentUpdateAvailable(component.Name) + if err != nil { + cli.Log.Debugw("unable to load components last check cache", "error", err) + } + if shouldPrint && component.ApiInfo != nil && component.InstalledVersion().LessThan(component.ApiInfo.Version) { + format := "\n%s v%s available: to update, run `lacework component update %s`\n" + cli.OutputHuman(format, cmd.Use, component.ApiInfo.Version, cmd.Use) + } + + return nil +} + +// LoadComponents reads the local components state and loads all installed components +// of type `CLI_COMMAND` dynamically into the root command of the CLI (`rootCmd`) +func (c *cliState) PrototypeLoadComponents() { + c.Log.Debugw("loading local components") + state, err := lwcomponent.LocalState() + if err != nil || state == nil { + c.Log.Debugw("unable to load components", "error", err) + return + } + + c.LwComponents = state + + // @dhazekamp how do we ensure component command names don't overlap with other commands? + + for _, component := range c.LwComponents.Components { + if component.IsInstalled() && component.IsCommandType() { + c.installedCmd = true + + ver, err := component.CurrentVersion() + if err != nil { + c.Log.Errorw("unable to load dynamic cli command", + "component", component.Name, "error", err, + ) + continue + } + + c.Log.Debugw("loading dynamic cli command", + "component", component.Name, "version", ver, + ) + componentCmd := + &cobra.Command{ + Use: component.Name, + Short: component.Description, + Annotations: map[string]string{"type": componentTypeAnnotation}, + Version: ver.String(), + SilenceUsage: true, + DisableFlagParsing: true, + DisableFlagsInUseLine: true, + RunE: func(cmd *cobra.Command, args []string) error { + // cobra will automatically add a -v/--version flag to + // the command, but because for components we're not + // parsing the args at the usual point in time, we have + // to repeat the check for -v here + versionVal, _ := cmd.Flags().GetBool("version") + if versionVal { + cmd.Printf("%s version %s\n", cmd.Use, cmd.Version) + return nil + } + go func() { + // Start the gRPC server for components to communicate back + if err := c.Serve(); err != nil { + c.Log.Errorw("couldn't serve gRPC server", "error", err) + } + }() + + c.Log.Debugw("running component", "component", cmd.Use, + "args", c.componentParser.componentArgs, + "cli_flags", c.componentParser.cliArgs) + f, ok := c.LwComponents.GetComponent(cmd.Use) + if ok { + shouldPrint, compVerErr := dailyComponentUpdateAvailable(f.Name) + if compVerErr != nil { + // Log an error but do not fail + cli.Log.Debugw("unable to run daily component version check", "error", err) + } + if shouldPrint && f.Status() == lwcomponent.UpdateAvailable { + format := "%s v%s available: to update, run `lacework component update %s`\n" + cli.OutputHuman(fmt.Sprintf(format, cmd.Use, f.LatestVersion.String(), cmd.Use)) + } + envs := []string{ + fmt.Sprintf("LW_COMPONENT_NAME=%s", cmd.Use), + } + envs = append(envs, c.envs()...) + return f.RunAndOutput(c.componentParser.componentArgs, envs...) + } + + // We will land here only if we couldn't run the component, which is not + // possible since we are adding the components dynamically, still if it + // happens, let the user know that we would love to hear their feedback + return errors.New("something went pretty wrong here, contact support@lacework.net") + }, + } + rootCmd.AddCommand(componentCmd) + } + } +} + +func runComponentsList(_ *cobra.Command, _ []string) (err error) { + if !lwcomponent.CatalogV1Enabled(cli.LwApi) { + return prototypeRunComponentsList() + } + + return listComponents() +} + +func listComponents() error { + catalog, err := LoadCatalog("", false) + if err != nil { + return errors.Wrap(err, "unable to load component Catalog") + } + + if cli.JSONOutput() { + return CDKComponentsJSON(catalog) + } + + if catalog.ComponentCount() == 0 { + msg := "There are no components available, " + + "come back later or contact support. (version: %s)\n" + cli.OutputHuman(fmt.Sprintf(msg, cli.LwComponents.Version)) + + return nil + } + + printComponents(catalog.PrintComponents()) + + return nil +} + +func printComponent(data []string) { + printComponents([][]string{data}) +} + +func printComponents(data [][]string) { + cli.OutputHuman( + renderCustomTable( + []string{"Status", "Name", "Version", "Description"}, + data, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + ) +} + +func runComponentsInstall(cmd *cobra.Command, args []string) (err error) { + if !lwcomponent.CatalogV1Enabled(cli.LwApi) { + return prototypeRunComponentsInstall(cmd, args) + } + + return installComponent(args) +} + +func installComponent(args []string) (err error) { + var ( + componentName string = args[0] + downloadComplete = make(chan int8, 1) + params map[string]interface{} = make(map[string]interface{}) + start time.Time + ) + + cli.Event.Component = componentName + cli.Event.Feature = "install_component" + defer cli.SendHoneyvent() + + catalog, err := LoadCatalog(componentName, false) + if err != nil { + err = errors.Wrap(err, "unable to load component Catalog") + return + } + + component, err := catalog.GetComponent(componentName) + if err != nil { + return + } + + installedVersion := component.InstalledVersion() + if installedVersion != nil { + cli.OutputHuman("Component %s is already installed. To upgrade run '%s'\n", + component.Name, + color.HiGreenString("lacework component update %s", component.Name)) + return nil + } + + cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", component.Name)) + + cli.StartProgress(fmt.Sprintf("Staging component %s...", componentName)) + + start = time.Now() + + progressClosure := func(path string, sizeB int64) { + downloadProgress(downloadComplete, path, sizeB) + } + + stageClose, err := catalog.Stage(component, versionArg, progressClosure) + defer stageClose() + downloadComplete <- 0 + + if err != nil { + cli.StopProgress() + return + } + + params["stage_duration_ms"] = time.Since(start).Milliseconds() + cli.Event.FeatureData = params + + cli.StopProgress() + if err != nil { + return + } + cli.OutputChecklist(successIcon, "Component %s staged\n", color.HiYellowString(componentName)) + + cli.StartProgress("Verifing component signature...") + + err = catalog.Verify(component) + + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "verification of component signature failed") + return + } + cli.OutputChecklist(successIcon, "Component signature verified\n") + + cli.StartProgress("Installing component...") + + err = catalog.Install(component) + + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "Install of component failed") + return + } + cli.OutputChecklist(successIcon, "Component version %s installed\n", component.InstalledVersion()) + + cli.StartProgress("Configuring component...") + + stdout, stderr, errCmd := component.Exec.Execute([]string{"cdk-init"}, cli.envs()...) + if errCmd != nil { + if errCmd != lwcomponent.ErrNonExecutable { + cli.Log.Warnw("component life cycle", + "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) + } + } else { + cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) + } + cli.StopProgress() + + cli.OutputChecklist(successIcon, "Component configured\n") + cli.OutputHuman("\nInstallation completed.\n") + + if component.InstallMessage != "" { + cli.OutputHuman(fmt.Sprintf("\n%s\n", component.InstallMessage)) + } + + return +} + +func runComponentsShow(_ *cobra.Command, args []string) (err error) { + if !lwcomponent.CatalogV1Enabled(cli.LwApi) { + return prototypeRunComponentsShow(args) + } + + return showComponent(args) +} + +func showComponent(args []string) error { + var ( + componentName string = args[0] + ) + + catalog, err := LoadCatalog(componentName, true) + if err != nil { + return errors.Wrap(err, "unable to load component Catalog") + } + + component, err := catalog.GetComponent(componentName) + if err != nil { + return err + } + + if cli.JSONOutput() { + return CDKComponentJSON(component) + } + + printComponent(component.PrintSummary()) + + allVersions, err := catalog.ListComponentVersions(component) + if err != nil { + return err + } + + printAvailableVersions(component.InstalledVersion(), allVersions) + + return nil +} + +func printAvailableVersions(installedVersion *semver.Version, availableVersions []*semver.Version) { + cli.OutputHuman("\n") + + result := "The following versions of this component are available to install:" + foundInstalled := false + + for _, version := range availableVersions { + result += "\n" + result += " - " + version.String() + if installedVersion != nil && version.Equal(installedVersion) { + result += " (installed)" + foundInstalled = true + } + } + + if installedVersion != nil && !foundInstalled { + result += fmt.Sprintf( + "\n\nThe currently installed version %s is no longer available to install.", + installedVersion.String(), + ) + } + + cli.OutputHuman(result) + cli.OutputHuman("\n") +} + +func runComponentsUpdate(_ *cobra.Command, args []string) (err error) { + if !lwcomponent.CatalogV1Enabled(cli.LwApi) { + return prototypeRunComponentsUpdate(args) + } + + return updateComponent(args) +} + +func updateComponent(args []string) (err error) { + var ( + componentName string = args[0] + downloadComplete = make(chan int8) + params map[string]interface{} = make(map[string]interface{}) + start time.Time + targetVersion *semver.Version + ) + + catalog, err := LoadCatalog(componentName, false) + if err != nil { + return errors.Wrap(err, "unable to load component Catalog") + } + + component, err := catalog.GetComponent(componentName) + if err != nil { + return err + } + + cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", component.Name)) + + installedVersion := component.InstalledVersion() + if installedVersion == nil { + return errors.Errorf("component %s not installed", color.HiYellowString(componentName)) + } + + latestVersion := component.LatestVersion() + if latestVersion == nil { + return errors.Errorf("component %s not available in API", color.HiYellowString(componentName)) + } + + if versionArg == "" { + targetVersion = latestVersion + } else { + targetVersion, err = semver.NewVersion(versionArg) + if err != nil { + return errors.Errorf("invalid semantic version %s", versionArg) + } + } + + if installedVersion.Equal(targetVersion) { + cli.OutputHuman("Component %s is version %s.\n", component.Name, color.HiYellowString(installedVersion.String())) + return nil + } + + cli.StartProgress(fmt.Sprintf("Staging component %s...", color.HiYellowString(componentName))) + + start = time.Now() + + progressClosure := func(path string, sizeB int64) { + downloadProgress(downloadComplete, path, sizeB) + } + + stageClose, err := catalog.Stage(component, versionArg, progressClosure) + defer stageClose() + downloadComplete <- 0 + if err != nil { + cli.StopProgress() + return + } + + params["stage_duration_ms"] = time.Since(start).Milliseconds() + cli.Event.FeatureData = params + + cli.StopProgress() + if err != nil { + return + } + cli.OutputChecklist(successIcon, "Component %s staged\n", color.HiYellowString(componentName)) + + cli.StartProgress("Verifing component signature...") + + err = catalog.Verify(component) + + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "verification of component signature failed") + return + } + cli.OutputChecklist(successIcon, "Component signature verified\n") + + cli.StartProgress(fmt.Sprintf("Updating component %s to version %s...", component.Name, targetVersion.String())) + + err = catalog.Install(component) + + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "Update of component failed") + return + } + + cli.OutputChecklist(successIcon, "Component %s updated to %s\n", + color.HiYellowString(component.Name), + color.HiCyanString(targetVersion.String())) + + cli.StartProgress("Configuring component...") + + stdout, stderr, errCmd := component.Exec.Execute([]string{"cdk-reconfigure"}, cli.envs()...) + if errCmd != nil { + if errCmd != lwcomponent.ErrNonExecutable { + cli.Log.Warnw("component life cycle", + "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) + } + } else { + cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) + } + cli.StopProgress() + + cli.OutputChecklist(successIcon, "Component reconfigured\n") + + if component.UpdateMessage != "" { + cli.OutputHuman(fmt.Sprintf("\n%s\n", component.UpdateMessage)) + } + + return +} + +func runComponentsDelete(_ *cobra.Command, args []string) (err error) { + if !lwcomponent.CatalogV1Enabled(cli.LwApi) { + return prototypeRunComponentsDelete(args) + } + + return deleteComponent(args) +} + +func deleteComponent(args []string) (err error) { + var ( + componentName string = args[0] + ) + + catalog, err := LoadCatalog(componentName, false) + if err != nil { + return errors.Wrap(err, "unable to load component Catalog") + } + + component, err := catalog.GetComponent(componentName) + if err != nil { + return err + } + + cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", component.Name)) + + cli.StartProgress("Cleaning component data...") + + stdout, stderr, errCmd := component.Exec.Execute([]string{"cdk-cleanup"}, cli.envs()...) + if errCmd != nil { + if errCmd != lwcomponent.ErrNonExecutable { + cli.Log.Warnw("component life cycle", + "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) + } + } else { + cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) + } + cli.StopProgress() + + cli.OutputChecklist(successIcon, "Component data removed\n") + + cli.StartProgress("Deleting component...") + defer cli.StopProgress() + + err = catalog.Delete(component) + if err != nil { + return + } + + cli.StopProgress() + + cli.OutputChecklist(successIcon, "Component %s deleted\n", color.HiYellowString(component.Name)) + cli.OutputHuman("\n- We will do better next time.\n") + cli.OutputHuman("\nDo you want to provide feedback?\n") + cli.OutputHuman("Reach out to us at %s\n", color.HiCyanString("support@lacework.net")) + return +} + +func prototypeRunComponentsList() (err error) { + cli.StartProgress("Loading components state...") + cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "unable to list components") + return + } + + if cli.JSONOutput() { + return cli.OutputJSON(cli.LwComponents) + } + + if len(cli.LwComponents.Components) == 0 { + msg := "There are no components available, " + + "come back later or contact support. (version: %s)\n" + cli.OutputHuman(fmt.Sprintf(msg, cli.LwComponents.Version)) + return + } + + cli.OutputHuman( + renderCustomTable( + []string{"Status", "Name", "Version", "Description"}, + componentsToTable(), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + ) + + cli.OutputHuman("\nComponents version: %s\n", cli.LwComponents.Version) + return +} + +func prototypeRunComponentsShow(args []string) (err error) { + cli.StartProgress("Loading components state...") + cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "unable to load state of components") + return + } + component, found := cli.LwComponents.GetComponent(args[0]) + if !found { + return errors.New("component not found") + } + if cli.JSONOutput() { + return cli.OutputJSON(component) + } + + var colorize *color.Color + switch component.Status() { + case lwcomponent.NotInstalled: + colorize = color.New(color.FgWhite, color.Bold) + case lwcomponent.Installed: + colorize = color.New(color.FgGreen, color.Bold) + case lwcomponent.UpdateAvailable: + colorize = color.New(color.FgYellow, color.Bold) + } + + cli.OutputHuman( + renderCustomTable( + []string{"Name", "Status", "Description"}, + [][]string{{ + component.Name, + colorize.Sprintf(component.Status().String()), + component.Description, + }}, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + ) + var currentVersion *semver.Version = nil + if component.Status() == lwcomponent.Installed || component.Status() == lwcomponent.UpdateAvailable { + installed, err := component.CurrentVersion() + if err != nil { + return err + } + currentVersion = installed + } + cli.OutputHuman("\n") + cli.OutputHuman(component.ListVersions(currentVersion)) + cli.OutputHuman("\n") + return +} + +func componentsToTable() [][]string { + out := [][]string{} + for _, cdata := range cli.LwComponents.Components { + var colorize *color.Color + switch cdata.Status() { + case lwcomponent.NotInstalled: + colorize = color.New(color.FgWhite, color.Bold) + case lwcomponent.Installed: + colorize = color.New(color.FgGreen, color.Bold) + case lwcomponent.UpdateAvailable: + colorize = color.New(color.FgYellow, color.Bold) + } + + // by default, we display the latest version + version := cdata.LatestVersion.String() + + // but if the component is installed, + // we display the current version instead + if currentVersion, err := cdata.CurrentVersion(); err == nil { + version = currentVersion.String() + } + + out = append(out, []string{ + colorize.Sprintf(cdata.Status().String()), + cdata.Name, + version, + cdata.Description, + }) + } + return out +} + +func prototypeRunComponentsInstall(_ *cobra.Command, args []string) (err error) { + var ( + componentName string = args[0] + downloadComplete = make(chan int8) + version string = versionArg + params map[string]interface{} = make(map[string]interface{}) + start time.Time + ) + + cli.Event.Component = componentName + cli.Event.Feature = "install_component" + defer cli.SendHoneyvent() + + cli.StartProgress("Loading components state...") + // @afiune maybe move the state to the cache and fetch if it if has expired + cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "unable to load components") + return + } + + component, found := cli.LwComponents.GetComponent(componentName) + if !found { + err = errors.New(fmt.Sprintf("component %s not found. Try running 'lacework component list'", componentName)) + return + } + + cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", componentName)) + + if version == "" { + version = component.LatestVersion.String() + } + + start = time.Now() + + progressClosure := func(path string, sizeB int64) { + downloadProgress(downloadComplete, path, sizeB) + } + + cli.StartProgress(fmt.Sprintf("Installing component %s...", component.Name)) + err = cli.LwComponents.Install(component, version, progressClosure) + downloadComplete <- 0 + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "unable to install component") + return + } + + cli.OutputChecklist(successIcon, "Component %s installed\n", color.HiYellowString(component.Name)) + + params["install_duration_ms"] = time.Since(start).Milliseconds() + + start = time.Now() + + cli.StartProgress("Verifing component signature...") + err = cli.LwComponents.Verify(component, version) + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "verification of component signature failed") + return + } + cli.OutputChecklist(successIcon, "Component signature verified\n") + + params["verify_duration_ms"] = time.Since(start).Milliseconds() + + start = time.Now() + + cli.StartProgress(fmt.Sprintf("Configuring component %s...", component.Name)) + // component life cycle: initialize + stdout, stderr, errCmd := component.RunAndReturn([]string{"cdk-init"}, nil, cli.envs()...) + if errCmd != nil { + cli.Log.Warnw("component life cycle", + "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) + } else { + cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) + } + cli.StopProgress() + + cli.OutputChecklist(successIcon, "Component configured\n") + cli.OutputHuman("\nInstallation completed.\n") + + params["configure_duration_ms"] = time.Since(start).Milliseconds() + + cli.Event.FeatureData = params + + if component.Breadcrumbs.InstallationMessage != "" { + cli.OutputHuman("\n") + cli.OutputHuman(component.Breadcrumbs.InstallationMessage) + cli.OutputHuman("\n") + } + return +} + +func prototypeRunComponentsUpdate(args []string) (err error) { + cli.StartProgress("Loading components state...") + // @afiune maybe move the state to the cache and fetch if it if has expired + cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "unable to load components") + return + } + + component_name := args[0] + + component, found := cli.LwComponents.GetComponent(args[0]) + if !found { + err = errors.New(fmt.Sprintf("component %s not found. Try running 'lacework component list'", component_name)) + return + } + // @afiune end boilerplate load components + + cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", component_name)) + + updateTo := component.LatestVersion + if versionArg != "" { + parsedVersion, err := semver.NewVersion(versionArg) + if err != nil { + err = errors.Wrap(err, "invalid version specified") + return err + } + updateTo = *parsedVersion + } + + currentVersion, err := component.CurrentVersion() + if err != nil { + return err + } + + if currentVersion.Equal(&updateTo) { + cli.OutputHuman("You are already running version %s of this component", currentVersion.String()) + return nil + } + + downloadComplete := make(chan int8) + + progressClosure := func(path string, sizeB int64) { + downloadProgress(downloadComplete, path, sizeB) + } + + cli.StartProgress(fmt.Sprintf("Updating component %s to version %s...", component.Name, &updateTo)) + err = cli.LwComponents.Install(component, updateTo.String(), progressClosure) + downloadComplete <- 0 + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "unable to update component") + return + } + + cli.OutputChecklist(successIcon, "Component %s updated to %s\n", + color.HiYellowString(component.Name), + color.HiCyanString(fmt.Sprintf("v%s", updateTo.String()))) + + cli.StartProgress("Verifing component signature...") + err = cli.LwComponents.Verify(component, updateTo.String()) + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "verification of component signature failed") + return + } + cli.OutputChecklist(successIcon, "Component signature verified\n") + + cli.StartProgress(fmt.Sprintf("Reconfiguring %s component...", component.Name)) + // component life cycle: reconfigure + stdout, stderr, errCmd := component.RunAndReturn( + []string{"cdk-reconfigure", currentVersion.String(), updateTo.String()}, + nil, cli.envs()...) + if errCmd != nil { + cli.Log.Warnw("component life cycle", + "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) + } else { + cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) + } + cli.StopProgress() + + cli.OutputChecklist(successIcon, "Component reconfigured\n") + cli.OutputHuman("\n") + cli.OutputHuman(component.MakeUpdateMessage(*currentVersion, updateTo)) + cli.OutputHuman("\n") + return +} + +func prototypeRunComponentsDelete(args []string) (err error) { + cli.StartProgress("Loading components state...") + // @afiune maybe move the state to the cache and fetch if it if has expired + // @afiune DO WE NEED THIS? It should already be loaded + cli.LwComponents, err = lwcomponent.LocalState() + cli.StopProgress() + if err != nil { + err = errors.Wrap(err, "unable to load components") + return + } + + component, found := cli.LwComponents.GetComponent(args[0]) + if !found { + err = errors.New("component not found. Try running 'lacework component list'") + return + } + + if component.UnderDevelopment() { + cli.OutputHuman("Component '%s' in under development. Bypassing checks.\n\n", + color.HiYellowString(component.Name)) + } else if !component.IsInstalled() { + err = errors.Errorf( + "component not installed. Try running 'lacework component install %s'", + args[0], + ) + return + } + + cli.StartProgress("Cleaning component data...") + // component life cycle: cleanup + stdout, stderr, errCmd := component.RunAndReturn([]string{"cdk-cleanup"}, nil, cli.envs()...) + if errCmd != nil { + cli.Log.Warnw("component life cycle", + "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) + } else { + cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) + } + cli.StopProgress() + + cli.OutputChecklist(successIcon, "Component data removed\n") + + cli.StartProgress("Deleting component...") + defer cli.StopProgress() + + cPath, err := component.RootPath() + if err != nil { + err = errors.Wrap(err, "unable to delete component") + return + } + + err = os.RemoveAll(cPath) + if err != nil { + err = errors.Wrap(err, "unable to delete component") + return + } + + cli.StopProgress() + + cli.OutputChecklist(successIcon, "Component %s deleted\n", color.HiYellowString(component.Name)) + cli.OutputHuman("\n- We will do better next time.\n") + cli.OutputHuman("\nDo you want to provide feedback?\n") + cli.OutputHuman("Reach out to us at %s\n", color.HiCyanString("support@lacework.net")) + return +} + +func downloadProgress(complete chan int8, path string, sizeB int64) { + file, err := os.Open(path) + if err != nil { + cli.Log.Errorf("Failed to open component file: %s", err.Error()) + return + } + defer file.Close() + + var ( + previous float64 = 0 + stop bool = false + spinnerSuffix string = "" + ) + + if cli.spinner != nil { + spinnerSuffix = cli.spinner.Suffix + } + + for !stop { + select { + case <-complete: + stop = true + default: + info, err := file.Stat() + if err != nil { + cli.Log.Errorf("Failed to stat component file: %s", err.Error()) + return + } + + size := info.Size() + if size == 0 { + size = 1 + } + + if sizeB == 0 { + mb := float64(size) / (1 << 20) + + if mb > previous { + sizeString := fmt.Sprintf("%.0fmb", mb) + cli.Log.Infow("downloading component", "size", sizeString) + cli.StartProgress(fmt.Sprintf("%s (%s downloaded)", spinnerSuffix, sizeString)) + previous = mb + } + } else { + percent := float64(size) / float64(sizeB) * 100 + + if percent > previous { + percentString := fmt.Sprintf("%.0f%%", percent) + cli.Log.Infow("downloading component", "percent", percentString) + cli.StartProgress(fmt.Sprintf("%s (%s downloaded)", spinnerSuffix, percentString)) + previous = percent + } + } + } + + time.Sleep(time.Second) + } +} + +func LoadCatalog(componentName string, getAllVersions bool) (*lwcomponent.Catalog, error) { + cli.StartProgress("Loading component catalog...") + defer cli.StopProgress() + + var componentsApiInfo map[string]*lwcomponent.ApiInfo + + // try to load components Catalog from cache + if !cli.noCache { + expired := cli.ReadCachedAsset(componentsCacheKey, &componentsApiInfo) + if !expired && !getAllVersions { + cli.Log.Infow("loaded components from cache", "components", componentsApiInfo) + return lwcomponent.NewCachedCatalog(cli.LwApi, lwcomponent.NewStageTarGz, componentsApiInfo) + } + } + + // load components Catalog from API + catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz) + if err != nil { + return nil, err + } + + // Retrieve the list of all available versions for a single component + if getAllVersions { + component, err := catalog.GetComponent(componentName) + if err != nil { + return nil, err + } + + if component.HostInfo == nil || (component.HostInfo != nil && !component.HostInfo.Development()) { + vers, err := catalog.ListComponentVersions(component) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("unable to fetch component '%s' versions", componentName)) + } + + component.ApiInfo.AllVersions = vers + catalog.Components[componentName] = lwcomponent.NewCDKComponent(component.ApiInfo, component.HostInfo) + } + } + + componentsApiInfo = make(map[string]*lwcomponent.ApiInfo, len(catalog.Components)) + + for _, c := range catalog.Components { + if c.ApiInfo != nil { + componentsApiInfo[c.Name] = c.ApiInfo + } + } + + cli.WriteAssetToCache(componentsCacheKey, time.Now().Add(time.Hour*12), componentsApiInfo) + + return catalog, nil +} + +type componentJSON struct { + Name string `json:"name"` + Description string `json:"description"` + Version string `json:"version"` + LatestVersion string `json:"latest_version"` + ComponentType string `json:"type"` + Status string `json:"status"` +} + +func newComponentJSON(component *lwcomponent.CDKComponent) *componentJSON { + semver := component.InstalledVersion() + version := "" + + if semver != nil { + version = semver.String() + } + + latestVersion := "" + if component.LatestVersion() != nil { + latestVersion = component.LatestVersion().String() + } + + return &componentJSON{ + Name: component.Name, + Description: component.Description, + Version: version, + LatestVersion: latestVersion, + ComponentType: string(component.Type), + Status: component.Status.String(), + } +} + +func CDKComponentsJSON(catalog *lwcomponent.Catalog) error { + type catalogJSON struct { + Components []*componentJSON `json:"components"` + } + + output := catalogJSON{} + + for _, component := range catalog.Components { + output.Components = append(output.Components, newComponentJSON(&component)) + } + + return cli.OutputJSON(output) +} + +func CDKComponentJSON(component *lwcomponent.CDKComponent) error { + return cli.OutputJSON(newComponentJSON(component)) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/component_args.go b/vendor/github.com/lacework/go-sdk/cli/cmd/component_args.go new file mode 100644 index 000000000..eaaee5eaf --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/component_args.go @@ -0,0 +1,179 @@ +// +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strings" + + "github.com/spf13/pflag" +) + +type componentArgParser struct { + componentArgs []string + cliArgs []string +} + +// Parsing component args is surprisingly tricky. What we do here is mirror +// what pflags does itself, but instead of parsing arguments set aside (in componentArgs) +// those arguments that we should pass to the component, and keep track of the +// arguments that we should parse ourselves in cliArgs. + +func (p *componentArgParser) parseArgs(globalFlags *pflag.FlagSet, args []string) { + p.componentArgs = []string{} + p.cliArgs = []string{} + for len(args) > 0 { + s := args[0] + args = args[1:] + if s == "help" || s == "--help" || s == "-h" { + // always pass help down to the component + p.componentArgs = append(p.componentArgs, s) + continue + } + if len(s) == 0 || s[0] != '-' || len(s) == 1 { + // not a flag, passthrough + p.componentArgs = append(p.componentArgs, s) + continue + } + if s[1] == '-' { + if len(s) == 2 { + // "--" terminates the flags, but we do want to pass along the -- + // to the compoent + p.componentArgs = append(p.componentArgs, s) + p.componentArgs = append(p.componentArgs, args...) + break + } + args = p.parseLongArg(globalFlags, s, args) + } else { + args = p.parseShortArg(globalFlags, s, args) + } + } +} + +func (p *componentArgParser) parseLongArg(flags *pflag.FlagSet, s string, args []string) []string { + name := s[2:] + if len(name) == 0 || name[0] == '-' || name[0] == '=' { + // ---, or --= are not legal cobra flags, but we'll just pass + // it through to the component and let that deal with it + p.componentArgs = append(p.componentArgs, s) + return args + } + split := strings.SplitN(name, "=", 2) + name = split[0] + + flag := flags.Lookup(name) + + if flag == nil { + p.componentArgs = append(p.componentArgs, s) + // We're actually a bit stuck here as we don't know if this flag + // takes an argument or not, so we don't know whether or not to consume + // the next arg. What we'll do is peek ahead, and if the next arg does + // not start with '--' then we'll take it. + if len(args) > 0 && len(args[0]) > 0 && (args[0][0] == '-' && args[0][1] == '-') { + // This component flag does not take an arg + return args + } + if len(args) > 0 { + p.componentArgs = append(p.componentArgs, args[0]) + return args[1:] + } + return args + } + + if len(split) == 2 { + // '--flag=arg' + p.cliArgs = append(p.cliArgs, s) + return args + } + if flag.NoOptDefVal != "" { + // '--flag' (arg was optional) + p.cliArgs = append(p.cliArgs, s) + return args + } + if len(args) > 0 { + // '--flag arg' + p.cliArgs = append(p.cliArgs, s, args[0]) + return args[1:] + } + // '--flag' (arg was required) + p.cliArgs = append(p.cliArgs, s) + return args +} + +func (p *componentArgParser) parseShortArg(flags *pflag.FlagSet, s string, args []string) []string { + shorthands := s[1:] + + // shorthands can be a repeated list, e.g. -vvv. + for len(shorthands) > 0 { + shorthand := shorthands[0:1] + + flag := flags.ShorthandLookup(shorthand) + if flag == nil { + // Not our flag, pass to the component. Like the long form above we + // don't know whether to consume an extra arg, so we'll do the same + // thing: if the next arg does not start with `--`` then pass it along + + // handles case + // -n + if len(shorthands) == 1 { + p.componentArgs = append(p.componentArgs, fmt.Sprintf("-%s", shorthand)) + return args + } + + // handles cases + // -n=1234 + // -n1234 + if len(shorthands) > 2 { + p.componentArgs = append(p.componentArgs, fmt.Sprintf("-%s", shorthands)) + return args + } + + // handles cases + // -n 24h + // -n -24h + if len(shorthands) == 1 && len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' && args[0][1] != '-' { + p.componentArgs = append(p.componentArgs, fmt.Sprintf("-%s", shorthand)) + p.componentArgs = append(p.componentArgs, args[0]) + return args[2:] + } + } + + if len(shorthands) > 2 && shorthands[1] == '=' { + // '-f=arg' + p.cliArgs = append(p.cliArgs, s) + return args + } else if flag.NoOptDefVal != "" { + // '-f' (arg was optional) + p.cliArgs = append(p.cliArgs, s) + return args + } else if len(shorthands) > 1 { + // '-farg' + p.cliArgs = append(p.cliArgs, s) + return args + } else if len(args) > 0 { + // '-f arg' + p.cliArgs = append(p.cliArgs, s, args[0]) + return args[1:] + } else { + // '-f' (arg was required) + p.cliArgs = append(p.cliArgs, s) + return args + } + } + return args +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/component_dev.go b/vendor/github.com/lacework/go-sdk/cli/cmd/component_dev.go new file mode 100644 index 000000000..23007dfe9 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/component_dev.go @@ -0,0 +1,675 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "bytes" + "fmt" + "html/template" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/abiosoft/colima/util/terminal" + "github.com/fatih/color" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/internal/databox" + "github.com/lacework/go-sdk/lwcomponent" +) + +var cdkDevState = struct { + Type string + Scaffolding string + Description string +}{} + +var cdkGolangScaffoldingRequirements = map[string]string{ + "go": "https://go.dev/dl/", +} + +var cdkPythonScaffoldingRequirements = map[string]string{ + "python3": "https://www.python.org/downloads/", + "poetry": "https://python-poetry.org/docs/", +} + +func init() { + componentsDevModeCmd.Flags().StringVar( + &cdkDevState.Type, + "type", "", + fmt.Sprintf("component type (%s, %s, %s)", + lwcomponent.BinaryType, + lwcomponent.CommandType, + lwcomponent.LibraryType, + ), + ) + + componentsDevModeCmd.Flags().StringVar( + &cdkDevState.Description, + "description", "", + "component description", + ) + + componentsDevModeCmd.Flags().StringVar( + &cdkDevState.Scaffolding, "scaffolding", "", + "autogenerate code for a new component (available: Golang, Python)", + ) +} + +func runComponentsDevMode(_ *cobra.Command, args []string) error { + if !lwcomponent.CatalogV1Enabled(cli.LwApi) { + return prototypeRunComponentsDevMode(args) + } + + return devModeComponent(args) +} + +func devModeComponent(args []string) error { + var ( + componentName string = args[0] + componentDescription string = "" + componentDir string = "" + ) + + cli.StartProgress("Loading components Catalog...") + + catalog, err := LoadCatalog(componentName, false) + + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to load component Catalog") + } + + component, _ := catalog.GetComponent(componentName) + if component == nil { + + cli.OutputHuman("Component '%s' not found. Defining a new component.\n", + color.HiYellowString(componentName)) + + var ( + helpMsg = fmt.Sprintf("What are these component types ?\n"+ + "\n'%s' - A binary accessible via the Lacework CLI (Users will run 'lacework ')"+ + "\n'%s' - A regular standalone-binary (this component type is not accessible via the CLI)"+ + "\n'%s' - A library that only provides content for the Lacework CLI or other components\n", + lwcomponent.CommandType, lwcomponent.BinaryType, lwcomponent.LibraryType) + ) + if cdkDevState.Type == "" { + if err := survey.AskOne(&survey.Select{ + Message: "Select the type of component you are developing:", + Help: helpMsg, + Options: []string{ + lwcomponent.CommandType, + lwcomponent.BinaryType, + lwcomponent.LibraryType, + }, + }, &cdkDevState.Type); err != nil { + return err + } + } + + componentType := lwcomponent.Type(cdkDevState.Type) + + if cdkDevState.Description == "" { + if err := survey.AskOne(&survey.Input{ + Message: "What is this component about? (component description):", + }, &componentDescription); err != nil { + return err + } + } else { + componentDescription = cdkDevState.Description + } + + dir, err := lwcomponent.CatalogCacheDir() + if err != nil { + return err + } + + componentDir = filepath.Join(dir, componentName) + + if err := os.MkdirAll(componentDir, os.ModePerm); err != nil { + return err + } + + hostInfo, err := lwcomponent.NewHostInfo(componentDir, componentDescription, componentType) + if err != nil { + return err + } + + newComponent := lwcomponent.NewCDKComponent(nil, hostInfo) + + component = &newComponent + } + + if err := component.EnterDevMode(); err != nil { + return errors.Wrap(err, "unable to enter development mode") + } + + cli.OutputHuman("Component '%s' in now in development mode.\n", + color.HiYellowString(component.Name)) + + if component.Type == lwcomponent.CommandType { + // Offer the creation of a component scaffolding + if cdkDevState.Scaffolding == "" && cli.InteractiveMode() { + if err := survey.AskOne(&survey.Select{ + Message: "Would you like to initialize your component with scaffolding? ", + Options: []string{"No. Start from scratch", "Golang", "Python"}, + }, &cdkDevState.Scaffolding); err != nil { + return err + } + } + + switch cdkDevState.Scaffolding { + case "Golang": + if err := cdkGolangScaffolding(component.Name, componentDir); err != nil { + return err + } + + case "Python": + if err := cdkPythonScaffolding(component.Name, componentDir); err != nil { + return err + } + + default: + cli.OutputHuman("\nDeploy your dev component at: %s\n", + color.HiYellowString(filepath.Join(componentDir, component.Name))) + } + } + + cli.OutputHuman("\nComponent directory: %s\n", color.HiCyanString(componentDir)) + cli.OutputHuman("Dev specs: %s\n", color.HiCyanString(filepath.Join(componentDir, ".dev"))) + return nil +} + +func prototypeRunComponentsDevMode(args []string) error { + cli.StartProgress("Loading components state...") + var err error + cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to load components") + } + + component, found := cli.LwComponents.GetComponent(args[0]) + if !found { + component = &lwcomponent.Component{ + Name: args[0], + } + + if component.UnderDevelopment() { + return errors.New("component already under development.") + } + + cli.OutputHuman("Component '%s' not found. Defining a new component.\n", + color.HiYellowString(component.Name)) + + var ( + helpMsg = fmt.Sprintf("What are these component types ?\n"+ + "\n'%s' - A binary accessible via the Lacework CLI (Users will run 'lacework ')"+ + "\n'%s' - A regular standalone-binary (this component type is not accessible via the CLI)"+ + "\n'%s' - A library that only provides content for the Lacework CLI or other components\n", + lwcomponent.CommandType, lwcomponent.BinaryType, lwcomponent.LibraryType) + ) + if cdkDevState.Type == "" { + if err := survey.AskOne(&survey.Select{ + Message: "Select the type of component you are developing:", + Help: helpMsg, + Options: []string{ + lwcomponent.CommandType, + lwcomponent.BinaryType, + lwcomponent.LibraryType, + }, + }, &cdkDevState.Type); err != nil { + return err + } + } + + component.Type = lwcomponent.Type(cdkDevState.Type) + + if cdkDevState.Description == "" { + if err := survey.AskOne(&survey.Input{ + Message: "What is this component about? (component description):", + }, &component.Description); err != nil { + return err + } + } else { + component.Description = cdkDevState.Description + } + } + + if err := component.EnterDevelopmentMode(); err != nil { + return errors.Wrap(err, "unable to enter development mode") + } + + rPath, err := component.RootPath() + if err != nil { + return errors.New("unable to detect RootPath") + } + + cli.OutputHuman("Component '%s' in now in development mode.\n", + color.HiYellowString(component.Name)) + + if component.Type == lwcomponent.CommandType { + // Offer the creation of a component scaffolding + if cdkDevState.Scaffolding == "" && cli.InteractiveMode() { + if err := survey.AskOne(&survey.Select{ + Message: "Would you like to initialize your component with scaffolding? ", + Options: []string{"No. Start from scratch", "Golang", "Python"}, + }, &cdkDevState.Scaffolding); err != nil { + return err + } + } + + switch cdkDevState.Scaffolding { + case "Golang": + if err := cdkGolangScaffolding(component.Name, rPath); err != nil { + return err + } + + case "Python": + if err := cdkPythonScaffolding(component.Name, rPath); err != nil { + return err + } + + default: + cli.OutputHuman("\nDeploy your dev component at: %s\n", + color.HiYellowString(filepath.Join(rPath, component.Name))) + } + } + + cli.OutputHuman("\nRoot path: %s\n", color.HiCyanString(rPath)) + cli.OutputHuman("Dev specs: %s\n", color.HiCyanString(filepath.Join(rPath, ".dev"))) + return nil +} + +func cdkGolangScaffolding(componentName string, componentDir string) error { + if err := cdkScaffoldingPreflightCheck("Golang", cdkGolangScaffoldingRequirements); err != nil { + return err + } + + cli.OutputHuman("\nDeploying %s scaffolding:\n", color.HiMagentaString("Golang")) + + for _, file := range databox.ListFilesFromDir("/scaffoldings/golang") { + content, found := databox.Get(file) + if found { + // Create directory, if needed + subDir := filepath.Dir(file) + subDir = strings.TrimPrefix(subDir, "/scaffoldings/golang") + fileDir := filepath.Join(componentDir, subDir) + if subDir != "" { + if err := os.MkdirAll(fileDir, 0755); err != nil { + return errors.Wrap(err, "unable to create subdirectory from scaffolding") + } + } + + var ( + buff = &bytes.Buffer{} + fileName = filepath.Base(file) + filePath = filepath.Join(fileDir, fileName) + tmpl = template.Must(template.New(fileName).Delims("[[", "]]").Parse(string(content))) + cData = struct{ Component string }{ + Component: componentName, + } + ) + if err := tmpl.Execute(buff, cData); err != nil { + return errors.Wrap(err, "unable to generate files from go scaffolding") + } + if err := os.WriteFile(filePath, buff.Bytes(), os.ModePerm); err != nil { + cli.OutputChecklist(failureIcon, "Unable to write file %s\n", color.HiRedString(filePath)) + cli.Log.Debugw("unable to write file", "error", err) + } else { + cli.OutputChecklist(successIcon, "File %s deployed\n", color.HiYellowString(filePath)) + } + } + } + + // Missing tasks we can do for the developer + // + // 1) Change directory to Root path + // > Command: 'cd ...' + // 2) Initialize git repository + // > Command: 'git init' + // 3) Create your initial commit + // > Command: 'git add .; git commit -m "feat: init component"' + // 4) Dowload Go dependencies + // > Command: 'make go-vendor' + // 5) Build the component + // > Command: 'make build' + // 6) Run the component via the Lacework CLI + // > Command: 'lacework placeholder' + // + cli.StartProgress("Initializing Git repository...") + err := cdkInitGitRepo(componentDir) + cli.StopProgress() + if err != nil { + cli.OutputChecklist(failureIcon, "Unable to initialize Git repository\n") + cli.Log.Debugw("unable to initialize Git repository", "error", err) + } else { + cli.OutputChecklist(successIcon, "Git repository initialized\n") + } + + cli.StartProgress("Downloading Go dependencies...") + err = cdkGoVendor(componentDir) + cli.StopProgress() + if err != nil { + cli.OutputChecklist(failureIcon, "Unable to download Go dependencies\n") + cli.Log.Debugw("unable to download Go dependencies", "error", err) + } else { + cli.OutputChecklist(successIcon, "Go dependencies downloaded\n") + } + + cli.StartProgress("Building your component...") + err = cdkGoBuild(componentDir) + cli.StopProgress() + if err != nil { + cli.OutputChecklist(failureIcon, "Unable to build your Go component\n") + cli.Log.Debugw("unable to build your Go component", "error", err) + } else { + cli.OutputChecklist(successIcon, "Dev component built at %s\n", + color.HiYellowString(filepath.Join(componentDir, componentName))) + } + + cli.StartProgress("Verifying component...") + err = cdkGoRunVerify(componentName) + cli.StopProgress() + if err != nil { + // this is not on the developer, it's on this codebase, notify to fix it + cli.OutputChecklist(failureIcon, "Unable run scaffolding component\n") + cli.Log.Debugw("unable to run scaffolding component", "error", err) + } else { + cli.OutputChecklist(successIcon, "Component verified\n") + } + + cli.OutputHuman("\nDeployment completed! Time for %s\n", randomEmoji()) + return nil +} + +func cdkPythonScaffolding(componentName string, componentDir string) error { + if err := cdkScaffoldingPreflightCheck("Python", cdkPythonScaffoldingRequirements); err != nil { + return err + } + + cli.OutputHuman("\nDeploying %s scaffolding:\n", color.HiMagentaString("Python")) + + for _, file := range databox.ListFilesFromDir("/scaffoldings/python") { + content, found := databox.Get(file) + if found { + // Create directory, if needed + subDir := filepath.Dir(file) + subDir = strings.TrimPrefix(subDir, "/scaffoldings/python") + fileDir := filepath.Join(componentDir, subDir) + if subDir != "" { + if err := os.MkdirAll(fileDir, 0755); err != nil { + return errors.Wrap(err, "unable to create subdirectory from scaffolding") + } + } + + var ( + fileName = filepath.Base(file) + filePath = filepath.Join(fileDir, fileName) + ) + if err := os.WriteFile(filePath, content, os.ModePerm); err != nil { + cli.OutputChecklist(failureIcon, "Unable to write file %s\n", color.HiRedString(filePath)) + cli.Log.Debugw("unable to write file", "error", err) + } else { + cli.OutputChecklist(successIcon, "File %s deployed\n", color.HiYellowString(filePath)) + } + } + } + + cli.StartProgress("Initializing Git repository...") + err := cdkInitGitRepo(componentDir) + cli.StopProgress() + if err != nil { + cli.OutputChecklist(failureIcon, "Unable to initialize Git repository\n") + cli.Log.Debugw("unable to initialize Git repository", "error", err) + } else { + cli.OutputChecklist(successIcon, "Git repository initialized\n") + } + + // Poetry repository structure `project-name/src/project-name/__init__.py` + err = os.Rename(filepath.Join(componentDir, "src/package"), filepath.Join(componentDir, "src", componentName)) + if err != nil { + cli.OutputChecklist(failureIcon, "Unable to rename package directory\n") + cli.Log.Debugw("unable to rename package directory", "error", err) + return err + } + + cli.StartProgress("Poetry init...") + err = cdkExec(componentDir, + "poetry", + "init", + "--no-interaction", + // Because of https://github.com/python-poetry/poetry/issues/5975 + fmt.Sprintf("--name=%s", strings.ReplaceAll(componentName, "-", "")), + "--python=^3.11,<3.12", + "--dev-dependency=pyinstaller", + "--dev-dependency=poethepoet") + cli.StopProgress() + if err != nil { + cli.OutputChecklist(failureIcon, "Unable to initialize Poetry\n") + cli.Log.Debugw("unable to initialize Poetry", "error", err) + return err + } else { + cli.OutputChecklist(successIcon, "Poetry init\n") + } + + f, err := os.OpenFile(filepath.Join(componentDir, "pyproject.toml"), os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return err + } + _, err = f.WriteString("[tool.poe.tasks]\n") + if err != nil { + return err + } + + _, err = f.WriteString("build.shell = \"poetry run pyinstaller src/") + if err != nil { + return err + } + _, err = f.WriteString(fmt.Sprintf( + "%s/__main__.py --collect-submodules application -D --name %s --distpath dist;", + componentName, componentName, + )) + if err != nil { + return err + } + _, err = f.WriteString(fmt.Sprintf( + " mv dist/%s/* .\"\n", + componentName, + )) + if err != nil { + return err + } + _, err = f.WriteString(fmt.Sprintf( + "clean = \"rm -r build/ %s %s.spec\"\n", + componentName, componentName, + )) + if err != nil { + return err + } + err = f.Close() + if err != nil { + return err + } + + cli.StartProgress("Poetry install...") + err = cdkExec(componentDir, "poetry", "install") + cli.StopProgress() + if err != nil { + cli.OutputChecklist(failureIcon, "Unable to Poetry install\n") + cli.Log.Debugw("unable to Poetry install", "error", err) + return err + } else { + cli.OutputChecklist(successIcon, "Poetry install\n") + } + + cli.StartProgress("Building your component...") + err = cdkExec(componentDir, "poetry", "run", "poe", "build") + cli.StopProgress() + if err != nil { + cli.OutputChecklist(failureIcon, "Unable to build your Python component\n") + cli.Log.Debugw("unable to build your Python component", "error", err) + } else { + cli.OutputChecklist(successIcon, "Dev component built at %s\n", + color.HiYellowString(filepath.Join(componentDir, componentName))) + } + + cli.OutputHuman("\nDeployment completed! Time for %s\n", randomEmoji()) + return nil +} + +func cdkScaffoldingPreflightCheck(scaffolding string, requirements map[string]string) error { + errMessage := "" + for file, site := range requirements { + if _, err := exec.LookPath(file); err != nil { + errMessage += fmt.Sprintf(` +%s is required to create the %s scaffolding. Please install it before proceeding: + %s: %s`, file, scaffolding, file, site) + } + } + if errMessage != "" { + return errors.New(errMessage) + } + return nil +} + +func cdkExec(rootPath string, name string, args ...string) error { + var ( + vw = terminal.NewVerboseWriter(10) + cmd = exec.Command(name, args...) + ) + if _, err := vw.Write([]byte(fmt.Sprintf("Command: %s %v\n", name, args))); err != nil { + cli.Log.Debugw("unable to write to virtual terminal", "error", err) + } + cmd.Env = os.Environ() + cmd.Dir = rootPath + cmd.Stdout = vw + cmd.Stderr = vw + + defer func() { + if _, err := vw.Write([]byte("\n")); err != nil { + cli.Log.Debugw("unable to write to virtual terminal", "error", err) + } + }() + + return cmd.Run() +} + +func cdkInitGitRepo(rootPath string) error { + eMsg := "unable to initialize Git repo" + + repo, err := git.PlainInit(rootPath, false) + if err != nil { + return errors.Wrap(err, eMsg) + } + + w, err := repo.Worktree() + if err != nil { + return errors.Wrap(err, eMsg) + } + + _, err = w.Add(".") + if err != nil { + return errors.Wrap(err, eMsg) + } + + _, err = w.Commit("example go-git commit", &git.CommitOptions{ + Author: &object.Signature{ + Name: "Component Scaffolding", + Email: "support@lacework.net", + When: time.Now(), + }, + }) + + return err +} + +func cdkGoVendor(rootPath string) error { + var ( + vw = terminal.NewVerboseWriter(10) + cmd = exec.Command("make", "go-vendor") + ) + if _, err := vw.Write([]byte("Command: make go-vendor\n")); err != nil { + cli.Log.Debugw("unable to write to virtual terminal", "error", err) + } + cmd.Env = os.Environ() + cmd.Dir = rootPath + cmd.Stdout = vw + cmd.Stderr = vw + + // @afiune silly workaround to clean the spinner output + defer func() { + if _, err := vw.Write([]byte("\n")); err != nil { + cli.Log.Debugw("unable to write to virtual terminal", "error", err) + } + }() + return cmd.Run() +} + +func cdkGoBuild(rootPath string) error { + var ( + vw = terminal.NewVerboseWriter(10) + cmd = exec.Command("make", "build") + ) + if _, err := vw.Write([]byte("Command: make build\n")); err != nil { + cli.Log.Debugw("unable to write to virtual terminal", "error", err) + } + cmd.Env = os.Environ() + cmd.Dir = rootPath + cmd.Stdout = vw + cmd.Stderr = vw + return cmd.Run() +} + +func cdkGoRunVerify(componentName string) error { + var ( + vw = terminal.NewVerboseWriter(10) + cmd = exec.Command(laceworkCLIBinary(), componentName, "placeholder") + ) + _, err := vw.Write([]byte(fmt.Sprintf("Command: %s\n", strings.Join(cmd.Args, " ")))) + if err != nil { + cli.Log.Debugw("unable to write to virtual terminal", "error", err) + } + cmd.Env = os.Environ() + cmd.Stdout = vw + cmd.Stderr = vw + return cmd.Run() +} + +func laceworkCLIBinary() string { + if bin := os.Getenv("LW_CLI_BIN"); bin != "" { + return bin + } + + if os.Getenv("LW_CLI_INTEGRATION_MODE") != "" { + return fmt.Sprintf( + "lacework-cli-%s-%s", + runtime.GOOS, runtime.GOARCH, + ) + } + + return "lacework" +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/configure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/configure.go new file mode 100644 index 000000000..420c5d822 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/configure.go @@ -0,0 +1,426 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "os" + "regexp" + "sort" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/lacework/go-sdk/internal/format" + "github.com/lacework/go-sdk/lwconfig" + "github.com/lacework/go-sdk/lwdomain" +) + +var ( + // configureJsonFile is the API key file downloaded form the Lacework WebUI + configureJsonFile string + + // configureCmd represents the configure command + configureCmd = &cobra.Command{ + Use: "configure", + Short: "Configure the Lacework CLI", + Args: cobra.NoArgs, + Long: `Configure settings that the Lacework CLI uses to interact with the Lacework +platform. These include your Lacework account, API access key and secret. + +To create a set of API keys, log in to your Lacework account via WebUI and +navigate to Settings > API Keys and click + Create New. Enter a name for +the key and an optional description, then click Save. To get the secret key, +download the generated API key file. + +Use the flag --json_file to preload the downloaded API key file. + +If this command is run with no flags, the Lacework CLI will store all +settings under the default profile. The information in the default profile +is used any time you run a Lacework CLI command that doesn't explicitly +specify a profile to use. + +You can configure multiple profiles by using the --profile flag. If a +config file does not exist (the default location is ~/.lacework.toml), +the Lacework CLI will create it for you.`, + RunE: func(_ *cobra.Command, _ []string) error { + return runConfigureSetup() + }, + } + + configureListCmd = &cobra.Command{ + Use: "list", + Short: "List all configured profiles at ~/.lacework.toml", + Aliases: []string{"ls"}, + Args: cobra.NoArgs, + Long: `List all profiles configured into the config file ~/.lacework.toml + +To switch profiles permanently use the command. + + lacework configure switch-profile profile2`, + RunE: func(_ *cobra.Command, _ []string) error { + profiles, err := cli.LoadProfiles() + if err != nil { + return err + } + + cli.OutputHuman( + renderSimpleTable( + []string{"Profile", "Account", "Subaccount", "API Key", "API Secret", "V"}, + buildProfilesTableContent(cli.Profile, profiles), + ), + ) + + cli.OutputHuman("\nTo switch profiles use 'lacework configure switch-profile '\n") + return nil + }, + } + + configureGetCmd = &cobra.Command{ + Use: "show ", + Short: "Show current configuration data", + Args: cobra.ExactArgs(1), + Long: `Prints the current computed configuration data from the specified configuration +key. The order of precedence to compute the configuration is flags, environment +variables, and the configuration file ~/.lacework.toml. + +The available configuration keys are: + +* profile +* account +* subaccount +* api_secret +* api_key + +To show the configuration from a different profile, use the flag --profile. + + lacework configure show account --profile my-profile`, + RunE: func(_ *cobra.Command, args []string) error { + data, ok := showConfigurationDataFromKey(args[0]) + if !ok { + // TODO change this to be dynamic + return errors.New( + "unknown configuration key. (available: profile, account, subaccount, api_secret, api_key, version)") + } + + if data != "" { + cli.OutputHuman(data) + cli.OutputHuman("\n") + } + + return nil + }, + } +) + +func showConfigurationDataFromKey(key string) (string, bool) { + switch key { + case "profile": + return cli.Profile, true + case "account": + return cli.Account, true + case "subaccount": + return cli.Subaccount, true + case "api_secret": + return cli.Secret, true + case "api_key": + return cli.KeyID, true + case "version": + return fmt.Sprintf("%d", cli.CfgVersion), true + default: + return "", false + } +} + +func init() { + rootCmd.AddCommand(configureCmd) + configureCmd.AddCommand(configureListCmd) + configureCmd.AddCommand(configureGetCmd) + + configureCmd.Flags().StringVarP(&configureJsonFile, + "json_file", "j", "", "loads the API key JSON file downloaded from the WebUI", + ) +} + +func runConfigureSetup() error { + cli.Log.Debugw("configuring cli", "profile", cli.Profile) + + // make sure that the state is loaded to use during configuration + cli.loadStateFromViper() + + // if the Lacework account is empty, and the profile that is being configured is + // not the 'default' profile, auto-populate the account with the provided profile + if cli.Account == "" && cli.Profile != "default" { + cli.Account = cli.Profile + } + + if len(configureJsonFile) != 0 { + err := loadUIJsonFile(configureJsonFile) + if err != nil { + return errors.Wrap(err, "unable to load keys from the provided json file") + } + } else { + if match, _ := regexp.MatchString(".lacework.net", cli.Account); match { + d, err := lwdomain.New(cli.Account) + if err != nil { + return errors.Wrap(err, "unable to configure the command-line: account") + } + cli.Account = d.String() + } + } + + // all new configurations should default to version 2 + cli.CfgVersion = 2 + + newProfile := lwconfig.Profile{ + Version: cli.CfgVersion, + Subaccount: cli.Subaccount, + Account: cli.Account, + ApiKey: cli.KeyID, + ApiSecret: cli.Secret, + } + if cli.InteractiveMode() { + if err := promptConfigureSetup(&newProfile); err != nil { + return err + } + + // before trying to detect if the account is organizational or not, and to + // check if there are sub-accounts, we need to update the CLI settings + cli.Log.Debug("storing interactive information into the cli state") + cli.Secret = newProfile.ApiSecret + cli.KeyID = newProfile.ApiKey + if cli.Account != newProfile.Account { + // if the account provided by the interactive prompt is different, + // we need to remove the previous sub-account since it's a reconfiguration + cli.Account = newProfile.Account + cli.Subaccount = "" + newProfile.Subaccount = "" + } + + // generate a new API client to connect and check for sub-accounts + if err := cli.NewClient(); err != nil { + return err + } + + // get sub-accounts from organizational accounts + subaccount, err := getSubAccountForOrgAdmins() + if err != nil { + // We do NOT error here since API v2 is sending 500 errors + // for mortal users, we need to fix this on the server side + cli.Log.Warnw("unable to get sub-accounts for org admins", "error", err) + } else { + newProfile.Subaccount = subaccount + } + cli.OutputHuman("\n") + } + + if err := newProfile.Verify(); err != nil { + return errors.Wrap(err, "unable to configure the command-line") + } + + if err := lwconfig.StoreProfileAt(viper.ConfigFileUsed(), cli.Profile, newProfile); err != nil { + return errors.Wrap(err, "unable to configure the command-line") + } + + cli.OutputHuman("You are all set!\n") + return nil +} + +func promptConfigureSetup(newProfile *lwconfig.Profile) error { + questions := []*survey.Question{ + { + Name: "account", + Prompt: &survey.Input{ + Message: "Account:", + Default: cli.Account, + }, + Validate: promptRequiredStringLen(1, + "The account subdomain of URL is required. (i.e. .lacework.net)", + ), + Transform: func(ans interface{}) interface{} { + answer, ok := ans.(string) + if ok && strings.Contains(answer, ".lacework.net") { + + d, err := lwdomain.New(answer) + if err != nil { + cli.Log.Warn(err) + return answer + } + cli.OutputHuman("\nPassing full 'lacework.net' domain not required. Using '%s'\n", d.String()) + return d.String() + } + + return ans + }, + }, + { + Name: "api_key", + Prompt: &survey.Input{ + Message: "Access Key ID:", + Default: cli.KeyID, + }, + Validate: promptRequiredStringLen(lwconfig.ApiKeyMinLength, + fmt.Sprintf("The API access key id must have more than %d characters.", lwconfig.ApiKeyMinLength), + ), + }, + } + + secretQuest := &survey.Question{ + Name: "api_secret", + Validate: func(input interface{}) error { + str, ok := input.(string) + if !ok || len(str) < lwconfig.ApiSecretMinLength { + if len(str) == 0 && len(cli.Secret) != 0 { + return nil + } + return errors.New(fmt.Sprintf( + "The API secret access key must have more than %d characters.", + lwconfig.ApiSecretMinLength, + )) + } + return nil + }, + } + + secretMessage := "Secret Access Key:" + if len(cli.Secret) != 0 { + secretMessage = fmt.Sprintf("Secret Access Key: (%s)", format.Secret(4, cli.Secret)) + } + secretQuest.Prompt = &survey.Password{ + Message: secretMessage, + } + + err := survey.Ask(append(questions, secretQuest), newProfile, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + if len(newProfile.ApiSecret) == 0 { + newProfile.ApiSecret = cli.Secret + } + + return nil +} + +func getSubAccountForOrgAdmins() (string, error) { + cli.StartProgress(" Verifying credentials ...") + user, err := cli.LwApi.V2.UserProfile.Get() + cli.StopProgress() + if err != nil { + return "", errors.Wrap(err, "unable to access UserProfile endpoint") + } + + // We only ask for the sub-account if the account is an organizational account + // and it has at least one sub-account other than the primary account + if len(user.Data) != 0 && + user.Data[0].OrgAccount && + len(user.Data[0].SubAccountNames()) > 0 { + + var ( + subaccount string + primary = fmt.Sprintf("PRIMARY (%s)", cli.Account) + ) + err := survey.AskOne(&survey.Select{ + Message: "(Org Admins) Managing a sub-account?", + Default: strings.ToLower(cli.Subaccount), + Options: append([]string{primary}, user.Data[0].SubAccountNames()...), + }, &subaccount, survey.WithIcons(promptIconsFunc)) + if err != nil { + return "", err + } + + if subaccount != primary { + return subaccount, nil + } + } + + return "", nil +} + +// apiKeyDetails represents the details of an API key, we use this struct +// internally to unmarshal the JSON file provided by the Lacework WebUI +type apiKeyDetails struct { + Account string `json:"account,omitempty"` + SubAccount string `json:"subAccount,omitempty"` + KeyID string `json:"keyId"` + Secret string `json:"secret"` +} + +func loadUIJsonFile(file string) error { + cli.Log.Debugw("loading API key JSON file", "path", file) + jsonData, err := os.ReadFile(file) + if err != nil { + return err + } + + cli.Log.Debugw("JSON file", "raw", string(jsonData)) + var auth apiKeyDetails + err = json.Unmarshal(jsonData, &auth) + if err != nil { + return err + } + + cli.KeyID = auth.KeyID + cli.Secret = auth.Secret + cli.Subaccount = strings.ToLower(auth.SubAccount) + if auth.Account != "" { + d, err := lwdomain.New(auth.Account) + if err != nil { + return err + } + cli.Account = d.String() + } + + return nil +} + +func buildProfilesTableContent(current string, profiles lwconfig.Profiles) [][]string { + out := [][]string{} + for profile, creds := range profiles { + out = append(out, []string{ + profile, + creds.Account, + creds.Subaccount, + creds.ApiKey, + format.Secret(4, creds.ApiSecret), + fmt.Sprintf("%d", creds.Version), + }) + } + + // order by severity + sort.Slice(out, func(i, j int) bool { + return out[i][0] < out[j][0] + }) + + for i := range out { + if out[i][0] == current { + out[i][0] = fmt.Sprintf("> %s", out[i][0]) + } else { + out[i][0] = fmt.Sprintf(" %s", out[i][0]) + } + } + + return out +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/configure_switch_profile.go b/vendor/github.com/lacework/go-sdk/cli/cmd/configure_switch_profile.go new file mode 100644 index 000000000..1b17aa196 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/configure_switch_profile.go @@ -0,0 +1,79 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "os" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + configureSwitchProfileCmd = &cobra.Command{ + Use: "switch-profile ", + Aliases: []string{"switch", "use"}, + Args: cobra.ExactArgs(1), + Short: "Switch between configured profiles", + Long: `Switch between profiles configured into the config file ~/.lacework.toml + +An alternative to temporarily switch to a different profile in your current terminal +is to export the environment variable: + + ` + configureListCmdSetProfileEnv, + RunE: func(_ *cobra.Command, args []string) error { + if args[0] == "default" { + cli.Log.Debug("removing global profile cache, going back to default") + if err := cli.Cache.Erase("global/profile"); err != nil { + if !os.IsNotExist(err) { + return errors.Wrap(err, "unable to switch profile") + } + } + cli.OutputHuman("Profile switched back to default.\n") + return nil + } + + profiles, err := cli.LoadProfiles() + if err != nil { + return err + } + + if _, ok := profiles[args[0]]; ok { + cli.Log.Debugw("storing global profile cache", + "current_profile", cli.Profile, + "new_profile", args[0], + ) + if err := cli.Cache.Write("global/profile", []byte(args[0])); err != nil { + return errors.Wrap(err, "unable to switch profile") + } + cli.OutputHuman("Profile switched to '%s'.\n", args[0]) + return nil + } + + return errors.Errorf( + "Profile '%s' not found. Try 'lacework configure list' to see all configured profiles.", + args[0], + ) + }, + } +) + +func init() { + configureCmd.AddCommand(configureSwitchProfileCmd) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/container_registry.go b/vendor/github.com/lacework/go-sdk/cli/cmd/container_registry.go new file mode 100644 index 000000000..17ad68e61 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/container_registry.go @@ -0,0 +1,199 @@ +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + containerRegistryCommand = &cobra.Command{ + Use: "container-registry", + Aliases: []string{"container-registries", "cr"}, + Short: "Manage container registries", + Long: "Manage container registry integrations with Lacework", + } + + // containerRegistriesListCmd represents the list sub-command inside the container registries command + containerRegistryListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all available container registry integrations", + Args: cobra.NoArgs, + RunE: containerRegistryList, + } + + // containerRegistryShowCmd represents the show sub-command inside the container registry command + containerRegistryShowCmd = &cobra.Command{ + Use: "show", + Aliases: []string{"get"}, + Short: "Show a single container registry integration", + Args: cobra.ExactArgs(1), + RunE: containerRegistryShow, + } + + // containerRegistryCreateCmd represents the show sub-command inside the container registries command + containerRegistryCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new container registry integration", + Args: cobra.NoArgs, + RunE: containerRegistryCreate, + } + + // containerRegistryDeleteCmd represents the delete sub-command inside the container registries command + containerRegistryDeleteCmd = &cobra.Command{ + Use: "delete", + Aliases: []string{"rm"}, + Short: "Delete a container registry integration", + Args: cobra.ExactArgs(1), + RunE: containerRegistryDelete, + } +) + +func init() { + // add the container-registry command + rootCmd.AddCommand(containerRegistryCommand) + containerRegistryCommand.AddCommand(containerRegistryListCmd) + containerRegistryCommand.AddCommand(containerRegistryShowCmd) + containerRegistryCommand.AddCommand(containerRegistryCreateCmd) + containerRegistryCommand.AddCommand(containerRegistryDeleteCmd) +} + +func containerRegistriesToTable(containerRegistries []api.ContainerRegistryRaw) [][]string { + var out [][]string + for _, cadata := range containerRegistries { + out = append(out, []string{ + cadata.IntgGuid, + cadata.Name, + cadata.Type, + cadata.Status(), + cadata.StateString(), + }) + } + return out +} + +func containerRegistryList(_ *cobra.Command, _ []string) error { + containerRegistries, err := cli.LwApi.V2.ContainerRegistries.List() + + if err != nil { + return errors.Wrap(err, "unable to get container registries") + } + + if cli.JSONOutput() { + return cli.OutputJSON(containerRegistries.Data) + } + + if len(containerRegistries.Data) == 0 { + cli.OutputHuman("No container registries found.\n") + return nil + } + + cli.OutputHuman( + renderSimpleTable( + []string{"container registry GUID", "Name", "Type", "Status", "State"}, + containerRegistriesToTable(containerRegistries.Data), + ), + ) + return nil +} + +func containerRegistryShow(_ *cobra.Command, args []string) error { + var ( + containerRegistry api.ContainerRegistryResponse + out [][]string + ) + cli.StartProgress(" Fetching container registry...") + err := cli.LwApi.V2.ContainerRegistries.Get(args[0], &containerRegistry) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to retrieve container registry") + } + + out = append(out, []string{containerRegistry.Data.IntgGuid, + containerRegistry.Data.Name, + containerRegistry.Data.Type, + containerRegistry.Data.Status(), + containerRegistry.Data.StateString(), + }) + + if cli.JSONOutput() { + return cli.OutputJSON(containerRegistry.Data) + } + + cli.OutputHuman(renderSimpleTable([]string{"Container Registry GUID", "Name", "Type", "Status", "State"}, out)) + cli.OutputHuman("\n") + cli.OutputHuman(buildDetailsTable(containerRegistry.Data)) + + return nil +} + +func containerRegistryCreate(_ *cobra.Command, _ []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + + err := promptCreateContainerRegistry() + if err != nil { + return errors.Wrap(err, "unable to create container registry") + } + + cli.OutputHuman("The container registry was created.\n") + return nil +} + +func containerRegistryDelete(_ *cobra.Command, args []string) error { + cli.StartProgress(" Deleting container registry...") + err := cli.LwApi.V2.ContainerRegistries.Delete(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to delete container registry") + } + cli.OutputHuman("The container registry %s was deleted.\n", args[0]) + return nil +} + +func promptCreateContainerRegistry() error { + var ( + containerRegistry = "" + prompt = &survey.Select{ + Message: "Choose a container registry type to create: ", + Options: []string{ + "Docker Hub Registry", + "Docker V2 Registry", + "Amazon Container Registry (ECR)", + "Google Container Registry (GCR)", + "Google Artifact Registry (GAR)", + "Github Container Registry (GHCR)", + "Inline Scanner Container Registry", + "Proxy Scanner Container Registry", + }, + } + err = survey.AskOne(prompt, &containerRegistry) + ) + if err != nil { + return err + } + + switch containerRegistry { + case "Docker Hub Registry": + return createDockerHubIntegration() + case "Docker V2 Registry": + return createDockerV2Integration() + case "Amazon Container Registry (ECR)": + return createAwsEcrIntegration() + case "Google Artifact Registry (GAR)": + return createGarIntegration() + case "Inline Scanner Container Registry": + return createInlineScannerIntegration() + case "Proxy Scanner Container Registry": + return createProxyScannerIntegration() + case "Github Container Registry (GHCR)": + return createGhcrIntegration() + case "Google Container Registry (GCR)": + return createGcrIntegration() + default: + return errors.New("unknown container registry type") + } +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/content_library.go b/vendor/github.com/lacework/go-sdk/cli/cmd/content_library.go new file mode 100644 index 000000000..b274de98a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/content_library.go @@ -0,0 +1,205 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + + "github.com/pkg/errors" + + "github.com/lacework/go-sdk/lwcomponent" +) + +const ( + lclComponentName string = "content-library" + lclIndexPath string = "content.index" +) + +type LCLContentType string + +const ( + LCLQueryType LCLContentType = "query" + LCLPolicyType LCLContentType = "policy" +) + +type LCLReference struct { + ID string `json:"id"` + Type LCLContentType `json:"content_type"` + Path string `json:"path"` + URI string `json:"uri"` +} + +func getPolicyReference(refs []LCLReference) (LCLReference, error) { + for i := range refs { + if refs[i].Type == LCLPolicyType { + return refs[i], nil + } + } + return LCLReference{}, errors.New("policy reference not found") +} + +type LCLQuery struct { + References []LCLReference `json:"references"` +} + +type LCLPolicy struct { + PolicyID string `json:"policyId"` + Title string `json:"title"` + Description string `json:"description"` + Tags []string `json:"tags"` + QueryID string `json:"queryId"` + References []LCLReference `json:"references"` +} + +type LaceworkContentLibrary struct { + Component *lwcomponent.Component + Queries map[string]LCLQuery `json:"queries"` + Policies map[string]LCLPolicy `json:"policies"` + PolicyTags map[string][]string `json:"policy_tags"` +} + +func (c *cliState) isLCLInstalled() bool { + return c.IsComponentInstalled(lclComponentName) +} + +func (c *cliState) LoadLCL() (*LaceworkContentLibrary, error) { + var ( + baseErr = "unable to load Lacework Content Library" + lcl = new(LaceworkContentLibrary) + found bool + ) + + if c.LwComponents == nil { + return lcl, errors.New(baseErr) + } + + lcl.Component, found = c.LwComponents.GetComponent(lclComponentName) + if !found { + return lcl, errors.Wrap(errors.New("component not installed"), baseErr) + } + + index, err := lcl.run(lclIndexPath) + if err != nil { + return new(LaceworkContentLibrary), errors.Wrap(err, baseErr) + } + + if err := json.Unmarshal([]byte(index), lcl); err != nil { + return new(LaceworkContentLibrary), errors.Wrap(err, baseErr) + } + + for policyID, policy := range lcl.Policies { + for i := range policy.References { + if policy.References[i].Type == LCLQueryType { + policy.QueryID = policy.References[i].ID + } + if policy.References[i].Type == LCLPolicyType { + policy.PolicyID = policy.References[i].ID + } + } + lcl.Policies[policyID] = policy + } + + return lcl, nil +} + +func (lcl *LaceworkContentLibrary) run(path string) (string, error) { + if lcl.Component == nil || !lcl.Component.IsInstalled() { + return "", errors.New("Lacework Content Library is not installed") + } + stdout, _, err := lcl.Component.RunAndReturn([]string{path}, nil) + return stdout, err +} + +func (lcl *LaceworkContentLibrary) getReferenceForQuery(id string) (LCLReference, error) { + var ref LCLReference + + if id == "" { + return ref, errors.New("query ID must be provided") + } + if _, ok := lcl.Queries[id]; !ok { + return ref, errors.New("query does not exist in library") + } + if len(lcl.Queries[id].References) < 1 { + return ref, errors.New("query exists but is malformed") + } + return lcl.Queries[id].References[0], nil +} + +func (lcl *LaceworkContentLibrary) getReferencesForPolicy(id string) ([]LCLReference, error) { + var refs []LCLReference + + if id == "" { + return refs, errors.New("policy ID must be provided") + } + if _, ok := lcl.Policies[id]; !ok { + return refs, errors.New("policy does not exist in library") + } + if len(lcl.Policies[id].References) < 2 { + return refs, errors.New("policy exists but is malformed") + } + return lcl.Policies[id].References, nil +} + +func (lcl *LaceworkContentLibrary) GetQuery(id string) (string, error) { + // get query reference + ref, err := lcl.getReferenceForQuery(id) + if err != nil { + return "", err + } + // check query path + if ref.Path == "" { + return "", errors.New("query exists but is malformed") + } + // get query string + return lcl.run(ref.Path) +} + +func (lcl *LaceworkContentLibrary) GetPolicy(id string) (string, error) { + // get policy references + refs, err := lcl.getReferencesForPolicy(id) + if err != nil { + return "", err + } + ref, err := getPolicyReference(refs) + if err != nil || ref.Path == "" { + return "", errors.New("policy exists but is malformed") + } + // get policy string + return lcl.run(ref.Path) +} + +func (lcl *LaceworkContentLibrary) GetPoliciesByTag(t string) map[string]LCLPolicy { + var ( + policies map[string]LCLPolicy = map[string]LCLPolicy{} + policyIDs []string + ok bool + ) + + if policyIDs, ok = lcl.PolicyTags[t]; !ok { + return policies + } + + for _, policyID := range policyIDs { + if lclPolicy, ok := lcl.Policies[policyID]; ok { + policies[policyID] = lclPolicy + } + } + + return policies +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/docs.go b/vendor/github.com/lacework/go-sdk/cli/cmd/docs.go new file mode 100644 index 000000000..950dc22e7 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/docs.go @@ -0,0 +1,98 @@ +//go:generate go run ../../scripts/docs/doc_gen.go +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// The commands behind the Lacework command-line interface (CLI) +package cmd + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" +) + +var ( + + // docsLink is the custom link used to render internal links + docsLink = "" + + // docsCmd is a hidden command that generates automatic documentation in Markdown + docsCmd = &cobra.Command{ + Use: "docs ", + Hidden: true, + Short: "Generate Markdown documentation", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + return GenerateMarkdownDocs(args[0]) + }, + } + + // headerTemplate adds front matter to generated documentation, this is how + // we automatically generate documentation at docs.lacework.com + headerTemplate = `--- +title: "%s" +slug: %s +hide_title: true +--- + +` +) + +func init() { + rootCmd.AddCommand(docsCmd) + docsCmd.Flags().StringVarP(&docsLink, + "link", "l", "", "customize the rendered internal links to the commands") +} + +func GenerateMarkdownDocs(location string) error { + // remove location before generating to ensure deleted commands are removed + err := os.RemoveAll(location) + if err != nil { + return err + } + + if err = os.MkdirAll(location, 0755); err != nil { + return err + } + + // given a filename, linkHandler is used to customize the rendered internal links + // to the commands, only if docsLinks was provided + linkHandler := func(name string) string { + if docsLink != "" { + base := strings.TrimSuffix(name, path.Ext(name)) + return docsLink + strings.ToLower(base) + "/" + } + return name + } + + // filePrepender uses headerTemplate to prepend front matter to the rendered Markdown + filePrepender := func(filename string) string { + var ( + name = filepath.Base(filename) + base = strings.TrimSuffix(name, path.Ext(name)) + ) + return fmt.Sprintf(headerTemplate, strings.Replace(base, "_", " ", -1), base) + } + + return doc.GenMarkdownTreeCustom(rootCmd, location, filePrepender, linkHandler) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/emoji.go b/vendor/github.com/lacework/go-sdk/cli/cmd/emoji.go new file mode 100644 index 000000000..92f0b2f1e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/emoji.go @@ -0,0 +1,30 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "math/rand" + "time" +) + +var emojis = []string{":beer:", ":pizza:", ":taco:"} + +func init() { + rand.New(rand.NewSource(time.Now().UTC().UnixNano())) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_unix.go b/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_unix.go new file mode 100644 index 000000000..1bb77773f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_unix.go @@ -0,0 +1,31 @@ +//go:build !windows + +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "math/rand" + + "github.com/kyokomi/emoji/v2" +) + +func randomEmoji() string { + return emoji.Sprint(emojis[rand.Intn(len(emojis))]) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_windows.go b/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_windows.go new file mode 100644 index 000000000..eaa48e542 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_windows.go @@ -0,0 +1,27 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "math/rand" +) + +func randomEmoji() string { + return emojis[rand.Intn(len(emojis))] +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/errors.go b/vendor/github.com/lacework/go-sdk/cli/cmd/errors.go new file mode 100644 index 000000000..5b318c695 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/errors.go @@ -0,0 +1,137 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/lwseverity" + "github.com/pkg/errors" +) + +type vulnerabilityPolicyError struct { + SeverityRating string + FixableSeverityRating string + FixableVulnCount int32 + FailOnSeverity string + FailOnFixable bool + ExitCode int + Message string + Err error +} + +func NewVulnerabilityPolicyErrorV2( + assessment api.VulnerabilitiesContainersResponse, + failOnSeverity string, failOnFixable bool, +) *vulnerabilityPolicyError { + return &vulnerabilityPolicyError{ + SeverityRating: assessment.HighestSeverity(), + FixableSeverityRating: assessment.HighestFixableSeverity(), + FixableVulnCount: assessment.TotalFixableVulnerabilities(), + FailOnSeverity: failOnSeverity, + FailOnFixable: failOnFixable, + // we use a default exit code that might change + // during NonCompliant() or Compliant() + ExitCode: 9, + } +} + +func NewVulnerabilityPolicyError( + assessment api.VulnerabilityAssessment, + failOnSeverity string, failOnFixable bool, +) *vulnerabilityPolicyError { + return &vulnerabilityPolicyError{ + SeverityRating: assessment.HighestSeverity(), + FixableSeverityRating: assessment.HighestFixableSeverity(), + FixableVulnCount: assessment.TotalFixableVulnerabilities(), + FailOnSeverity: failOnSeverity, + FailOnFixable: failOnFixable, + // we use a default exit code that might change + // during NonCompliant() or Compliant() + ExitCode: 9, + } +} + +// Example of an error message sent to the end-user: +// +// ERROR (FAIL-ON): fixable vulnerabilities found with threshold 'critical' (exit code: 9) +func (e *vulnerabilityPolicyError) Error() string { + if e.ExitCode == 0 { + return "" + } + return fmt.Sprintf("(FAIL-ON): %s (exit code: %d)", e.Message, e.ExitCode) +} + +func (e *vulnerabilityPolicyError) Unwrap() error { + return e.Err +} + +func (e *vulnerabilityPolicyError) NonCompliant() bool { + return !e.validate() +} + +func (e *vulnerabilityPolicyError) Compliant() bool { + return e.validate() +} + +// validate returns true if the error policy is compliant, +// that is, when the provided assessment doesn't meet the +// thresholds. It returns false if the policy is NOT compliant +func (e *vulnerabilityPolicyError) validate() bool { + severityRating, _ := lwseverity.Normalize(e.SeverityRating) + fixableSeverityRating, _ := lwseverity.Normalize(e.FixableSeverityRating) + threshold, _ := lwseverity.Normalize(e.FailOnSeverity) + + cli.Log.Debugw("validating policy", + "severity_rating", severityRating, + "fixable_severity_rating", fixableSeverityRating, + "threshold", threshold, + "fixable_vuln_count", e.FixableVulnCount, + ) + if e.FailOnSeverity == "" && e.FailOnFixable && e.FixableVulnCount > 0 { + e.Message = "fixable vulnerabilities found" + return false + } + + if e.FailOnFixable && e.FixableVulnCount > 0 && fixableSeverityRating <= threshold { + e.Message = fmt.Sprintf( + "fixable vulnerabilities found with threshold '%s'", + e.FailOnSeverity) + return false + } + + if e.FailOnSeverity != "" && (severityRating <= threshold && severityRating != 0) { + e.Message = fmt.Sprintf( + "vulnerabilities found with threshold '%s'", + e.FailOnSeverity) + return false + } + + e.Message = "Compliant policy" + e.ExitCode = 0 + return true +} + +func yikes(msg string) error { + return errors.Wrap( + errors.New("something went pretty wrong here, contact support@lacework.net"), + msg, + ) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/errors_lql.go b/vendor/github.com/lacework/go-sdk/cli/cmd/errors_lql.go new file mode 100644 index 000000000..2d5c02b50 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/errors_lql.go @@ -0,0 +1,101 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + + "github.com/lacework/go-sdk/internal/failon" +) + +type queryFailonError struct { + ExitCode int + Message string + Err error + FailonCount string + Count int +} + +func NewQueryFailonError(failonCount string, count int) *queryFailonError { + return &queryFailonError{ + FailonCount: failonCount, + Count: count, + // we use a default exit code that might change + // during NonCompliant() or Compliant() + ExitCode: 9, + } +} + +// Example of an error message sent to the end-user: +// +// ERROR (FAIL-ON): query matched fail_on_count expression [count:5] [expr:!=0] (exit code: 9) +func (e *queryFailonError) Error() string { + if e.ExitCode == 0 { + return "" + } + return fmt.Sprintf("(FAIL-ON): %s (exit code: %d)", e.Message, e.ExitCode) +} + +func (e *queryFailonError) Unwrap() error { + return e.Err +} + +func (e *queryFailonError) NonCompliant() bool { + return !e.validate() +} + +func (e *queryFailonError) Compliant() bool { + return e.validate() +} + +// validate returns true if the error query is compliant, that is, +// when the provided count doesn't match the provided fail on count +// expression. It returns false if the query count matches +func (e *queryFailonError) validate() bool { + cli.Log.Debugw("validating policy", + "count", e.Count, + "fail_on_count", e.FailonCount, + ) + + co := failon.CountOperation{} + if err := co.Parse(e.FailonCount); err != nil { + e.ExitCode = 123 + e.Message = err.Error() + return false + } + + isFail, err := co.IsFail(e.Count) + if err != nil { + e.ExitCode = 123 + e.Message = err.Error() + return false + } + + if isFail { + e.Message = fmt.Sprintf( + "query matched fail_on_count expression. [count:%d] [expr:%s]", + e.Count, e.FailonCount, + ) + return false + } + + e.Message = "Compliant query" + e.ExitCode = 0 + return true +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/flags.go b/vendor/github.com/lacework/go-sdk/cli/cmd/flags.go new file mode 100644 index 000000000..7b96e9b69 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/flags.go @@ -0,0 +1,124 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "reflect" + "strings" + + "github.com/pkg/errors" +) + +// Used to store the list of available filters from a CLI command +// +// E.g. get available filters for a cobra.Command.Long +// +// ```go +// +// dummyCmdState = struct { +// // The available filters +// AvailableFilters CmdFilters +// +// // List of filters to apply +// Filters []string +// }{} +// +// dummyCmdState := &cobra.Command{ +// Long: `The available keys for this command are: +// +// ` + stringSliceToMarkdownList( +// +// dummyCmdState.AvailableFilters.GetFiltersFrom( +// api.MachineDetailEntity{}, +// ), +// +// )} +// ``` +type CmdFilters struct { + Filters []string +} + +func (f *CmdFilters) GetFiltersFrom(T interface{}) []string { + if len(f.Filters) == 0 { + f.Filters = getFiltersFrom(T, "") + } + + return f.Filters +} + +func getFiltersFrom(T interface{}, prefix string) []string { + var ( + filters = []string{} + rt = reflect.TypeOf(T) + rv = reflect.Indirect(reflect.ValueOf(T)) + ) + + for i := 0; i < rt.NumField(); i++ { + v := rv.Field(i) + + // only use a field if it has a 'json' tag + if fieldJSON, ok := rt.Field(i).Tag.Lookup("json"); ok { + // split fieldJSON by comma to handle omitempty/omitzero modifiers + fieldJSONSlice := strings.Split(fieldJSON, ",") + if len(fieldJSONSlice) > 0 { + fieldJSON = fieldJSONSlice[0] + } + + // if there is any prefix, we need to append it to the JSON field + if prefix != "" { + fieldJSON = fmt.Sprintf("%s.%s", prefix, fieldJSON) + } + + // if the field is a struct we recursively get the fields inside + if v.Kind() == reflect.Struct { + filters = append(filters, getFiltersFrom(v.Interface(), fieldJSON)...) + } else { + filters = append(filters, fieldJSON) + } + + } + } + + return filters +} + +// validateKeyValuePairs returns and error if any filter is malformed +func validateKeyValuePairs(filters []string) error { + for _, pair := range filters { + kv := strings.Split(pair, ":") + if len(kv) != 2 || kv[0] == "" || kv[1] == "" { + return errors.Errorf("malformed filter '%s'. Expected format 'key:value'", pair) + } + } + return nil +} + +// stringSliceToMarkdownList display a list of filters in Markdown format. +// +// E.g. The list []string{"a","b","c"} will return +// - a +// - b +// - c +func stringSliceToMarkdownList(filters []string) string { + if len(filters) == 0 { + return "" + } + return fmt.Sprintf(" * %s", strings.Join(filters, "\n * ")) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/gcp.go new file mode 100644 index 000000000..1e2057584 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/gcp.go @@ -0,0 +1,111 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "context" + "fmt" + "net/http" + + "cloud.google.com/go/compute/metadata" + instances "github.com/lacework/go-sdk/lwcloud/gcp/resources/instances" + resources "github.com/lacework/go-sdk/lwcloud/gcp/resources/models" + "github.com/lacework/go-sdk/lwrunner" +) + +// gcpDescribeInstancesInProject takes a GCP project ID and the username of an IAM username in the +// project associated with the credentials in use as input, and outputs a list of GCP instances +// in the project. It reads the flag value `InstallIncludeRegions` if populated to filter on regions, +// and the flag values `InstallTag` and `InstallTagKey` if populated to filter on tag. +func gcpDescribeInstancesInProject(parentUsername, projectID string) ([]*lwrunner.GCPRunner, error) { + var discoveredInstances []resources.InstanceDetails + var err error + + // Filter instances by region, if provided as CLI flag value + if len(agentCmdState.InstallIncludeRegions) > 0 { + cli.Log.Debugw("filtering on regions", "regions", agentCmdState.InstallIncludeRegions) + for _, region := range agentCmdState.InstallIncludeRegions { + discoveredInstances, err = instances.EnumerateInstancesInProject(context.Background(), nil, region, projectID) + if err != nil { + return nil, err + } + } + } else { + discoveredInstances, err = instances.EnumerateInstancesInProject(context.Background(), nil, "", projectID) + if err != nil { + return nil, err + } + } + cli.Log.Debugw("found instances", "instances", discoveredInstances) + + runners := []*lwrunner.GCPRunner{} + + for _, instance := range discoveredInstances { + // Filter out instances that are not in the RUNNING state + if instance.State != "RUNNING" { + continue + } + + // Filter instances by tag and tag key, if provided as CLI flag values + // NB that tags are another name for GCP "metadata" + if len(agentCmdState.InstallTag) == 2 { + cli.Log.Debugw("filtering on tag (metadata)", "tag", agentCmdState.InstallTag) + if tagVal, ok := instance.Props[agentCmdState.InstallTag[0]]; ok { // is tag key present? + if tagVal != agentCmdState.InstallTag[1] { // does tag value match? + continue // skip this instance if filter tag key and value are not present + } + } else { // tag key was not present, skip + continue + } + } + if agentCmdState.InstallTagKey != "" { + cli.Log.Debugw("filtering on tag (metadata) key", "tag key", agentCmdState.InstallTagKey) + if _, ok := instance.Props[agentCmdState.InstallTagKey]; !ok { + continue // skip this instance if filter tag key is not present + } + } + runner, err := lwrunner.NewGCPRunner( + instance.PublicIP, + parentUsername, + projectID, + instance.Zone, + instance.InstanceID, + verifyHostCallback, + ) + if err != nil { + return nil, err + } + runners = append(runners, runner) + } + cli.Log.Debugw("filtered list of runners", "runners", runners) + + return runners, nil +} + +func gcpGetProjectIDFromMetadataServer() (string, error) { + client := metadata.NewClient(&http.Client{}) + + projectID, err := client.ProjectID() + if err != nil { + err = fmt.Errorf("cannot get project details due to %s", err.Error()) + return "", err + } + + return projectID, nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate.go new file mode 100644 index 000000000..6bb5b640e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate.go @@ -0,0 +1,323 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + QuestionRunTfPlan = "Run Terraform plan now?" + QuestionUsePreviousCache = "Previous IaC generation detected, load cached values?" + + iacGenerateTfCommand = &cobra.Command{ + Use: "iac-generate", + Aliases: []string{"iac"}, + Short: "Create IaC code", + Long: "Create IaC content for various different cloud environments and configurations", + Deprecated: "This command is deprecated. Use 'generate'.", + Hidden: true, + } + + generateTfCommand = &cobra.Command{ + Use: "generate", + Aliases: []string{"gen"}, + Short: "Generate code to onboard your account", + Long: `Generate code to onboard your account and deploy Lacework into various cloud environments. + +This command creates Infrastructure as Code (IaC) in the form of Terraform HCL, with the option of running +Terraform and deploying Lacework into AWS, Azure, GCP or OCI. +`, + } +) + +func init() { + rootCmd.AddCommand(generateTfCommand) + + //Deprecated + cloudAccountCommand.AddCommand(iacGenerateTfCommand) + initGenerateAwsTfCommandFlags() + initGenerateGcpTfCommandFlags() + initGenerateAzureTfCommandFlags() + iacGenerateTfCommand.AddCommand(generateAwsTfCommand) + iacGenerateTfCommand.AddCommand(generateGcpTfCommand) + iacGenerateTfCommand.AddCommand(generateAzureTfCommand) + + // aws subcommands + generateAwsTfCommand.AddCommand(generateAwsControlTowerTfCommand) + + // Common flags + generateTfCommand.PersistentFlags().Bool( + "apply", + false, + "run terraform apply without executing plan or prompting", + ) + generateTfCommand.PersistentFlags().String( + "output", + "", + "location to write generated content", + ) +} + +type SurveyQuestionWithValidationArgs struct { + Prompt survey.Prompt + // Supplied checks can be used to validate IF the question should be asked + Checks []*bool + Response interface{} + Opts []survey.AskOpt + Required bool + Icon string +} + +// SurveyQuestionInteractiveOnly Prompt use for question, only if the CLI is in interactive mode +func SurveyQuestionInteractiveOnly(question SurveyQuestionWithValidationArgs) error { + // Do validations pass? + ok := true + for _, v := range question.Checks { + if !*v { + ok = false + } + } + + // If the optional check doesn't pass, skip + if !ok { + return nil + } + + // If required is set, add that question opt + if question.Required { + question.Opts = append(question.Opts, survey.WithValidator(survey.Required)) + } + + // Add custom icon + if question.Icon != "" { + question.Opts = append(question.Opts, survey.WithIcons(customPromptIconsFunc(question.Icon))) + } else { + question.Opts = append(question.Opts, survey.WithIcons(promptIconsFunc)) + } + + // If noninteractive is not set, ask the question + if !cli.nonInteractive { + err := survey.AskOne(question.Prompt, question.Response, question.Opts...) + if err != nil { + return err + } + } + + return nil +} + +// SurveyMultipleQuestionWithValidation Prompt for many values at once +// +// checks: If supplied check(s) are true, questions will be asked +func SurveyMultipleQuestionWithValidation(questions []SurveyQuestionWithValidationArgs, checks ...bool) error { + // Do validations pass? + ok := true + for _, v := range checks { + if !v { + ok = false + } + } + + // Ask questions + if ok { + for _, qs := range questions { + if err := SurveyQuestionInteractiveOnly(qs); err != nil { + return err + } + } + } + return nil +} + +// determineOutputDirPath get output directory location based on how the output location was set +func determineOutputDirPath(location string, cloud string) (string, error) { + // determine code output path + dirname, err := os.UserHomeDir() + if err != nil { + return "", err + } + + // If location was passed, return that location + if location != "" { + return filepath.FromSlash(location), nil + } + + // If location was not passed, assemble it with lacework from os homedir + return filepath.FromSlash(fmt.Sprintf("%s/%s/%s", dirname, "lacework", cloud)), nil +} + +// writeHclOutputPreCheck Prompt for confirmation if main.tf already exists; return true to continue +func writeHclOutputPreCheck(outputLocation string, cloud string) (bool, error) { + // If noninteractive, continue + if !cli.InteractiveMode() { + return true, nil + } + + outputDir, err := determineOutputDirPath(outputLocation, cloud) + if err != nil { + return false, err + } + + hclPath := filepath.FromSlash(fmt.Sprintf("%s/main.tf", outputDir)) + + // If the file doesn't exist, carry on + if _, err := os.Stat(hclPath); os.IsNotExist(err) { + return true, nil + } + + // If it does exist; confirm overwrite + answer := false + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: fmt.Sprintf("%s already exists, overwrite?", hclPath)}, + Response: &answer, + }); err != nil { + return false, err + } + + return answer, nil +} + +// writeHclOutput Write HCL output +func writeHclOutput(hcl string, location string, cloud string) (string, error) { + // Determine write location + dirname, err := determineOutputDirPath(location, cloud) + if err != nil { + return "", err + } + + // check if output location exists and if it's a file + outputDirLocation, err := os.Stat(dirname) + if !os.IsNotExist(err) && !outputDirLocation.IsDir() { + return "", fmt.Errorf("output location %s already exists and is a file", dirname) + } + + // Create directory, if needed + if os.IsNotExist(err) { + directory := filepath.FromSlash(dirname) + if _, err := os.Stat(directory); os.IsNotExist(err) { + err = os.MkdirAll(directory, 0700) + if err != nil { + return "", err + } + } + } + + // Create HCL file + outputLocation := filepath.FromSlash(fmt.Sprintf("%s/main.tf", dirname)) + err = os.WriteFile( + filepath.FromSlash(outputLocation), + []byte(hcl), + 0700, + ) + if err != nil { + return "", err + } + + cli.StopProgress() + return outputLocation, nil +} + +// validateOutputLocation This function used to validate provided output location exists and is a directory +func validateOutputLocation(dirname string) error { + // If output location was supplied, validate it exists + if dirname != "" { + outputLocation, err := os.Stat(dirname) + if err != nil && !os.IsNotExist(err) { + return errors.Wrap(err, "could not access specified output location") + } + + if err == nil && !outputLocation.IsDir() { + return errors.New("output location already exists and is a file") + } + } + + return nil +} + +// validateStringWithRegex create survey.Validator for string with regex +func validateStringWithRegex(val interface{}, regex string, errorString string) error { + switch value := val.(type) { + case string: + // if value doesn't match regex, return invalid arn + ok, err := regexp.MatchString(regex, value) + if err != nil { + return errors.Wrap(err, "failed to validate input") + } + + if !ok { + return errors.New(errorString) + } + default: + // if the value passed is not a string + return errors.New("value must be a string") + } + + return nil +} + +// Used to test if path supplied for output exists +func validPathExists(val interface{}) error { + switch value := val.(type) { + case string: + // Test if supplied path exists + if err := validateOutputLocation(value); err != nil { + return err + } + default: + // if the value passed is not a string + return errors.New("value must be a string") + } + + return nil +} + +// writeGeneratedCodeToLocation Write-out generated code to location specified +func writeGeneratedCodeToLocation(cmd *cobra.Command, hcl string, cloud string) (string, string, error) { + //dirname, ok, location := "", false, "" + // Write-out generated code to location specified + dirname, err := cmd.Flags().GetString("output") + if err != nil { + return dirname, "", errors.Wrap(err, "failed to parse output location") + } + + ok, err := writeHclOutputPreCheck(dirname, cloud) + if err != nil { + return dirname, "", errors.Wrap(err, "failed to validate output location") + } + + if !ok { + return dirname, "", errors.Wrap(err, "aborting to avoid overwriting existing terraform code") + } + + location, err := writeHclOutput(hcl, dirname, cloud) + if err != nil { + return dirname, location, errors.Wrap(err, "failed to write terraform code to disk") + } + + return dirname, location, nil +} + +// executionPreRunChecks Execution pre-run check +func executionPreRunChecks(dirname string, locationDir string, cloud string) error { + ok, err := TerraformExecutePreRunCheck(dirname, cloud) + if err != nil { + return errors.Wrap(err, "failed to check for existing terraform state") + } + + if !ok { + cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) + return nil + } + + if err := TerraformPlanAndExecute(locationDir); err != nil { + return errors.Wrap(err, "failed to run terraform apply") + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws.go new file mode 100644 index 000000000..9ac57d32e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws.go @@ -0,0 +1,1645 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + "time" + + "github.com/imdario/mergo" + "github.com/spf13/cobra" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/internal/format" + "github.com/lacework/go-sdk/lwgenerate/aws" + "github.com/pkg/errors" +) + +// Question labels +const ( + IconAgentless = "[Agentless]" + IconConfig = "[Configuration]" + IconCloudTrail = "[CloudTrail]" +) + +var ( + // Define question text here so they can be reused in testing + // Core questions + QuestionEnableAwsOrganization = "Enable integrations for AWS organization?" + QuestionMainAwsProfile = "Main AWS account profile:" + QuestionMainAwsRegion = "Main AWS account region:" + + // Agentless questions + QuestionEnableAgentless = "Enable Agentless integration?" + QuestionAgentlessManagementAccountID = "AWS management account ID:" + QuestionAgentlessManagementAccountRegion = "AWS management account region:" + + QuestionAgentlessScanningAccountProfile = "Scanning AWS account profile:" + QuestionAgentlessScanningAccountRegion = "Scanning AWS account region:" + QuestionAgentlessScanningAccountAddMore = "Add another scanning AWS account?" + + QuestionAgentlessScanningAccountsReplace = "Currently configured scanning accounts: %s, replace?" + QuestionAgentlessMonitoredAccountIDs = "Monitored AWS account ID list:" + QuestionAgentlessMonitoredAccountIDsHelp = "Please provide a comma separated list that may " + + "contain account IDs, OUs, or the organization root (e.g. 123456789000,ou-abcd-12345678,r-abcd)." + + QuestionAgentlessMonitoredAccountProfile = "Monitored AWS account profile:" + QuestionAgentlessMonitoredAccountRegion = "Monitored AWS account region:" + + // Config questions + QuestionEnableConfig = "Enable Configuration integration?" + QuestionConfigAdditionalAccountProfile = "Additional AWS account profile:" + QuestionConfigAdditionalAccountRegion = "Additional AWS account region:" + QuestionConfigAdditionalAccountsReplace = "Currently configured additional accounts: %s, replace?" + QuestionConfigAdditionalAccountAddMore = "Add another AWS account?" + + // Config Org questions + QuestionConfigOrgLWAccount = "Lacework account:" + QuestionConfigOrgLWSubaccount = "Lacework subaccount (optional):" + QuestionConfigOrgLWAccessKeyId = "Lacework access key ID:" + QuestionConfigOrgLWSecretKey = "Lacework secret key:" + QuestionConfigOrgId = "AWS organization ID:" + QuestionConfigOrgUnits = "AWS organization units (multiple can be supplied comma separated):" + QuestionConfigOrgCfResourcePrefix = "Cloudformation resource prefix:" + + // CloudTrail questions + QuestionEnableCloudtrail = "Enable CloudTrail integration?" + QuestionCloudtrailName = "Existing trail name:" + QuestionCloudtrailAdvanced = "Configure advanced options?" + + // CloudTrail Control Tower questions + QuestionControlTower = "Is your AWS organzation using Control Tower?" + QuestionControlTowerS3BucketArn = "AWS Control Tower S3 bucket ARN:" + QuestionControlTowerSnsTopicArn = "AWS Control Tower SNS topic ARN:" + QuestionControlTowerAuditAccountProfile = "AWS Control Tower audit account profile:" + QuestionControlTowerAuditAccountRegion = "AWS Control Tower audit account region:" + QuestionControlTowerLogArchiveAccountProfile = "AWS Control Tower log archive account profile:" + QuestionControlTowerLogArchiveAccountRegion = "AWS Control Tower log archive account region:" + QuestionControlTowerKmsKeyArn = "AWS Control Tower custom KMS Key ARN (optional):" + + // CloudTrail advanced options + OptCloudtrailMessage = "Which options would you like to configure?" + + OptCloudtrailOrg = "Configure org account mappings" + OptCloudtrailKmsKeyArn = "Configure custom KMS key" + OptCloudtrailS3 = "Configure S3 bucket" + OptCloudtrailSNS = "Configure SNS topic" + OptCloudtrailSQS = "Configure SQS queue" + OptCloudtrailIAM = "Configure an existing IAM role" + OptCloudtrailDone = "Done" + + // CloudTrail Org questions + QuestionCloudtrailOrgAccountMappingsDefaultLWAccount = "Org account mappings default Lacework account:" + QuestionCloudtrailOrgAccountMappingsAnotherAddMore = "Add another org account mapping?" + QuestionCloudtrailOrgAccountMappingsLWAccount = "Lacework account:" + QuestionCloudtrailOrgAccountMappingsAwsAccounts = "AWS accounts:" + + // CloudTrail S3 Bucket Questions + QuestionCloudtrailUseConsolidated = "Use consolidated CloudTrail?" + QuestionCloudtrailUseExistingTrail = "Use an existing CloudTrail?" + QuestionCloudtrailS3ExistingBucketArn = "Existing S3 bucket ARN used for CloudTrail logs:" + QuestionCloudtrailS3BucketEnableEncryption = "Enable S3 bucket encryption" + + QuestionCloudtrailS3BucketSseKeyArn = "Existing KMS encryption key arn for S3 bucket (optional):" + QuestionCloudtrailS3BucketName = "New S3 bucket name (optional):" + QuestionCloudtrailS3BucketNotification = "Enable S3 bucket notifications" + + // CloudTrail SNS Topic Questions + QuestionCloudtrailUseExistingSNSTopic = "Use an existing SNS topic? (If not, S3 notification will be used)" + QuestionCloudtrailSnsExistingTopicArn = "Existing SNS topic arn:" + QuestionCloudtrailSnsEnableEncryption = "Enable encryption on SNS topic?" + QuestionCloudtrailSnsEncryptionKeyArn = "Existing KMS encryption key arn for SNS topic (optional):" + QuestionCloudtrailSnsTopicName = "New SNS topic name (optional):" + + // CloudTrail SQS Queue Questions + QuestionCloudtrailSqsEnableEncryption = "Enable encryption on SQS queue:" + QuestionCloudtrailSqsEncryptionKeyArn = "Existing KMS encryption key arn for SQS queue (optional):" + QuestionCloudtrailSqsQueueName = "New SQS queue name (optional):" + + // CloudTrail IAM Role Questions + QuestionCloudtrailExistingIamRoleName = "Existing IAM role name for CloudTrail access:" + QuestionCloudtrailExistingIamRoleArn = "Existing IAM role ARN for CloudTrail access:" + QuestionCloudtrailExistingIamRoleExtID = "External ID for the existing IAM role:" + + // Custom location Question + QuestionAwsOutputLocation = "Custom output location (optional):" + + // Other options + AwsAdvancedOptDone = "Done" // Used in aws controltower and eks_audit + + // AwsArnRegex original source: https://regex101.com/r/pOfxYN/1 + AwsArnRegex = `^arn:(?P[^:\n]*):(?P[^:\n]*):(?P[^:\n]*):(?P[^:\n]*):(?P(?P[^:\/\n]*)[:\/])?(?P.*)$` //nolint + // AwsRegionRegex regex used for validating region input; note intentionally does not match gov cloud + AwsRegionRegex = `(af|ap|ca|eu|me|sa|us)-(central|(north|south)?(east|west)?)-\d` + AwsProfileRegex = `([A-Za-z_0-9-]+)` + AwsAccountIDRegex = `^\d{12}$` + AwsOUIDRegex = `^ou-[0-9a-z]{4,32}-[a-z0-9]{8,32}$` + AWSRootIDRegex = `^r-[0-9a-z]{4,32}$` + AwsAssumeRoleRegex = `^arn:aws:iam::\d{12}:role\/.*$` + ValidateSubAccountFlagRegex = fmt.Sprintf(`%s:%s`, AwsProfileRegex, AwsRegionRegex) + AwsCfResourcePrefixRegex = `^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$` + + GenerateAwsCommandState = &aws.GenerateAwsTfConfigurationArgs{ + ExistingIamRole: &aws.ExistingIamRoleDetails{}, + } + GenerateAwsCommandExtraState = &aws.AwsGenerateCommandExtraState{} + + CachedAwsArgsKey = "iac-aws-generate-args" + CachedAwsExtraStateKey = "iac-aws-extra-state" + + // aws command is used to generate TF code for aws + generateAwsTfCommand = &cobra.Command{ + Use: "aws", + Short: "Generate and/or execute Terraform code for AWS integration", + Long: `Use this command to generate Terraform code for deploying Lacework into an AWS environment. + +By default, this command interactively prompts for the required information to setup the new cloud account. +In interactive mode, this command will: + +* Prompt for the required information to setup the integration +* Generate new Terraform code using the inputs +* Optionally, run the generated Terraform code: + * If Terraform is already installed, the version is verified as compatible for use + * If Terraform is not installed, or the version installed is not compatible, a new + version will be installed into a temporary location + * Once Terraform is detected or installed, Terraform plan will be executed + * The command will prompt with the outcome of the plan and allow to view more details + or continue with Terraform apply + * If confirmed, Terraform apply will be run, completing the setup of the cloud account + +This command can also be run in noninteractive mode. +See help output for more details on the parameter value(s) required for Terraform code generation. +`, + RunE: func(cmd *cobra.Command, args []string) error { + // Generate TF Code + cli.StartProgress("Generating Terraform Code...") + + // Explicitly set Lacework profile if it was passed in main args + if cli.Profile != "default" { + GenerateAwsCommandState.LaceworkProfile = cli.Profile + } + + // Setup modifiers for NewTerraform constructor + mods := []aws.AwsTerraformModifier{ + aws.WithAwsProfile(GenerateAwsCommandState.AwsProfile), + aws.WithAwsRegion(GenerateAwsCommandState.AwsRegion), + aws.WithAwsAssumeRole(GenerateAwsCommandState.AwsAssumeRole), + aws.WithLaceworkProfile(GenerateAwsCommandState.LaceworkProfile), + aws.WithLaceworkAccountID(GenerateAwsCommandState.LaceworkAccountID), + aws.WithAgentlessManagementAccountID(GenerateAwsCommandState.AgentlessManagementAccountID), + aws.WithAgentlessMonitoredAccountIDs(GenerateAwsCommandState.AgentlessMonitoredAccountIDs), + aws.WithAgentlessMonitoredAccounts(GenerateAwsCommandState.AgentlessMonitoredAccounts...), + aws.WithAgentlessScanningAccounts(GenerateAwsCommandState.AgentlessScanningAccounts...), + aws.WithConfigAdditionalAccounts(GenerateAwsCommandState.ConfigAdditionalAccounts...), + aws.WithConfigOrgLWAccount(GenerateAwsCommandState.ConfigOrgLWAccount), + aws.WithConfigOrgLWSubaccount(GenerateAwsCommandState.ConfigOrgLWSubaccount), + aws.WithConfigOrgLWAccessKeyId(GenerateAwsCommandState.ConfigOrgLWAccessKeyId), + aws.WithConfigOrgLWSecretKey(GenerateAwsCommandState.ConfigOrgLWSecretKey), + aws.WithConfigOrgId(GenerateAwsCommandState.ConfigOrgId), + aws.WithConfigOrgUnits(GenerateAwsCommandState.ConfigOrgUnits), + aws.WithConfigOrgCfResourcePrefix(GenerateAwsCommandState.ConfigOrgCfResourcePrefix), + aws.WithControlTower(GenerateAwsCommandState.ControlTower), + aws.WithControlTowerAuditAccount(GenerateAwsCommandState.ControlTowerAuditAccount), + aws.WithControlTowerLogArchiveAccount(GenerateAwsCommandState.ControlTowerLogArchiveAccount), + aws.WithControlTowerKmsKeyArn(GenerateAwsCommandState.ControlTowerKmsKeyArn), + aws.WithConsolidatedCloudtrail(GenerateAwsCommandState.ConsolidatedCloudtrail), + aws.WithCloudtrailUseExistingTrail(GenerateAwsCommandState.CloudtrailUseExistingTrail), + aws.WithCloudtrailUseExistingSNSTopic(GenerateAwsCommandState.CloudtrailUseExistingSNSTopic), + aws.WithExistingCloudtrailBucketArn(GenerateAwsCommandState.ExistingCloudtrailBucketArn), + aws.WithExistingSnsTopicArn(GenerateAwsCommandState.ExistingSnsTopicArn), + aws.WithSubaccounts(GenerateAwsCommandState.SubAccounts...), + aws.WithExistingIamRole(GenerateAwsCommandState.ExistingIamRole), + aws.WithCloudtrailName(GenerateAwsCommandState.CloudtrailName), + aws.WithOrgAccountMappings(GenerateAwsCommandState.OrgAccountMappings), + aws.WithBucketName(GenerateAwsCommandState.BucketName), + aws.WithBucketEncryptionEnabled(GenerateAwsCommandState.BucketEncryptionEnabled), + aws.WithBucketSSEKeyArn(GenerateAwsCommandState.BucketSseKeyArn), + aws.WithSnsTopicName(GenerateAwsCommandState.SnsTopicName), + aws.WithSnsTopicEncryptionEnabled(GenerateAwsCommandState.SnsTopicEncryptionEnabled), + aws.WithSnsTopicEncryptionKeyArn(GenerateAwsCommandState.SnsTopicEncryptionKeyArn), + aws.WithSqsQueueName(GenerateAwsCommandState.SqsQueueName), + aws.WithSqsEncryptionEnabled(GenerateAwsCommandState.SqsEncryptionEnabled), + aws.WithSqsEncryptionKeyArn(GenerateAwsCommandState.SqsEncryptionKeyArn), + aws.WithS3BucketNotification(GenerateAwsCommandState.S3BucketNotification), + } + + // Create new struct + data := aws.NewTerraform( + GenerateAwsCommandState.AwsOrganization, + GenerateAwsCommandState.Agentless, + GenerateAwsCommandState.Config, + GenerateAwsCommandState.Cloudtrail, + mods..., + ) + + // Generate + hcl, err := data.Generate() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "failed to generate terraform code") + } + + // Write-out generated code to location specified + dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "aws") + if err != nil { + return err + } + + // Prompt to execute + err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Default: GenerateAwsCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, + Response: &GenerateAwsCommandExtraState.TerraformApply, + }) + + if err != nil { + return errors.Wrap(err, "failed to prompt for terraform execution") + } + + locationDir, _ := determineOutputDirPath(dirname, "aws") + if GenerateAwsCommandExtraState.TerraformApply { + // Execution pre-run check + err := executionPreRunChecks(dirname, locationDir, "aws") + if err != nil { + return err + } + } + + // Output where code was generated + if !GenerateAwsCommandExtraState.TerraformApply { + cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) + } + + return nil + }, + PreRunE: func(cmd *cobra.Command, _ []string) error { + // Validate output location is OK if supplied + dirname, err := cmd.Flags().GetString("output") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateOutputLocation(dirname); err != nil { + return err + } + + // Validate aws assume role, if passed + assumeRole, err := cmd.Flags().GetString("aws_assume_role") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsAssumeRole(assumeRole); assumeRole != "" && err != nil { + return err + } + + // Validate aws profile, if passed + profile, err := cmd.Flags().GetString("aws_profile") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsProfile(profile); profile != "" && err != nil { + return err + } + + // Validate aws region, if passed + region, err := cmd.Flags().GetString("aws_region") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsRegion(region); region != "" && err != nil { + return err + } + + // Parse cloudtrail org_account_mapping json, if passed + if cmd.Flags().Changed("cloudtrail_org_account_mapping") { + if err := parseCloudtrailOrgAccountMappingsFlag(GenerateAwsCommandState); err != nil { + return err + } + } + + // Validate cloudtrail bucket arn, if passed + arn, err := cmd.Flags().GetString("existing_bucket_arn") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsArnFormat(arn); arn != "" && err != nil { + return err + } + if arn != "" { + GenerateAwsCommandState.CloudtrailUseExistingTrail = true + } + + // Validate SNS Topic Arn if passed + arn, err = cmd.Flags().GetString("existing_sns_topic_arn") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsArnFormat(arn); arn != "" && err != nil { + return err + } + if arn != "" { + GenerateAwsCommandState.CloudtrailUseExistingSNSTopic = true + } + + // Load any cached inputs if interactive + if cli.InteractiveMode() { + cachedOptions := &aws.GenerateAwsTfConfigurationArgs{} + awsArgsExpired := cli.ReadCachedAsset(CachedAwsArgsKey, &cachedOptions) + if awsArgsExpired { + cli.Log.Debug("loaded previously set values for AWS iac generation") + } + + extraState := &aws.AwsGenerateCommandExtraState{} + extraStateExpired := cli.ReadCachedAsset(CachedAwsExtraStateKey, &extraState) + if extraStateExpired { + cli.Log.Debug("loaded previously set values for AWS iac generation (extra state)") + } + + // Determine if previously cached options exists; prompt user if they'd like to continue + answer := false + if !awsArgsExpired || !extraStateExpired { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, + Response: &answer, + }); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs + // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get + // re-prompted. + if answer { + // Merge cached inputs to current options (current options win) + if err := mergo.Merge(GenerateAwsCommandState, cachedOptions); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + if err := mergo.Merge(GenerateAwsCommandExtraState, extraState); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + // Collect and/or confirm parameters + err = promptAwsGenerate(GenerateAwsCommandState, GenerateAwsCommandExtraState) + if err != nil { + return errors.Wrap(err, "collecting/confirming parameters") + } + } + + // Parse passed in AWS accounts + if len(GenerateAwsCommandExtraState.AwsSubAccounts) > 0 { + accounts, err := parseAwsAccountsFromCommandFlag(GenerateAwsCommandExtraState.AwsSubAccounts) + if err != nil { + return err + } + GenerateAwsCommandState.SubAccounts = accounts + GenerateAwsCommandState.ConfigAdditionalAccounts = accounts + } + + // Parse passed in Agentless monirtoed AWS accounts + if len(GenerateAwsCommandExtraState.AgentlessMonitoredAccounts) > 0 { + accounts, err := parseAwsAccountsFromCommandFlag(GenerateAwsCommandExtraState.AgentlessMonitoredAccounts) + if err != nil { + return err + } + GenerateAwsCommandState.AgentlessMonitoredAccounts = accounts + } + + // Parse passed in Agentless scanning AWS accounts + if len(GenerateAwsCommandExtraState.AgentlessScanningAccounts) > 0 { + accounts, err := parseAwsAccountsFromCommandFlag(GenerateAwsCommandExtraState.AgentlessScanningAccounts) + if err != nil { + return err + } + GenerateAwsCommandState.AgentlessScanningAccounts = accounts + } + + // Parse passed in Control Tower Audit account + if GenerateAwsCommandExtraState.ControlTowerAuditAccount != "" { + accounts, err := parseAwsAccountsFromCommandFlag( + []string{GenerateAwsCommandExtraState.ControlTowerAuditAccount}, + ) + if err != nil { + return err + } + GenerateAwsCommandState.ControlTowerAuditAccount = &accounts[0] + } + + // Parse passed in Control Tower Log Archive account + if GenerateAwsCommandExtraState.ControlTowerLogArchiveAccount != "" { + accounts, err := parseAwsAccountsFromCommandFlag( + []string{GenerateAwsCommandExtraState.ControlTowerLogArchiveAccount}, + ) + if err != nil { + return err + } + GenerateAwsCommandState.ControlTowerLogArchiveAccount = &accounts[0] + } + + return nil + }, + } +) + +func parseAwsAccountsFromCommandFlag(accountsInput []string) ([]aws.AwsSubAccount, error) { + // Validate the format of supplied values is correct + if err := validateAwsSubAccounts(accountsInput); err != nil { + return nil, err + } + accounts := []aws.AwsSubAccount{} + for _, account := range accountsInput { + accountDetails := strings.Split(account, ":") + profile := accountDetails[0] + region := accountDetails[1] + alias := fmt.Sprintf("%s-%s", profile, region) + accounts = append(accounts, aws.NewAwsSubAccount(profile, region, alias)) + } + return accounts, nil +} + +func parseCloudtrailOrgAccountMappingsFlag(args *aws.GenerateAwsTfConfigurationArgs) error { + if err := json.Unmarshal([]byte(args.OrgAccountMappingsJson), &args.OrgAccountMappings); err != nil { + return errors.Wrap(err, "failed to parse 'cloudtrail_org_account_mapping'") + } + return nil +} + +func initGenerateAwsTfCommandFlags() { + // add flags to sub commands + // TODO Share the help with the interactive generation + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.AwsOrganization, + "aws_organization", + false, + "enable organization integration") + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.Agentless, + "agentless", + false, + "enable agentless integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.AgentlessManagementAccountID, + "agentless_management_account_id", + "", + "AWS management account ID for Agentless integration") + generateAwsTfCommand.PersistentFlags().StringSliceVar( + &GenerateAwsCommandState.AgentlessMonitoredAccountIDs, + "agentless_monitored_account_ids", + []string{}, + "AWS monitored account IDs for Agentless integrations; may "+ + "contain account IDs, OUs, or the organization root (e.g. 123456789000,ou-abcd-12345678,r-abcd)") + generateAwsTfCommand.PersistentFlags().StringSliceVar( + &GenerateAwsCommandExtraState.AgentlessMonitoredAccounts, + "agentless_monitored_accounts", + []string{}, + "AWS monitored accounts for Agentless integrations; value format must be :") + generateAwsTfCommand.PersistentFlags().StringSliceVar( + &GenerateAwsCommandExtraState.AgentlessScanningAccounts, + "agentless_scanning_accounts", + []string{}, + "AWS scanning accounts for Agentless integrations; value format must be :") + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.ControlTower, + "controltower", + false, + "enable Control Tower integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandExtraState.ControlTowerAuditAccount, + "controltower_audit_account", + "", + "specify AWS Control Tower Audit account; value format must be :") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandExtraState.ControlTowerLogArchiveAccount, + "controltower_log_archive_account", + "", + "specify AWS Control Tower Log Archive account; value format must be :") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ControlTowerKmsKeyArn, + "controltower_kms_key_arn", + "", + "specify AWS Control Tower custom kMS key ARN") + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.Cloudtrail, + "cloudtrail", + false, + "enable cloudtrail integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.CloudtrailName, + "cloudtrail_name", + "", + "specify name of cloudtrail integration") + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.Config, + "config", + false, + "enable config integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ConfigOrgLWAccount, + "config_lacework_account", + "", + "specify lacework account for Config organization integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ConfigOrgLWSubaccount, + "config_lacework_sub_account", + "", + "specify lacework sub-account for Config organization integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ConfigOrgLWAccessKeyId, + "config_lacework_access_key_id", + "", + "specify AWS access key ID for Config organization integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ConfigOrgLWSecretKey, + "config_lacework_secret_key", + "", + "specify AWS secret key for Config organization integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ConfigOrgId, + "config_organization_id", + "", + "specify AWS organization ID for Config organization integration") + generateAwsTfCommand.PersistentFlags().StringSliceVar( + &GenerateAwsCommandState.ConfigOrgUnits, + "config_organization_units", + nil, + "specify AWS organization units for Config organization integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ConfigOrgCfResourcePrefix, + "config_cf_resource_prefix", + "", + "specify Cloudformation resource prefix for Config organization integration") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.AwsRegion, + "aws_region", + "", + "specify aws region") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.AwsProfile, + "aws_profile", + "", + "specify aws profile") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.AwsAssumeRole, + "aws_assume_role", + "", + "specify aws assume role") + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.BucketEncryptionEnabled, + "bucket_encryption_enabled", + true, + "enable S3 bucket encryption when creating bucket") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.BucketName, + "bucket_name", + "", + "specify bucket name when creating bucket") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.BucketSseKeyArn, + "bucket_sse_key_arn", + "", + "specify existing KMS encryption key arn for bucket") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ExistingCloudtrailBucketArn, + "existing_bucket_arn", + "", + "specify existing cloudtrail S3 bucket ARN") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ExistingIamRole.Arn, + "existing_iam_role_arn", + "", + "specify existing iam role arn to use") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ExistingIamRole.Name, + "existing_iam_role_name", + "", + "specify existing iam role name to use") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ExistingIamRole.ExternalId, + "existing_iam_role_externalid", + "", + "specify existing iam role external_id to use") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ExistingSnsTopicArn, + "existing_sns_topic_arn", + "", + "specify existing SNS topic arn") + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.ConsolidatedCloudtrail, + "consolidated_cloudtrail", + false, + "use consolidated trail") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.OrgAccountMappingsJson, + "cloudtrail_org_account_mapping", "", "Org account mapping json string. Example: "+ + "'{\"default_lacework_account\":\"main\", \"mapping\": [{ \"aws_accounts\": [\"123456789011\"], "+ + "\"lacework_account\": \"sub-account-1\"}]}'") + + // DEPRECATED + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.ForceDestroyS3Bucket, + "force_destroy_s3", + true, + "enable force destroy S3 bucket") + errcheckWARN(generateAwsTfCommand.PersistentFlags().MarkDeprecated( + "force_destroy_s3", "by default, force destroy is enabled.", + )) + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.ConfigName, + "config_name", + "", + "specify name of config integration") + errcheckWARN(generateAwsTfCommand.PersistentFlags().MarkDeprecated( + "config_name", "default config is used.", + )) + // --- + + generateAwsTfCommand.PersistentFlags().StringSliceVar( + &GenerateAwsCommandExtraState.AwsSubAccounts, + "aws_subaccount", + []string{}, + "configure an additional aws account; value format must be :") + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandExtraState.TerraformApply, + "apply", + false, + "run terraform apply without executing plan or prompting", + ) + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandExtraState.Output, + "output", + "", + "location to write generated content (default is ~/lacework/aws)", + ) + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.SnsTopicEncryptionEnabled, + "sns_topic_encryption_enabled", + true, + "enable encryption on SNS topic when creating one") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.SnsTopicEncryptionKeyArn, + "sns_topic_encryption_key_arn", + "", + "specify existing KMS encryption key arn for SNS topic") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.SnsTopicName, + "sns_topic_name", + "", + "specify SNS topic name if creating new one") + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.SqsEncryptionEnabled, + "sqs_encryption_enabled", + true, + "enable encryption on SQS queue when creating") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.SqsEncryptionKeyArn, + "sqs_encryption_key_arn", + "", + "specify existing KMS encryption key arn for SQS queue") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.SqsQueueName, + "sqs_queue_name", + "", + "specify SQS queue name if creating new one") + generateAwsTfCommand.PersistentFlags().StringVar( + &GenerateAwsCommandState.LaceworkAccountID, + "lacework_aws_account_id", + "", + "the Lacework AWS root account id") + generateAwsTfCommand.PersistentFlags().BoolVar( + &GenerateAwsCommandState.S3BucketNotification, + "use_s3_bucket_notification", + false, + "enable S3 bucket notifications") +} + +// survey.Validator for aws ARNs +// +// This isn't service/type specific but rather just validates that an ARN was entered that matches valid ARN formats +func validateAwsArnFormat(val interface{}) error { + return validateStringWithRegex(val, AwsArnRegex, "invalid arn supplied") +} + +// Validate AWS Arn only if a value is set, this can be used for optional ARN cofiguration +func validateOptionalAwsArnFormat(val interface{}) error { + if val.(string) != "" { + return validateAwsArnFormat(val) + } + return nil +} + +func validateAwsAccountID(val interface{}) error { + return validateStringWithRegex(val, AwsAccountIDRegex, "invalid account ID supplied") +} + +func validateAwsSubAccounts(subaccounts []string) error { + // validate the format of supplied values is correct + for _, account := range subaccounts { + if ok, err := regexp.MatchString(ValidateSubAccountFlagRegex, account); !ok { + if err != nil { + return errors.Wrap(err, "failed to validate supplied subaccount format") + } + return errors.New("supplied aws subaccount in invalid format") + } + } + + return nil +} + +func validateAgentlessMonitoredAccountIDList(val interface{}) error { + switch value := val.(type) { + case string: + regex := fmt.Sprintf(`%s|%s|%s`, AwsAccountIDRegex, AwsOUIDRegex, AWSRootIDRegex) + ids := strings.Split(value, ",") + for _, id := range ids { + if err := validateStringWithRegex( + id, + regex, + fmt.Sprintf("invalid account ID, OU ID or root ID supplied: %s", id), + ); err != nil { + return err + } + } + default: + // if the value passed is not a string + return errors.New("value must be a string") + } + return nil +} + +// survey.Validator for aws region +func validateAwsRegion(val interface{}) error { + return validateStringWithRegex(val, AwsRegionRegex, "invalid region supplied") +} + +// survey.Validator for aws profile +func validateAwsProfile(val interface{}) error { + return validateStringWithRegex(val, fmt.Sprintf(`^%s$`, AwsProfileRegex), "invalid profile name supplied") +} + +// survey.Validator for aws assume role +func validateAwsAssumeRole(val interface{}) error { + return validateStringWithRegex(val, AwsAssumeRoleRegex, "invalid assume name supplied") +} + +// survey.Validator for CloudFormation resource prefix +func validateAwsCfResourcePrefix(val interface{}) error { + return validateStringWithRegex(val, AwsCfResourcePrefixRegex, "invalid CloudFormation resource name prefix supplied") +} + +func promptAgentlessQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + if !config.Agentless { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: IconAgentless, + Prompt: &survey.Confirm{Message: QuestionEnableAgentless, Default: config.Agentless}, + Response: &config.Agentless, + }); err != nil { + return err + } + } + + if !config.Agentless { + return nil + } + + if config.AwsOrganization { + monitoredAccountIDListInput := "" + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconAgentless, + Prompt: &survey.Input{ + Message: QuestionAgentlessManagementAccountID, + Default: config.AgentlessManagementAccountID, + }, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsAccountID)}, + Response: &config.AgentlessManagementAccountID, + Required: true, + }, + { + Icon: IconAgentless, + Prompt: &survey.Input{ + Message: QuestionAgentlessMonitoredAccountIDs, + Default: strings.Join(config.AgentlessMonitoredAccountIDs, ","), + Help: QuestionAgentlessMonitoredAccountIDsHelp, + }, + Opts: []survey.AskOpt{survey.WithValidator(validateAgentlessMonitoredAccountIDList)}, + Response: &monitoredAccountIDListInput, + Required: true, + }, + }, config.AwsOrganization); err != nil { + return err + } + + config.AgentlessMonitoredAccountIDs = strings.Split(monitoredAccountIDListInput, ",") + config.AgentlessMonitoredAccounts = []aws.AwsSubAccount{} + + // Prompt user to enter profile/region for single accounts + for _, accountID := range config.AgentlessMonitoredAccountIDs { + err := validateAwsAccountID(accountID) + if err != nil { + continue + } + var profile, region string + profileMessage := fmt.Sprintf( + "%s for account %s:", + QuestionAgentlessMonitoredAccountProfile[:len(QuestionAgentlessMonitoredAccountProfile)-1], + accountID, + ) + regionMessage := fmt.Sprintf( + "%s for account %s:", + QuestionAgentlessMonitoredAccountRegion[:len(QuestionAgentlessMonitoredAccountRegion)-1], + accountID, + ) + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconAgentless, + Prompt: &survey.Input{Message: profileMessage}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsProfile)}, + Required: true, + Response: &profile, + }, + { + Icon: IconAgentless, + Prompt: &survey.Input{Message: regionMessage}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, + Required: true, + Response: ®ion, + }, + }); err != nil { + return err + } + alias := fmt.Sprintf("%s-%s", profile, region) + config.AgentlessMonitoredAccounts = append( + config.AgentlessMonitoredAccounts, + aws.AwsSubAccount{AwsProfile: profile, AwsRegion: region, Alias: alias}, + ) + } + } + + if err := promptAwsAccountsQuestions( + &config.AgentlessScanningAccounts, + IconAgentless, + QuestionAgentlessScanningAccountProfile, + QuestionAgentlessScanningAccountRegion, + QuestionAgentlessScanningAccountAddMore, + QuestionAgentlessScanningAccountsReplace, + !config.AwsOrganization, + ); err != nil { + return err + } + + return nil +} + +func promptConfigQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + if !config.Config { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: IconConfig, + Prompt: &survey.Confirm{Message: QuestionEnableConfig, Default: config.Config}, + Response: &config.Config, + }); err != nil { + return err + } + } + + if !config.Config { + return nil + } + + tempLwSecretKey := "" + lwSecretKeyMessage := QuestionConfigOrgLWSecretKey + if len(config.ConfigOrgLWSecretKey) > 0 { + lwSecretKeyMessage = fmt.Sprintf( + "%s: (%s)", + QuestionConfigOrgLWSecretKey, + format.Secret(4, config.ConfigOrgLWSecretKey), + ) + } + + if config.AwsOrganization { + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconConfig, + Prompt: &survey.Input{Message: QuestionConfigOrgLWAccount, Default: config.ConfigOrgLWAccount}, + Response: &config.ConfigOrgLWAccount, + Required: true, + }, + { + Icon: IconConfig, + Prompt: &survey.Input{Message: QuestionConfigOrgLWSubaccount, Default: config.ConfigOrgLWSubaccount}, + Response: &config.ConfigOrgLWSubaccount, + }, + { + Icon: IconConfig, + Prompt: &survey.Input{Message: QuestionConfigOrgLWAccessKeyId, Default: config.ConfigOrgLWAccessKeyId}, + Response: &config.ConfigOrgLWAccessKeyId, + Required: true, + }, + { + Icon: IconConfig, + Prompt: &survey.Password{Message: lwSecretKeyMessage}, + Response: &tempLwSecretKey, + Required: config.ConfigOrgLWSecretKey == "", + }, + { + Icon: IconConfig, + Prompt: &survey.Input{Message: QuestionConfigOrgId, Default: config.ConfigOrgId}, + Response: &config.ConfigOrgId, + Required: true, + }, + }); err != nil { + return err + } + + // Use newly entered secret key, otherwise use the cached value + if tempLwSecretKey != "" { + config.ConfigOrgLWSecretKey = tempLwSecretKey + } + + var orgUnitsInput string + if err := survey.AskOne( + &survey.Input{Message: QuestionConfigOrgUnits, Default: strings.Join(config.ConfigOrgUnits, ",")}, &orgUnitsInput, + survey.WithValidator(survey.Required), survey.WithIcons(customPromptIconsFunc(IconConfig)), + ); err != nil { + return err + } + config.ConfigOrgUnits = strings.Split(orgUnitsInput, ",") + + if err := survey.AskOne( + &survey.Input{ + Message: QuestionConfigOrgCfResourcePrefix, Default: config.ConfigOrgCfResourcePrefix, + }, &config.ConfigOrgCfResourcePrefix, + survey.WithValidator(survey.Required), + survey.WithValidator(validateAwsCfResourcePrefix), + survey.WithIcons(customPromptIconsFunc(IconConfig)), + ); err != nil { + return err + } + + return nil + } + + if err := promptAwsAccountsQuestions( + &config.ConfigAdditionalAccounts, + IconConfig, + QuestionConfigAdditionalAccountProfile, + QuestionConfigAdditionalAccountRegion, + QuestionConfigAdditionalAccountAddMore, + QuestionConfigAdditionalAccountsReplace, + true, + ); err != nil { + return err + } + + return nil +} + +func promptCloudtrailQuestions( + config *aws.GenerateAwsTfConfigurationArgs, + extraState *aws.AwsGenerateCommandExtraState, +) error { + if !config.Cloudtrail { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: IconCloudTrail, + Prompt: &survey.Confirm{Message: QuestionEnableCloudtrail, Default: config.Cloudtrail}, + Response: &config.Cloudtrail, + }); err != nil { + return err + } + } + + if !config.Cloudtrail { + return nil + } + + if err := promptCloudtrailControlTowerQuestions(config); err != nil { + return err + } + + noControlTower := !config.ControlTower + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: IconCloudTrail, + Prompt: &survey.Confirm{Message: QuestionCloudtrailUseConsolidated, Default: config.ConsolidatedCloudtrail}, + Response: &config.ConsolidatedCloudtrail, + Checks: []*bool{&noControlTower}, + }); err != nil { + return err + } + + if err := promptCloudtrailExistingTrailQuestions(config); err != nil { + return err + } + + // Find out if the customer wants to specify more advanced features + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: IconCloudTrail, + Prompt: &survey.Confirm{Message: QuestionCloudtrailAdvanced, Default: extraState.CloudtrailAdvanced}, + Response: &extraState.CloudtrailAdvanced, + }); err != nil { + return err + } + + // Keep prompting for advanced options until the say done + if extraState.CloudtrailAdvanced { + answer := "" + options := []string{ + OptCloudtrailS3, + OptCloudtrailSNS, + OptCloudtrailSQS, + OptCloudtrailIAM, + OptCloudtrailDone, + } + if config.CloudtrailUseExistingTrail { + options = []string{ + OptCloudtrailSQS, + OptCloudtrailIAM, + OptCloudtrailDone, + } + } + if config.AwsOrganization { + if config.ControlTower { + options = []string{ + OptCloudtrailKmsKeyArn, + OptCloudtrailIAM, + OptCloudtrailDone, + } + } + options = append([]string{OptCloudtrailOrg}, options...) + } + for answer != OptCloudtrailDone { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: IconCloudTrail, + Prompt: &survey.Select{ + Message: OptCloudtrailMessage, + Options: options, + }, + Response: &answer, + }); err != nil { + return err + } + switch answer { + case OptCloudtrailOrg: + if err := promptCloudtrailOrgQuestions(config); err != nil { + return err + } + case OptCloudtrailKmsKeyArn: + if err := promptCloudtrailKmsKeyQuestions(config); err != nil { + return err + } + case OptCloudtrailS3: + if err := promptCloudtrailS3Questions(config); err != nil { + return err + } + case OptCloudtrailSNS: + if err := promptCloudtrailSNSQuestions(config); err != nil { + return err + } + case OptCloudtrailSQS: + if err := promptCloudtrailSQSQuestions(config); err != nil { + return err + } + case OptCloudtrailIAM: + if err := promptCloudtrailIAMQuestions(config); err != nil { + return err + } + } + } + } + + return nil +} + +func promptCloudtrailControlTowerQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: IconCloudTrail, + Prompt: &survey.Confirm{Message: QuestionControlTower, Default: config.ControlTower}, + Response: &config.ControlTower, + Checks: []*bool{&config.AwsOrganization}, + }); err != nil { + return err + } + + if !config.ControlTower { + return nil + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{ + Message: QuestionControlTowerS3BucketArn, + Default: config.ExistingCloudtrailBucketArn, + }, + Required: true, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &config.ExistingCloudtrailBucketArn, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{ + Message: QuestionControlTowerSnsTopicArn, + Default: config.ExistingSnsTopicArn, + }, + Required: true, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &config.ExistingSnsTopicArn, + }, + }); err != nil { + return err + } + + profile := "" + region := "" + defaultProfile := "" + defaultRegion := "" + if config.ControlTowerAuditAccount != nil { + defaultProfile = config.ControlTowerAuditAccount.AwsProfile + } + if config.ControlTowerAuditAccount != nil { + defaultRegion = config.ControlTowerAuditAccount.AwsRegion + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{ + Message: QuestionControlTowerAuditAccountProfile, + Default: defaultProfile, + }, + Required: true, + Response: &profile, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{ + Message: QuestionControlTowerAuditAccountRegion, + Default: defaultRegion, + }, + Required: true, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, + Response: ®ion, + }, + }); err != nil { + return err + } + + config.ControlTowerAuditAccount = &aws.AwsSubAccount{ + AwsProfile: profile, + AwsRegion: region, + Alias: fmt.Sprintf("%s-%s", profile, region), + } + + defaultProfile = "" + defaultRegion = "" + if config.ControlTowerLogArchiveAccount != nil { + defaultProfile = config.ControlTowerLogArchiveAccount.AwsProfile + } + if config.ControlTowerLogArchiveAccount != nil { + defaultRegion = config.ControlTowerLogArchiveAccount.AwsRegion + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{ + Message: QuestionControlTowerLogArchiveAccountProfile, + Default: defaultProfile, + }, + Required: true, + Response: &profile, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{ + Message: QuestionControlTowerLogArchiveAccountRegion, + Default: defaultRegion, + }, + Required: true, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, + Response: ®ion, + }, + }); err != nil { + return err + } + + config.ControlTowerLogArchiveAccount = &aws.AwsSubAccount{ + AwsProfile: profile, + AwsRegion: region, + Alias: fmt.Sprintf("%s-%s", profile, region), + } + + return nil +} + +func promptCloudtrailOrgAccountMappingQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + mapping := aws.OrgAccountMap{} + var accountsAnswer string + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailOrgAccountMappingsLWAccount}, + Response: &mapping.LaceworkAccount, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Multiline{Message: QuestionCloudtrailOrgAccountMappingsAwsAccounts}, + Response: &accountsAnswer, + }, + }); err != nil { + return err + } + mapping.AwsAccounts = strings.Split(accountsAnswer, "\n") + config.OrgAccountMappings.Mapping = append(config.OrgAccountMappings.Mapping, mapping) + return nil +} + +func promptCloudtrailOrgQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{ + Message: QuestionCloudtrailOrgAccountMappingsDefaultLWAccount, + Default: config.OrgAccountMappings.DefaultLaceworkAccount}, + Response: &config.OrgAccountMappings.DefaultLaceworkAccount, + }, + }); err != nil { + return err + } + + askAgain := true + for askAgain { + if err := promptCloudtrailOrgAccountMappingQuestions(config); err != nil { + return err + } + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: IconCloudTrail, + Prompt: &survey.Confirm{Message: QuestionCloudtrailOrgAccountMappingsAnotherAddMore}, + Response: &askAgain}); err != nil { + return err + } + } + + return nil +} + +func promptCloudtrailKmsKeyQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: IconCloudTrail, + Prompt: &survey.Input{ + Message: QuestionControlTowerKmsKeyArn, + Default: config.ControlTowerKmsKeyArn, + }, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &config.ControlTowerKmsKeyArn, + }); err != nil { + return err + } + + return nil +} + +func promptCloudtrailExistingTrailQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + if config.ControlTower { + return nil + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Confirm{Message: QuestionCloudtrailUseExistingTrail, Default: config.CloudtrailUseExistingTrail}, + Response: &config.CloudtrailUseExistingTrail, + }, + }); err != nil { + return err + } + + if !config.CloudtrailUseExistingTrail { + return nil + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailName, Default: config.CloudtrailName}, + Response: &config.CloudtrailName, + Required: true, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{ + Message: QuestionCloudtrailS3ExistingBucketArn, + Default: config.ExistingCloudtrailBucketArn, + }, + Required: true, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &config.ExistingCloudtrailBucketArn, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Confirm{ + Message: QuestionCloudtrailUseExistingSNSTopic, + Default: config.CloudtrailUseExistingSNSTopic, + }, + Response: &config.CloudtrailUseExistingSNSTopic, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailSnsExistingTopicArn, Default: config.ExistingSnsTopicArn}, + Checks: []*bool{&config.CloudtrailUseExistingSNSTopic}, + Required: true, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &config.ExistingSnsTopicArn, + }, + }); err != nil { + return err + } + + // If no SNS topic is provided for the existing trail, fallback to use S3 notification + if !config.CloudtrailUseExistingSNSTopic { + config.S3BucketNotification = true + } + + return nil +} + +func promptCloudtrailS3Questions(config *aws.GenerateAwsTfConfigurationArgs) error { + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailS3BucketName, Default: config.BucketName}, + Response: &config.BucketName, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Confirm{ + Message: QuestionCloudtrailS3BucketEnableEncryption, + Default: config.BucketEncryptionEnabled, + }, + Response: &config.BucketEncryptionEnabled, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailS3BucketSseKeyArn, Default: config.BucketSseKeyArn}, + Response: &config.BucketSseKeyArn, + Opts: []survey.AskOpt{survey.WithValidator(validateOptionalAwsArnFormat)}, + Checks: []*bool{&config.BucketEncryptionEnabled}, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Confirm{Message: QuestionCloudtrailS3BucketNotification, Default: config.S3BucketNotification}, + Response: &config.S3BucketNotification, + }, + }, !config.CloudtrailUseExistingTrail); err != nil { + return err + } + + return nil +} + +func promptCloudtrailSNSQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailSnsTopicName, Default: config.SnsTopicName}, + Response: &config.SnsTopicName, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Confirm{Message: QuestionCloudtrailSnsEnableEncryption, Default: config.SnsTopicEncryptionEnabled}, + Response: &config.SnsTopicEncryptionEnabled, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailSnsEncryptionKeyArn, Default: config.SnsTopicEncryptionKeyArn}, + Response: &config.SnsTopicEncryptionKeyArn, + Opts: []survey.AskOpt{survey.WithValidator(validateOptionalAwsArnFormat)}, + Checks: []*bool{&config.SnsTopicEncryptionEnabled}, + }, + }, !config.CloudtrailUseExistingSNSTopic); err != nil { + return err + } + + return nil +} + +func promptCloudtrailSQSQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailSqsQueueName, Default: config.SqsQueueName}, + Response: &config.SqsQueueName, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Confirm{Message: QuestionCloudtrailSqsEnableEncryption, Default: config.SqsEncryptionEnabled}, + Response: &config.SqsEncryptionEnabled, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailSqsEncryptionKeyArn, Default: config.SqsEncryptionKeyArn}, + Response: &config.SqsEncryptionKeyArn, + Opts: []survey.AskOpt{survey.WithValidator(validateOptionalAwsArnFormat)}, + Checks: []*bool{&config.SqsEncryptionEnabled}, + }, + }); err != nil { + return err + } + return nil +} + +func promptCloudtrailIAMQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { + // ensure struct is initialized + if config.ExistingIamRole == nil { + config.ExistingIamRole = &aws.ExistingIamRoleDetails{} + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailExistingIamRoleName, Default: config.ExistingIamRole.Name}, + Response: &config.ExistingIamRole.Name, + Required: true, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailExistingIamRoleArn, Default: config.ExistingIamRole.Arn}, + Response: &config.ExistingIamRole.Arn, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Required: true, + }, + { + Icon: IconCloudTrail, + Prompt: &survey.Input{Message: QuestionCloudtrailExistingIamRoleExtID, Default: config.ExistingIamRole.ExternalId}, + Response: &config.ExistingIamRole.ExternalId, + Required: true, + }}); err != nil { + return err + } + + return nil +} + +func promptAwsAccountsQuestions( + accounts *[]aws.AwsSubAccount, + questionIcon string, + questionProfile string, + questionRegion string, + questionAddMore string, + questionReplace string, + askFirst bool, +) error { + if !cli.InteractiveMode() { + return nil + } + + askAgain := true + newAccounts := []aws.AwsSubAccount{} + + // Ask if replacing existing accounts + if len(*accounts) > 0 { + accountListing := []string{} + for _, account := range *accounts { + accountListing = append( + accountListing, + fmt.Sprintf("%s:%s", account.AwsProfile, account.AwsRegion), + ) + } + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: questionIcon, + Prompt: &survey.Confirm{ + Message: fmt.Sprintf( + questionReplace, + strings.Trim(strings.Join(strings.Fields(fmt.Sprint(accountListing)), ", "), "[]"), + ), + }, + Response: &askAgain, + }); err != nil { + return err + } + } + + if askFirst { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: questionIcon, + Prompt: &survey.Confirm{Message: questionAddMore}, + Response: &askAgain, + }); err != nil { + return err + } + } + + for askAgain { + var profile, region string + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Icon: questionIcon, + Prompt: &survey.Input{Message: questionProfile}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsProfile)}, + Required: true, + Response: &profile, + }, + { + Icon: questionIcon, + Prompt: &survey.Input{Message: questionRegion}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, + Required: true, + Response: ®ion, + }, + }); err != nil { + return err + } + alias := fmt.Sprintf("%s-%s", profile, region) + newAccounts = append( + newAccounts, + aws.AwsSubAccount{AwsProfile: profile, AwsRegion: region, Alias: alias}, + ) + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Icon: questionIcon, + Prompt: &survey.Confirm{Message: questionAddMore}, + Response: &askAgain, + }); err != nil { + return err + } + } + + if len(newAccounts) > 0 { + *accounts = newAccounts + } + + return nil +} + +func writeArgsCache(a *aws.GenerateAwsTfConfigurationArgs) { + if !a.IsEmpty() { + // If ExistingIamRole is partially set, don't write this to cache; the values won't work when loaded + if a.ExistingIamRole.IsPartial() { + a.ExistingIamRole = nil + } + cli.WriteAssetToCache(CachedAwsArgsKey, time.Now().Add(time.Hour*1), a) + } +} + +func writeExtraStateCache(a *aws.AwsGenerateCommandExtraState) { + if !a.IsEmpty() { + cli.WriteAssetToCache(CachedAwsExtraStateKey, time.Now().Add(time.Hour*1), a) + } +} + +// Entry point for launching a survey to build out the required generation parameters +func promptAwsGenerate( + config *aws.GenerateAwsTfConfigurationArgs, + extraState *aws.AwsGenerateCommandExtraState, +) error { + // Cache for later use if generation is abandon and in interactive mode + if cli.InteractiveMode() { + defer writeArgsCache(config) + defer writeExtraStateCache(extraState) + } + + // Core questions + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{ + Message: QuestionEnableAwsOrganization, + Default: config.AwsOrganization, + }, + Response: &config.AwsOrganization, + }, + { + Prompt: &survey.Input{Message: QuestionMainAwsProfile, Default: config.AwsProfile}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsProfile)}, + Response: &config.AwsProfile, + Required: true, + }, + { + Prompt: &survey.Input{Message: QuestionMainAwsRegion, Default: config.AwsRegion}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, + Response: &config.AwsRegion, + Required: true, + }, + }); err != nil { + return err + } + + if err := promptAgentlessQuestions(config); err != nil { + return err + } + if err := promptConfigQuestions(config); err != nil { + return err + } + if err := promptCloudtrailQuestions(config, extraState); err != nil { + return err + } + + // Custom ouput location + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionAwsOutputLocation, Default: extraState.Output}, + Response: &extraState.Output, + Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, + }); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_controltower.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_controltower.go new file mode 100644 index 000000000..8eede5f4e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_controltower.go @@ -0,0 +1,730 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/lacework/go-sdk/lwgenerate/aws_controltower" + + "github.com/imdario/mergo" + "github.com/spf13/cobra" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" +) + +var ( + QuestionAwsControlTowerCoreS3Bucket = "Provide the Arn of the S3 Bucket for consolidated CloudTrail:" + QuestionAwsControlTowerCoreSnsTopic = "Provide the Arn of the SNS Topic:" + QuestionAwsControlTowerCoreLogProfile = "Provide the aws profile of the 'log_archive' account:" + QuestionAwsControlTowerCoreLogRegion = "Provide the aws region of the 'log_archive' account:" + QuestionAwsControlTowerCoreAuditProfile = "Provide the aws profile of the 'audit' account:" + QuestionAwsControlTowerCoreAuditRegion = "Provide the aws region of the 'audit' account:" + QuestionAwsControlTowerConfigureAdvanced = "Configure advanced integration options?" + QuestionAwsControlTowerCustomizeOutputLocation = "Provide the location for the output to be written:" + + ControlTowerConfigureExistingIamRoleOpt = "Configure existing Iam Role?" + QuestionAwsControlTowerCoreIamRoleName = "Specify Existing Iam Role name:" + QuestionAwsControlTowerCoreIamRoleArn = "Specify Existing Iam Arn:" + QuestionAwsControlTowerCoreIamRoleExternalID = "Specify Existing Iam Role external ID:" + ControlTowerIntegrationNameOpt = "Customize integration name?" + QuestionControlTowerIntegrationName = "Specify a custom integration name:" + ControlTowerIntegrationPrefixOpt = "Customize resource prefix name?" + QuestionControlTowerPrefix = "Specify a prefix name for resources:" + ControlTowerIntegrationSqsOpt = "Customize sqs queue name?" + QuestionControlTowerSqsQueueName = "Specify a name for sqs queue:" + QuestionControlTowerOrgAccountMappingsLWDefaultAccount = "Specify org account mappings default Lacework account:" + QuestionControlTowerOrgAccountMappingAnotherAdvancedOpt = "Configure another org account mapping?" + QuestionControlTowerOrgAccountMappingsLWAccount = "Specify lacework account: " + QuestionControlTowerOrgAccountMappingsAwsAccounts = "Specify aws accounts:" + ControlTowerAdvancedOptLocation = "Customize output location" + ControlTowerAdvancedOptMappings = "Configure Org Account Mappings" + QuestionControlTowerAnotherAdvancedOpt = "Configure another advanced integration option?" + ControlTowerAdvancedOptDone = "Done" + + GenerateAwsControlTowerCommandState = &aws_controltower.GenerateAwsControlTowerTfConfigurationArgs{} + GenerateAwsControlTowerCommandExtraState = &AwsControlTowerGenerateCommandExtraState{} + CachedAssetAwsControlTowerIacParams = "iac-aws-controltower-generate-params" + CachedAssetAwsControlTowerExtraState = "iac-aws-controltower-extra-state" + + generateAwsControlTowerTfCommand = &cobra.Command{ + Use: "controltower", + Short: "Generate and/or execute Terraform code for ControlTower integration", + Long: `Use this command to generate Terraform code for deploying Lacework with Aws Cloudtrail and +ControlTower. + +By default, this command interactively prompts for the required information to set up the new cloud account. +In interactive mode, this command will: + +* Prompt for the required information to set up the integration +* Generate new Terraform code using the inputs +* Optionally, run the generated Terraform code: + * If Terraform is already installed, the version is verified as compatible for use + * If Terraform is not installed, or the version installed is not compatible, a new + version will be installed into a temporary location + * Once Terraform is detected or installed, the Terraform plan is executed + * The command prompts you with the outcome of the plan and allows you to view more + details or continue with Terraform apply + * If confirmed, Terraform apply runs, completing the setup of the cloud account + +This command can also be run in noninteractive mode. +See help output for more details on the parameter values required for Terraform code generation. +`, + RunE: func(cmd *cobra.Command, args []string) error { + // Generate TF Code + cli.StartProgress("Generating Terraform Code...") + + // Explicitly set Lacework profile if it was passed in main args + if cli.Profile != "default" { + GenerateAwsControlTowerCommandState.LaceworkProfile = cli.Profile + } + + // Setup modifiers for NewTerraform constructor + mods := []aws_controltower.AwsControlTowerTerraformModifier{ + aws_controltower.WithLaceworkAccountID(GenerateAwsControlTowerCommandState.LaceworkAccountID), + aws_controltower.WithSubaccounts(GenerateAwsControlTowerCommandState.SubAccounts...), + aws_controltower.WithSqsQueueName(GenerateAwsControlTowerCommandState.SqsQueueName), + aws_controltower.WithPrefix(GenerateAwsControlTowerCommandState.Prefix), + aws_controltower.WithCrossAccountPolicyName(GenerateAwsControlTowerCommandState.CrossAccountPolicyName), + aws_controltower.WithSubaccounts(GenerateAwsControlTowerCommandState.SubAccounts...), + aws_controltower.WithLaceworkProfile(GenerateAwsControlTowerCommandState.LaceworkProfile), + aws_controltower.WithExternalIdLength(GenerateAwsControlTowerCommandState.ExternalIdLength), + aws_controltower.WithWaitTime(GenerateAwsControlTowerCommandState.WaitTime), + aws_controltower.WithTags(GenerateAwsControlTowerCommandState.Tags), + aws_controltower.WithKmsKeyArn(GenerateAwsControlTowerCommandState.KmsKeyArn), + aws_controltower.WithLaceworkIntegrationName(GenerateAwsControlTowerCommandState.LaceworkIntegrationName), + } + + if useExistingIamRole(GenerateAwsControlTowerCommandState) { + mods = append(mods, aws_controltower.WithExisitingIamRole( + GenerateAwsControlTowerCommandState.IamRoleArn, + GenerateAwsControlTowerCommandState.IamRoleName, + GenerateAwsControlTowerCommandState.IamRoleExternalID, + )) + } + + if !GenerateAwsControlTowerCommandState.OrgAccountMappings.IsEmpty() { + mods = append(mods, aws_controltower.WithOrgAccountMappings(GenerateAwsControlTowerCommandState.OrgAccountMappings)) + } + + if GenerateAwsControlTowerCommandState.EnableLogFileValidation { + mods = append(mods, aws_controltower.WithEnableLogFileValidation()) + } + + data := aws_controltower.NewTerraform( + GenerateAwsControlTowerCommandState.S3BucketArn, + GenerateAwsControlTowerCommandState.SNSTopicArn, + mods...) + + // Generate + hcl, err := data.Generate() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "failed to generate terraform code") + } + + // Write-out generated code to location specified + dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "aws_controltower") + if err != nil { + return err + } + + // Prompt to execute + err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{ + Default: GenerateAwsControlTowerCommandExtraState.TerraformApply, + Message: QuestionRunTfPlan, + }, + Response: &GenerateAwsControlTowerCommandExtraState.TerraformApply, + }) + + if err != nil { + return errors.Wrap(err, "failed to prompt for terraform execution") + } + + // Execute + locationDir, _ := determineOutputDirPath(dirname, "aws_controltower") + if GenerateAwsControlTowerCommandExtraState.TerraformApply { + // Execution pre-run check + err := executionPreRunChecks(dirname, locationDir, "aws_controltower") + if err != nil { + return err + } + } + + // Output where code was generated + if !GenerateAwsControlTowerCommandExtraState.TerraformApply { + cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) + } + + return nil + }, + PreRunE: func(cmd *cobra.Command, _ []string) error { + // Validate output location is OK if supplied + dirname, err := cmd.Flags().GetString("output") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateOutputLocation(dirname); err != nil { + return err + } + + // Validate aws profile, if passed + profile, err := cmd.Flags().GetString("aws_profile") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsProfile(profile); profile != "" && err != nil { + return err + } + + // Validate s3_bucket_arn, if passed + s3BucketArn, err := cmd.Flags().GetString("s3_bucket_arn") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsArnFormat(s3BucketArn); s3BucketArn != "" && err != nil { + return err + } + + // Validate sns_topic_arn, if passed + snsTopicArn, err := cmd.Flags().GetString("sns_topic_arn") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsArnFormat(snsTopicArn); snsTopicArn != "" && err != nil { + return err + } + + // Parse audit_account, if passed + if cmd.Flags().Changed("audit_account") { + if err := parseAuditAccountFlag(GenerateAwsControlTowerCommandState); err != nil { + return err + } + } + + GenerateAwsControlTowerCommandState.SubAccounts = append(GenerateAwsControlTowerCommandState.SubAccounts, + aws_controltower.AwsSubAccount{ + AwsProfile: GenerateAwsControlTowerCommandState.AuditProfile, + AwsRegion: GenerateAwsControlTowerCommandState.AuditRegion}) + + // Parse log_archive_account, if passed + if cmd.Flags().Changed("log_archive_account") { + if err := parseLogArchiveAccountFlag(GenerateAwsControlTowerCommandState); err != nil { + return err + } + } + + GenerateAwsControlTowerCommandState.SubAccounts = append(GenerateAwsControlTowerCommandState.SubAccounts, + aws_controltower.AwsSubAccount{ + AwsProfile: GenerateAwsControlTowerCommandState.LogArchiveProfile, + AwsRegion: GenerateAwsControlTowerCommandState.LogArchiveRegion}) + + // Parse org_account_mapping json, if passed + if cmd.Flags().Changed("org_account_mapping") { + if err := parseOrgAccountMappingsFlag(GenerateAwsControlTowerCommandState); err != nil { + return err + } + } + + // Load any cached inputs if interactive + if cli.InteractiveMode() { + cachedOptions := &aws_controltower.GenerateAwsControlTowerTfConfigurationArgs{} + iacParamsExpired := cli.ReadCachedAsset(CachedAssetAwsControlTowerIacParams, &cachedOptions) + if iacParamsExpired { + cli.Log.Debug("loaded previously set values for AWS ControlTower IAC generation") + } + + extraState := &AwsControlTowerGenerateCommandExtraState{} + extraStateParamsExpired := cli.ReadCachedAsset(CachedAssetAwsControlTowerExtraState, &extraState) + if extraStateParamsExpired { + cli.Log.Debug("loaded previously set values for AWS ControlTower IAC generation (extra state)") + } + + // Determine if previously cached options exists; prompt user if they'd like to continue + answer := false + if !iacParamsExpired || !extraStateParamsExpired { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, + Response: &answer, + }); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs + // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get + // re-prompted. + if answer { + // Merge cached inputs to current options (current options win) + if err := mergo.Merge(GenerateAwsControlTowerCommandState, cachedOptions); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + if err := mergo.Merge(GenerateAwsControlTowerCommandExtraState, extraState); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + } + + // Collect and/or confirm parameters + err = promptAwsControlTowerGenerate(GenerateAwsControlTowerCommandState, + GenerateAwsControlTowerCommandExtraState) + if err != nil { + return errors.Wrap(err, "collecting/confirming parameters") + } + + return nil + }, + } +) + +func useExistingIamRole(args *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) bool { + return args.IamRoleArn != "" && args.IamRoleExternalID != "" && args.IamRoleName != "" +} + +func parseAuditAccountFlag(args *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { + parsedAccount := strings.Split(args.AuditAccount, ":") + if len(parsedAccount) != 2 { + return errors.New("invalid audit_account. Format must be 'profile:region'") + } + + args.AuditProfile = parsedAccount[0] + args.AuditRegion = parsedAccount[1] + return nil +} + +func parseLogArchiveAccountFlag(args *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { + parsedAccount := strings.Split(args.LogArchiveAccount, ":") + + if len(parsedAccount) != 2 { + fmt.Println(parsedAccount) + return errors.New("invalid log_archive_account. Format must be 'profile:region'") + } + + args.LogArchiveProfile = parsedAccount[0] + args.LogArchiveRegion = parsedAccount[1] + return nil +} + +func parseOrgAccountMappingsFlag(args *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { + if err := json.Unmarshal([]byte(args.OrgAccountMappingsJson), &args.OrgAccountMappings); err != nil { + return errors.Wrap(err, "failed to parse 'org_account_mapping'") + } + + return nil +} + +type AwsControlTowerGenerateCommandExtraState struct { + AskAdvanced bool + Output string + ConfigureBucketSettings bool + UseExistingKmsKey bool + MultiRegion bool + TerraformApply bool +} + +func (controltower *AwsControlTowerGenerateCommandExtraState) isEmpty() bool { + return controltower.Output == "" && + !controltower.AskAdvanced && + !controltower.ConfigureBucketSettings && + !controltower.UseExistingKmsKey && + !controltower.TerraformApply +} + +// Flush current state of the struct to disk, provided it's not empty +func (controltower *AwsControlTowerGenerateCommandExtraState) writeCache() { + if !controltower.isEmpty() { + cli.WriteAssetToCache(CachedAssetAwsControlTowerExtraState, time.Now().Add(time.Hour*1), controltower) + } +} + +func initGenerateAwsControlTowerTfCommandFlags() { + // add flags to sub commands + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.LaceworkAccountID, + "lacework_aws_account_id", "", "the Lacework AWS root account id") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.S3BucketArn, + "s3_bucket_arn", "", "the S3 Bucket for consolidated CloudTrail") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.SNSTopicArn, + "sns_topic_arn", "", "the SNS Topic") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.LogArchiveAccount, + "log_archive_account", "", "The log archive account flag input in the format profile:region") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.AuditAccount, + "audit_account", "", "The audit account flag input in the format profile:region") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.OrgAccountMappingsJson, + "org_account_mapping", "", "Org account mapping json string. Example: "+ + "'{\"default_lacework_account\":\"main\", \"mapping\": [{ \"aws_accounts\": [\"123456789011\"], "+ + "\"lacework_account\": \"sub-account-1\"}]}'") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.IamRoleExternalID, + "iam_role_external_id", + "", + "specify the external id of the existing iam role") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.IamRoleName, + "iam_role_name", + "", + "specify the name of the existing iam role") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.IamRoleArn, + "iam_role_arn", + "", + "specify the arn of the existing iam role") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.SqsQueueName, + "sqs_queue_name", + "", + "specify the name of the sqs queue") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandState.Prefix, + "prefix", + "", + "specify the prefix that will be used at the beginning of every generated resource") + + generateAwsControlTowerTfCommand.PersistentFlags().BoolVar( + &GenerateAwsControlTowerCommandExtraState.TerraformApply, + "apply", + false, + "run terraform apply without executing plan or prompting") + + generateAwsControlTowerTfCommand.PersistentFlags().StringVar( + &GenerateAwsControlTowerCommandExtraState.Output, + "output", + "", + "location to write generated content") +} + +func promptCustomizeControlTowerOutputLocation(extraState *AwsControlTowerGenerateCommandExtraState) error { + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionAwsControlTowerCustomizeOutputLocation, + Default: extraState.Output}, + Response: &extraState.Output, + Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, + Required: true, + }) + + return err +} + +func promptAwsIamRoleQuestions(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionAwsControlTowerCoreIamRoleName, + Default: input.IamRoleName, + }, + Opts: []survey.AskOpt{}, + Response: &input.IamRoleName, + }); err != nil { + return err + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionAwsControlTowerCoreIamRoleArn, + Default: input.IamRoleArn, + }, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &input.IamRoleArn, + }); err != nil { + return err + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionAwsControlTowerCoreIamRoleExternalID, + Default: input.IamRoleExternalID, + }, + Response: &input.IamRoleExternalID, + }); err != nil { + return err + } + + return nil +} + +func promptCustomIntegrationName(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionControlTowerIntegrationName, + Default: input.LaceworkIntegrationName, + }, + Opts: []survey.AskOpt{}, + Response: &input.LaceworkIntegrationName, + }) + + return err +} + +func promptCustomPrefix(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionControlTowerPrefix, + Default: input.Prefix, + }, + Opts: []survey.AskOpt{}, + Response: &input.Prefix, + }) + + return err +} + +func promptCustomSqsQueueName(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionControlTowerSqsQueueName, + Default: input.SqsQueueName, + }, + Opts: []survey.AskOpt{}, + Response: &input.SqsQueueName, + }) + + return err +} + +func promptControlTowerAddOrgAccountMappings(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { + mapping := aws_controltower.OrgAccountMap{} + var accountsAnswer string + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionControlTowerOrgAccountMappingsLWAccount}, + Response: &mapping.LaceworkAccount, + }, + { + Prompt: &survey.Multiline{Message: QuestionControlTowerOrgAccountMappingsAwsAccounts}, + Response: &accountsAnswer, + }, + }); err != nil { + return err + } + mapping.AwsAccounts = strings.Split(accountsAnswer, "\n") + input.OrgAccountMappings.Mapping = append(input.OrgAccountMappings.Mapping, mapping) + return nil +} + +func promptControlTowerOrgAccountMappings(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{ + Message: QuestionControlTowerOrgAccountMappingsLWDefaultAccount, + Default: input.OrgAccountMappings.DefaultLaceworkAccount}, + Response: &input.OrgAccountMappings.DefaultLaceworkAccount, + }, + }); err != nil { + return err + } + + if err := promptControlTowerAddOrgAccountMappings(input); err != nil { + return err + } + + var askAgain bool + for { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionControlTowerOrgAccountMappingAnotherAdvancedOpt}, + Response: &askAgain}); err != nil { + return err + } + + if !askAgain { + break + } + + if err := promptControlTowerAddOrgAccountMappings(input); err != nil { + return err + } + } + + return nil +} + +func askAdvancedControlTowerOptions(config *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs, + extraState *AwsControlTowerGenerateCommandExtraState) error { + answer := "" + + //Prompt for options + for answer != AwsAdvancedOptDone { + var options []string + + options = append(options, + ControlTowerConfigureExistingIamRoleOpt, + ControlTowerIntegrationNameOpt, + ControlTowerAdvancedOptLocation, + ControlTowerIntegrationPrefixOpt, + ControlTowerIntegrationSqsOpt, + ControlTowerAdvancedOptMappings, + ControlTowerAdvancedOptDone) + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: "Which options would you like to configure?", + Options: options, + }, + Response: &answer, + }); err != nil { + return err + } + + // Based on response, prompt for actions + switch answer { + case ControlTowerConfigureExistingIamRoleOpt: + if err := promptAwsIamRoleQuestions(config); err != nil { + return err + } + config.UseExistingIamRole = true + case ControlTowerIntegrationNameOpt: + if err := promptCustomIntegrationName(config); err != nil { + return err + } + case ControlTowerIntegrationPrefixOpt: + if err := promptCustomPrefix(config); err != nil { + return err + } + case ControlTowerIntegrationSqsOpt: + if err := promptCustomSqsQueueName(config); err != nil { + return err + } + case ControlTowerAdvancedOptLocation: + if err := promptCustomizeControlTowerOutputLocation(extraState); err != nil { + return err + } + case ControlTowerAdvancedOptMappings: + if err := promptControlTowerOrgAccountMappings(config); err != nil { + return err + } + } + + // Re-prompt if not done + innerAskAgain := true + if answer == ControlTowerAdvancedOptDone { + innerAskAgain = false + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Checks: []*bool{&innerAskAgain}, + Prompt: &survey.Confirm{Message: QuestionControlTowerAnotherAdvancedOpt, Default: false}, + Response: &innerAskAgain, + }); err != nil { + return err + } + + if !innerAskAgain { + answer = AwsAdvancedOptDone + } + } + + return nil +} + +func controltowerConfigIsEmpty(g *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) bool { + return g.SNSTopicArn == "" && + g.S3BucketArn == "" && + g.LaceworkProfile == "" +} + +func writeAwsControlTowerGenerationArgsCache(a *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) { + if !controltowerConfigIsEmpty(a) { + cli.WriteAssetToCache(CachedAssetAwsControlTowerIacParams, time.Now().Add(time.Hour*1), a) + } +} + +// entry point for launching a survey to build out the required generation parameters +func promptAwsControlTowerGenerate( + config *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs, + extraState *AwsControlTowerGenerateCommandExtraState, +) error { + + // Cache for later use if generation is abandoned and in interactive mode + if cli.InteractiveMode() { + defer writeAwsControlTowerGenerationArgsCache(config) + defer extraState.writeCache() + } + + // Set Flags if set + + // prompt ControlTower core questions + if err := promptAwsControlTowerCoreQuestions(config, extraState); err != nil { + return err + } + + // Find out if the customer wants to specify more advanced features + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionAwsControlTowerConfigureAdvanced, + Default: extraState.AskAdvanced}, + Response: &extraState.AskAdvanced, + }); err != nil { + return err + } + + // Keep prompting for advanced options until the say done + if extraState.AskAdvanced { + if err := askAdvancedControlTowerOptions(config, extraState); err != nil { + return err + } + } + + return nil +} + +func init() { + initGenerateAwsControlTowerTfCommandFlags() +} + +func promptAwsControlTowerCoreQuestions(config *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs, + state *AwsControlTowerGenerateCommandExtraState) error { + if err := SurveyMultipleQuestionWithValidation( + []SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreS3Bucket, Default: config.S3BucketArn}, + Response: &config.S3BucketArn, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + }, + { + Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreSnsTopic, Default: config.SNSTopicArn}, + Response: &config.SNSTopicArn, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + }, + { + Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreLogProfile, Default: config.LogArchiveProfile}, + Response: &config.LogArchiveProfile, + }, + { + Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreLogRegion, Default: config.LogArchiveRegion}, + Response: &config.LogArchiveRegion, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, + }, + { + Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreAuditProfile, Default: config.AuditProfile}, + Response: &config.AuditProfile, + }, + { + Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreAuditRegion, Default: config.AuditRegion}, + Response: &config.AuditRegion, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, + }, + }); err != nil { + return err + } + config.SubAccounts = []aws_controltower.AwsSubAccount{ + aws_controltower.NewAwsSubAccount(config.LogArchiveProfile, config.LogArchiveRegion, "log_archive"), + aws_controltower.NewAwsSubAccount(config.AuditProfile, config.AuditRegion, "audit")} + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_eks_audit.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_eks_audit.go new file mode 100644 index 000000000..e992fa2b1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_eks_audit.go @@ -0,0 +1,983 @@ +package cmd + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/lacework/go-sdk/lwgenerate/aws_eks_audit" + + "github.com/imdario/mergo" + "github.com/spf13/cobra" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" +) + +var ( + // Define question text here, so they can be reused in testing + QuestionEksAuditMultiRegion = "Integrate clusters in more than one region?" + QuestionEksAuditRegionClusterCurrent = "Currently configured regions and clusters: %s. " + + "Configure additional?" + QuestionEksAuditRegion = "Specify AWS region:" + QuestionEksAuditRegionClusters = "Specify a comma-seperated list of clusters in region" + + " to ingest EKS Audit Logs:" + QuestionEksAuditAdditionalRegion = "Configure another AWS region?" + + QuestionEksAuditConfigureAdvanced = "Configure advanced integration options?" + + // S3 Bucket Questions + QuestionUseExistingBucket = "Use existing bucket?" + QuestionExistingBucketArn = "Specify an existing bucket ARN used for EKS audit log:" + EksAuditConfigureBucket = "Configure bucket settings" + QuestionEksAuditBucketVersioning = "Enable access versioning on the new bucket?" + QuestionEksAuditMfaDeleteS3Bucket = "Should MFA object deletion be required for the new bucket?" + QuestionEksAuditBucketLifecycle = "Specify the bucket lifecycle expiration days: (optional)" + QuestionEksAuditBucketEncryption = "Enable encryption for the new bucket?" + QuestionEksAuditBucketSseAlgorithm = "Specify the bucket SSE Algorithm: (optional)" + QuestionEksAuditBucketExistingKey = "Use existing KMS key?" + QuestionEksAuditBucketKeyArn = "Specify the bucket existing SSE KMS key ARN:" + QuestionEksAuditKmsKeyRotation = "Should the KMS key have rotation enabled?" + QuestionEksAuditKmsKeyDeletionDays = "Specify the KMS key deletion days: (optional)" + + // SNS Topic Questions + EksAuditConfigureSns = "Configure SNS settings" + QuestionEksAuditSnsEncryption = "Enable encryption on SNS topic when creating?" + QuestionEksAuditSnsEncryptionKeyArn = "Specify existing KMS encryption key ARN for SNS topic (optional)" + + // Cloudwatch IAM Questions + EksAuditExistingCwIamRole = "Configure and use existing Cloudwatch IAM role" + QuestionEksAuditExistingCwIamArn = "Specify an existing Cloudwatch IAM role ARN:" + + // Firehose Questions + EksAuditConfigureFh = "Configure Firehose settings" + QuestionEksAuditExistingFhIamRole = "Use existing Firehose IAM role?" + QuestionEksAuditExistingFhIamArn = "Specify an existing Firehose IAM role ARN:" + QuestionEksAuditFhEncryption = "Enable encryption on Firehose when creating?" + QuestionEksAuditFhEncryptionKeyArn = "Specify existing KMS encryption key ARN for Firehose (optional)" + + // Cross Account IAM Questions + EksAuditExistingCaIamRole = "Configure and use existing Cross Account IAM role" + QuestionEksAuditExistingCaIamArn = "Specify an existing Cross Account IAM role ARN:" + QuestionEksAuditExistingCaIamExtID = "Specify the external ID to be used with the existing IAM role:" + + // Customize integration name + EksAuditIntegrationNameOpt = "Customize integration name" + QuestionEksAuditCustomIntegrationName = "Specify a custom integration name: (optional)" + + // Customize output location + EksAuditAdvancedOptLocation = "Customize output location" + QuestionEksAuditCustomizeOutputLocation = "Provide the location for the output to be written:" + + QuestionEksAuditAnotherAdvancedOpt = "Configure another advanced integration option" + EksAuditAdvancedOptDone = "Done" + + // AwsEksAuditRegionRegex regex used for validating region input; note intentionally does not match gov cloud + AwsEksAuditRegionRegex = `(af|ap|ca|eu|me|sa|us)-(central|(north|south)?(east|west)?)-\d` + + GenerateAwsEksAuditCommandState = &aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs{} + GenerateAwsEksAuditCommandExtraState = &AwsEksAuditGenerateCommandExtraState{} + GenerateAwsEksAuditExistingRoleState = &aws_eks_audit.ExistingCrossAccountIamRoleDetails{} + CachedAssetAwsEksAuditIacParams = "iac-aws-eks-audit-generate-params" + CachedAssetAwsEksAuditExtraState = "iac-aws-eks-audit-extra-state" + + // aws-eks-audit-log command is used to generate TF code for aws-eks-audit-log + generateAwsEksAuditTfCommand = &cobra.Command{ + Use: "eks", + Short: "Generate and/or execute Terraform code for EKS integration", + Long: `Use this command to generate Terraform code for deploying Lacework into an EKS +environment. + +By default, this command interactively prompts for the required information to set up the new cloud account. +In interactive mode, this command will: + +* Prompt for the required information to set up the integration +* Generate new Terraform code using the inputs +* Optionally, run the generated Terraform code: + * If Terraform is already installed, the version is verified as compatible for use + * If Terraform is not installed, or the version installed is not compatible, a new + version will be installed into a temporary location + * Once Terraform is detected or installed, the Terraform plan is executed + * The command prompts you with the outcome of the plan and allows you to view more + details or continue with Terraform apply + * If confirmed, Terraform apply runs, completing the setup of the cloud account + +This command can also be run in noninteractive mode. +See help output for more details on the parameter values required for Terraform code generation. +`, + RunE: func(cmd *cobra.Command, args []string) error { + // Generate TF Code + cli.StartProgress("Generating Terraform Code...") + + // Explicitly set Lacework profile if it was passed in main args + if cli.Profile != "default" { + GenerateAwsEksAuditCommandState.LaceworkProfile = cli.Profile + } + + // Setup modifiers for NewTerraform constructor + mods := []aws_eks_audit.AwsEksAuditTerraformModifier{ + aws_eks_audit.WithAwsProfile(GenerateAwsEksAuditCommandState.AwsProfile), + aws_eks_audit.WithLaceworkAccountID(GenerateAwsEksAuditCommandState.LaceworkAccountID), + aws_eks_audit.WithBucketLifecycleExpirationDays(GenerateAwsEksAuditCommandState.BucketLifecycleExpirationDays), + aws_eks_audit.WithExistingBucketArn(GenerateAwsEksAuditCommandState.ExistinglBucketArn), + aws_eks_audit.WithBucketSseAlgorithm(GenerateAwsEksAuditCommandState.BucketSseAlgorithm), + aws_eks_audit.WithBucketSseKeyArn(GenerateAwsEksAuditCommandState.BucketSseKeyArn), + aws_eks_audit.WithEksAuditIntegrationName(GenerateAwsEksAuditCommandState.EksAuditIntegrationName), + aws_eks_audit.WithExistingCloudWatchIamRoleArn(GenerateAwsEksAuditCommandState.ExistingCloudWatchIamRoleArn), + aws_eks_audit.WithExistingCrossAccountIamRole(GenerateAwsEksAuditCommandState.ExistingCrossAccountIamRole), + aws_eks_audit.WithExistingFirehoseIamRoleArn(GenerateAwsEksAuditCommandState.ExistingFirehoseIamRoleArn), + aws_eks_audit.WithFilterPattern(GenerateAwsEksAuditCommandState.FilterPattern), + aws_eks_audit.WithFirehoseEncryptionKeyArn(GenerateAwsEksAuditCommandState.FirehoseEncryptionKeyArn), + aws_eks_audit.WithKmsKeyDeletionDays(GenerateAwsEksAuditCommandState.KmsKeyDeletionDays), + aws_eks_audit.WithPrefix(GenerateAwsEksAuditCommandState.Prefix), + aws_eks_audit.WithParsedRegionClusterMap(GenerateAwsEksAuditCommandState.ParsedRegionClusterMap), + aws_eks_audit.WithSnsTopicEncryptionKeyArn(GenerateAwsEksAuditCommandState.SnsTopicEncryptionKeyArn), + aws_eks_audit.WithLaceworkProfile(GenerateAwsEksAuditCommandState.LaceworkProfile), + aws_eks_audit.EnableBucketEncryption(GenerateAwsEksAuditCommandState.BucketEnableEncryption), + aws_eks_audit.EnableBucketVersioning(GenerateAwsEksAuditCommandState.BucketVersioning), + aws_eks_audit.EnableFirehoseEncryption(GenerateAwsEksAuditCommandState.FirehoseEncryptionEnabled), + aws_eks_audit.EnableSnsTopicEncryption(GenerateAwsEksAuditCommandState.SnsTopicEncryptionEnabled), + aws_eks_audit.EnableBucketVersioning(GenerateAwsEksAuditCommandState.BucketVersioning), + aws_eks_audit.EnableKmsKeyRotation(GenerateAwsEksAuditCommandState.KmsKeyRotation), + } + + if GenerateAwsEksAuditCommandState.UseExistinglBucket { + mods = append(mods, aws_eks_audit.EnableUseExistingBucket()) + } + + if GenerateAwsEksAuditCommandState.BucketEnableMfaDelete { + mods = append(mods, aws_eks_audit.EnableBucketMfaDelete()) + } + + // Create new struct + data := aws_eks_audit.NewTerraform(mods...) + + // Generate + hcl, err := data.Generate() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "failed to generate terraform code") + } + + // Write-out generated code to location specified + dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "aws_eks_audit") + if err != nil { + return err + } + + // Prompt to execute + err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{ + Default: GenerateAwsEksAuditCommandExtraState.TerraformApply, + Message: QuestionRunTfPlan, + }, + Response: &GenerateAwsEksAuditCommandExtraState.TerraformApply, + }) + + if err != nil { + return errors.Wrap(err, "failed to prompt for terraform execution") + } + + // Execute + locationDir, _ := determineOutputDirPath(dirname, "aws_eks_audit") + if GenerateAwsEksAuditCommandExtraState.TerraformApply { + // Execution pre-run check + err := executionPreRunChecks(dirname, locationDir, "aws_eks_audit") + if err != nil { + return err + } + } + + // Output where code was generated + if !GenerateAwsEksAuditCommandExtraState.TerraformApply { + cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) + } + + return nil + }, + PreRunE: func(cmd *cobra.Command, _ []string) error { + // Validate output location is OK if supplied + dirname, err := cmd.Flags().GetString("output") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateOutputLocation(dirname); err != nil { + return err + } + + // Validate aws profile, if passed + profile, err := cmd.Flags().GetString("aws_profile") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsProfile(profile); profile != "" && err != nil { + return err + } + + // Validate bucket sse key ARN, if passed + bucketSseKeyArn, err := cmd.Flags().GetString("bucket_sse_key_arn") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsArnFormat(bucketSseKeyArn); bucketSseKeyArn != "" && err != nil { + return err + } + + // Validate firehose key ARN, if passed + firehoseKeyArn, err := cmd.Flags().GetString("firehose_encryption_key_arn") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsArnFormat(firehoseKeyArn); firehoseKeyArn != "" && err != nil { + return err + } + + // Validate sns topic key ARN, if passed + snsKeyArn, err := cmd.Flags().GetString("sns_topic_encryption_key_arn") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAwsArnFormat(snsKeyArn); snsKeyArn != "" && err != nil { + return err + } + + // Load any cached inputs if interactive + if cli.InteractiveMode() { + cachedOptions := &aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs{} + iacParamsExpired := cli.ReadCachedAsset(CachedAssetAwsEksAuditIacParams, &cachedOptions) + if iacParamsExpired { + cli.Log.Debug("loaded previously set values for AWS EKS Audit IAC generation") + } + + extraState := &AwsEksAuditGenerateCommandExtraState{} + extraStateParamsExpired := cli.ReadCachedAsset(CachedAssetAwsEksAuditExtraState, &extraState) + if extraStateParamsExpired { + cli.Log.Debug("loaded previously set values for AWS EKS Audit IAC generation (extra state)") + } + + // Determine if previously cached options exists; prompt user if they'd like to continue + answer := false + if !iacParamsExpired || !extraStateParamsExpired { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, + Response: &answer, + }); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs + // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get + // re-prompted. + if answer { + // Merge cached inputs to current options (current options win) + if err := mergo.Merge(GenerateAwsEksAuditCommandState, cachedOptions); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + if err := mergo.Merge(GenerateAwsEksAuditCommandExtraState, extraState); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + } + + // Parse regions passed as part of the region cluster map + if len(GenerateAwsEksAuditCommandState.RegionClusterMap) > 0 { + // validate the format of supplied values is correct + + awsParsedRegionClusterMap := make(map[string][]string) + for region, clusters := range GenerateAwsEksAuditCommandState.RegionClusterMap { + // verify each region is a valid aws region + if err := validateStringWithRegex(region, AwsEksAuditRegionRegex, + "invalid region name supplied"); err != nil { + return err + } + // parse the cluster comma-seperated string into a list of clusters + parsedClusters := strings.Split(clusters, ",") + awsParsedRegionClusterMap[region] = append(awsParsedRegionClusterMap[region], parsedClusters...) + } + GenerateAwsEksAuditCommandState.ParsedRegionClusterMap = awsParsedRegionClusterMap + } + + // Collect and/or confirm parameters + err = promptAwsEksAuditGenerate(GenerateAwsEksAuditCommandState, GenerateAwsEksAuditExistingRoleState, + GenerateAwsEksAuditCommandExtraState) + if err != nil { + return errors.Wrap(err, "collecting/confirming parameters") + } + + return nil + }, + } +) + +type AwsEksAuditGenerateCommandExtraState struct { + AskAdvanced bool + Output string + ConfigureBucketSettings bool + UseExistingKmsKey bool + MultiRegion bool + TerraformApply bool +} + +func (eks *AwsEksAuditGenerateCommandExtraState) isEmpty() bool { + return eks.Output == "" && + !eks.AskAdvanced && + !eks.ConfigureBucketSettings && + !eks.UseExistingKmsKey && + !eks.TerraformApply +} + +// Flush current state of the struct to disk, provided it's not empty +func (eks *AwsEksAuditGenerateCommandExtraState) writeCache() { + if !eks.isEmpty() { + cli.WriteAssetToCache(CachedAssetAwsEksAuditExtraState, time.Now().Add(time.Hour*1), eks) + } +} + +func initGenerateAwsEksAuditTfCommandFlags() { + // add flags to sub commands + // TODO Share the help with the interactive generation + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.AwsProfile, "aws_profile", "", "specify aws profile") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.LaceworkAccountID, + "lacework_aws_account_id", "", "the Lacework AWS root account id") + generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( + &GenerateAwsEksAuditCommandState.BucketEnableMfaDelete, + "enable_mfa_delete_s3", + false, + "enable mfa delete on s3 bucket. Requires bucket versioning.") + generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( + &GenerateAwsEksAuditCommandState.BucketEnableEncryption, + "enable_encryption_s3", + true, + "enable encryption on s3 bucket") + + // DEPRECATED + generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( + &GenerateAwsEksAuditCommandState.BucketForceDestroy, + "enable_force_destroy", + true, + "enable force destroy s3 bucket") + errcheckWARN(generateAwsEksAuditTfCommand.PersistentFlags().MarkDeprecated( + "enable_force_destroy", "by default, force destroy is enabled.", + )) + // --- + + generateAwsEksAuditTfCommand.PersistentFlags().IntVar( + &GenerateAwsEksAuditCommandState.BucketLifecycleExpirationDays, + "bucket_lifecycle_exp_days", + 0, + "specify the s3 bucket lifecycle expiration days") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.BucketSseAlgorithm, + "bucket_sse_algorithm", + "", + "specify the encryption algorithm to use for S3 bucket server-side encryption") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.BucketSseKeyArn, + "bucket_sse_key_arn", + "", + "specify the kms key arn to be used for s3. "+ + "(required when bucket_sse_algorithm is aws:kms & using an existing kms key)") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.ExistinglBucketArn, + "existing_bucket_arn", + "", + "specify existing s3 bucket arn for the audit log") + generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( + &GenerateAwsEksAuditCommandState.UseExistinglBucket, + "use_existing_bucket", + false, + "use existing supplied s3 bucket (default false)") + generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( + &GenerateAwsEksAuditCommandState.BucketVersioning, + "enable_bucket_versioning", + true, + "enable s3 bucket versioning") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.EksAuditIntegrationName, + "integration_name", + "", + "specify the name of the eks audit integration") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.ExistingCloudWatchIamRoleArn, + "existing_cw_iam_role_arn", + "", + "specify existing cloudwatch iam role arn to use") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditExistingRoleState.Arn, + "existing_ca_iam_role_arn", + "", + "specify existing cross account iam role arn to use") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditExistingRoleState.ExternalId, + "existing_ca_iam_role_external_id", + "", + "specify existing cross account iam role external_id to use") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.ExistingFirehoseIamRoleArn, + "existing_firehose_iam_role_arn", + "", + "specify existing firehose iam role arn to use") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.FilterPattern, + "custom_filter_pattern", + "", + "specify a custom cloudwatch log filter pattern") + generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( + &GenerateAwsEksAuditCommandState.FirehoseEncryptionEnabled, + "enable_firehose_encryption", + true, + "enable firehose encryption") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.FirehoseEncryptionKeyArn, + "firehose_encryption_key_arn", + "", + "specify the kms key arn to be used with the Firehose") + generateAwsEksAuditTfCommand.PersistentFlags().IntVar( + &GenerateAwsEksAuditCommandState.KmsKeyDeletionDays, + "kms_key_deletion_days", + 0, + "specify the kms waiting period before deletion, in number of days") + generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( + &GenerateAwsEksAuditCommandState.KmsKeyRotation, + "enable_kms_key_rotation", + true, + "enable automatic kms key rotation") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.Prefix, + "prefix", + "", + "specify the prefix that will be used at the beginning of every generated resource") + generateAwsEksAuditTfCommand.PersistentFlags().StringToStringVar( + &GenerateAwsEksAuditCommandState.RegionClusterMap, + "region_clusters", + map[string]string{}, + "configure eks clusters per aws region. To configure multiple regions pass the flag"+ + " multiple times. Example format: --region_clusters =\"cluster,list\"") + generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( + &GenerateAwsEksAuditCommandState.SnsTopicEncryptionEnabled, + "enable_sns_topic_encryption", + true, + "enable encryption on the sns topic") + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandState.SnsTopicEncryptionKeyArn, + "sns_topic_encryption_key_arn", + "", + "specify the kms key arn to be used with the sns topic") + generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( + &GenerateAwsEksAuditCommandExtraState.TerraformApply, + "apply", + false, + "run terraform apply without executing plan or prompting", + ) + generateAwsEksAuditTfCommand.PersistentFlags().StringVar( + &GenerateAwsEksAuditCommandExtraState.Output, + "output", + "", + "location to write generated content", + ) +} + +// Validate the response is of type int +func validateResponseTypeInt(val interface{}) error { + switch value := val.(type) { + case string: + if _, err := strconv.Atoi(value); err != nil { + // if the value passed is not of type int + return errors.New("value must be a number") + } + default: + // if the value passed is not a string + return errors.New("value must be a string") + } + return nil +} + +func promptAwsEksAuditBucketQuestions(config *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs) error { + // Only ask these questions if configure bucket is true + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionUseExistingBucket, Default: config.UseExistinglBucket}, + Response: &config.UseExistinglBucket, + }, + { + Prompt: &survey.Input{Message: QuestionExistingBucketArn, Default: config.ExistinglBucketArn}, + Checks: []*bool{&config.UseExistinglBucket}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &config.ExistinglBucketArn, + }, + }); err != nil { + return err + } + + // Only ask the next questions if the user did not indicate they wanted to use and existing bucket + if config.UseExistinglBucket { + return nil + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionEksAuditBucketVersioning, Default: config.BucketVersioning}, + Response: &config.BucketVersioning, + }, + { + Prompt: &survey.Confirm{Message: QuestionEksAuditMfaDeleteS3Bucket, Default: config.BucketEnableMfaDelete}, + Response: &config.BucketEnableMfaDelete, + }, + { + Prompt: &survey.Confirm{Message: QuestionEksAuditBucketEncryption, + Default: config.BucketEnableEncryption}, + Response: &config.BucketEnableEncryption, + }, + }); err != nil { + return err + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionEksAuditBucketExistingKey}, + Checks: []*bool{&config.BucketEnableEncryption}, + Opts: []survey.AskOpt{}, + Response: &config.ExistingBucketKmsKey, + }); err != nil { + return err + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionEksAuditBucketSseAlgorithm}, + Checks: []*bool{&config.BucketEnableEncryption}, + Opts: []survey.AskOpt{}, + Response: &config.BucketSseAlgorithm, + }); err != nil { + return err + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionEksAuditBucketKeyArn}, + Checks: []*bool{&config.BucketEnableEncryption, &config.ExistingBucketKmsKey}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &config.BucketSseKeyArn, + }); err != nil { + return err + } + + newKmsKey := config.BucketEnableEncryption && !config.ExistingBucketKmsKey + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionEksAuditKmsKeyRotation}, + Checks: []*bool{&config.BucketEnableEncryption, &newKmsKey}, + Required: true, + Opts: []survey.AskOpt{}, + Response: &config.KmsKeyRotation, + }, + { + Prompt: &survey.Input{ + Message: QuestionEksAuditKmsKeyDeletionDays, + Default: strconv.Itoa(config.KmsKeyDeletionDays), + }, + Checks: []*bool{&config.BucketEnableEncryption, &newKmsKey}, + Opts: []survey.AskOpt{survey.WithValidator(validateResponseTypeInt)}, + Response: &config.KmsKeyDeletionDays, + }, + { + Prompt: &survey.Input{ + Message: QuestionEksAuditBucketLifecycle, + Default: strconv.Itoa(config.BucketLifecycleExpirationDays), + }, + Opts: []survey.AskOpt{survey.WithValidator(validateResponseTypeInt)}, + Response: &config.BucketLifecycleExpirationDays, + }, + }); err != nil { + return err + } + + return nil +} + +func promptAwsEksAuditExistingCrossAccountIamQuestions(input *aws_eks_audit. + GenerateAwsEksAuditTfConfigurationArgs) error { + // ensure struct is initialized + if input.ExistingCrossAccountIamRole == nil { + input.ExistingCrossAccountIamRole = &aws_eks_audit.ExistingCrossAccountIamRoleDetails{} + } + + err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{ + Message: QuestionEksAuditExistingCaIamArn, + Default: input.ExistingCrossAccountIamRole.Arn, + }, + Response: &input.ExistingCrossAccountIamRole.Arn, + Opts: []survey.AskOpt{survey.WithValidator(survey.Required), survey.WithValidator(validateAwsArnFormat)}, + }, + { + Prompt: &survey.Input{ + Message: QuestionEksAuditExistingCaIamExtID, + Default: input.ExistingCrossAccountIamRole.ExternalId, + }, + Response: &input.ExistingCrossAccountIamRole.ExternalId, + Opts: []survey.AskOpt{survey.WithValidator(survey.Required)}, + }}) + return err +} + +func promptAwsEksAuditFirehoseQuestions(input *aws_eks_audit. + GenerateAwsEksAuditTfConfigurationArgs) error { + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{ + Message: QuestionEksAuditExistingFhIamRole, + Default: input.ExistingFirehoseIam, + }, + Opts: []survey.AskOpt{}, + Response: &input.ExistingFirehoseIam, + Required: false, + }); err != nil { + return err + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{ + Message: QuestionEksAuditExistingFhIamArn, + Default: input.ExistingFirehoseIamRoleArn, + }, + Checks: []*bool{&input.ExistingFirehoseIam}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &input.ExistingFirehoseIamRoleArn, + Required: true, + }, + { + Prompt: &survey.Confirm{ + Message: QuestionEksAuditFhEncryption, + Default: input.FirehoseEncryptionEnabled, + }, + Opts: []survey.AskOpt{}, + Response: &input.FirehoseEncryptionEnabled, + Required: true, + }, + }); err != nil { + return err + } + + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionEksAuditFhEncryptionKeyArn, + Default: input.FirehoseEncryptionKeyArn, + }, + Checks: []*bool{&input.FirehoseEncryptionEnabled}, + Opts: []survey.AskOpt{}, + Response: &input.FirehoseEncryptionKeyArn, + }) + return err +} + +func promptAwsEksAuditExistingCloudwatchIamQuestions(input *aws_eks_audit. + GenerateAwsEksAuditTfConfigurationArgs) error { + + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionEksAuditExistingCwIamArn, + Default: input.ExistingCloudWatchIamRoleArn, + }, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, + Response: &input.ExistingCloudWatchIamRoleArn, + Required: true, + }) + return err +} + +func promptAwsEksAuditSnsQuestions(input *aws_eks_audit. + GenerateAwsEksAuditTfConfigurationArgs) error { + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{ + Message: QuestionEksAuditSnsEncryption, + Default: input.SnsTopicEncryptionEnabled, + }, + Opts: []survey.AskOpt{}, + Response: &input.SnsTopicEncryptionEnabled, + Required: true, + }); err != nil { + return err + } + + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionEksAuditSnsEncryptionKeyArn, + Default: input.SnsTopicEncryptionKeyArn, + }, + Checks: []*bool{&input.SnsTopicEncryptionEnabled}, + Opts: []survey.AskOpt{}, + Response: &input.SnsTopicEncryptionKeyArn, + }) + return err +} + +func promptAwsEksAuditCustomIntegrationName(input *aws_eks_audit. + GenerateAwsEksAuditTfConfigurationArgs) error { + + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{ + Message: QuestionEksAuditCustomIntegrationName, + Default: input.EksAuditIntegrationName, + }, + Opts: []survey.AskOpt{}, + Response: &input.EksAuditIntegrationName, + }) + return err +} + +func promptAwsEksAuditAdditionalClusterRegionQuestions( + config *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs, + extraState *AwsEksAuditGenerateCommandExtraState, +) error { + // For each region, collect which clusters to integrate with + askAgain := false + if cli.InteractiveMode() { + askAgain = true + } + + if config.ParsedRegionClusterMap == nil { + config.ParsedRegionClusterMap = make(map[string][]string) + } + + // If there are existing region clusters configured (i.e., from the CLI) display them and ask if they want to add more + if len(config.ParsedRegionClusterMap) > 0 { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{ + Message: fmt.Sprintf( + QuestionEksAuditRegionClusterCurrent, + config.ParsedRegionClusterMap, + ), + }, + Response: &askAgain}); err != nil { + return err + } + } + + // If we already have more than 1 region, don't bother asking the user if it's + // multi region and instead just set MultiRegion to true + if len(config.ParsedRegionClusterMap) > 1 { + extraState.MultiRegion = true + } + + // If only 1 region has been configured and the user wishes to add more clusters, + // ask if they want this be to multi region + if len(config.ParsedRegionClusterMap) <= 1 && askAgain && !extraState.MultiRegion { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{ + Message: QuestionEksAuditMultiRegion, + Default: extraState.MultiRegion, + }, + Checks: []*bool{&askAgain}, + Opts: []survey.AskOpt{}, + Required: true, + Response: &extraState.MultiRegion, + }); err != nil { + return err + } + } + + // For each region to add, collect the list of clusters to integrate with + for askAgain { + var awsEksAuditRegion string + var awsEksAuditRegionClusters string + regionClustersQuestions := []SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionEksAuditRegion}, + Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, + Required: true, + Response: &awsEksAuditRegion, + }, + { + Prompt: &survey.Input{Message: QuestionEksAuditRegionClusters}, + Opts: []survey.AskOpt{}, + Required: true, + Response: &awsEksAuditRegionClusters, + }, + } + + if err := SurveyMultipleQuestionWithValidation(regionClustersQuestions); err != nil { + return err + } + + // append region clusters in case the user has input a region more than once + config.ParsedRegionClusterMap[awsEksAuditRegion] = append( + config.ParsedRegionClusterMap[awsEksAuditRegion], strings.Split(awsEksAuditRegionClusters, ",")...) + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionEksAuditAdditionalRegion}, + Checks: []*bool{&extraState.MultiRegion}, + Response: &askAgain}); err != nil { + return err + } + + if !extraState.MultiRegion { + askAgain = false + } + } + + return nil +} + +func promptCustomizeEksAuditOutputLocation(extraState *AwsEksAuditGenerateCommandExtraState) error { + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionEksAuditCustomizeOutputLocation, + Default: extraState.Output}, + Response: &extraState.Output, + Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, + Required: true, + }) + + return err +} + +func askAdvancedEksAuditOptions(config *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs, + extraState *AwsEksAuditGenerateCommandExtraState) error { + answer := "" + + // Prompt for options + for answer != AwsAdvancedOptDone { + // Construction of this slice is a bit strange at first look, but the reason for that is because we have + // to do string validation to know which option was selected due to how survey works; and doing it by index + // (also supported) is difficult when the options are dynamic (which they are) + // + // Only ask about more accounts if consolidated cloudtrail is set up (matching scenario's doc) + var options []string + + options = append(options, + EksAuditConfigureBucket, + EksAuditExistingCaIamRole, + EksAuditConfigureFh, + EksAuditExistingCwIamRole, + EksAuditConfigureSns, + EksAuditIntegrationNameOpt, + EksAuditAdvancedOptLocation, + EksAuditAdvancedOptDone) + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: "Which options would you like to configure?", + Options: options, + }, + Response: &answer, + }); err != nil { + return err + } + + // Based on response, prompt for actions + switch answer { + case EksAuditConfigureBucket: + if err := promptAwsEksAuditBucketQuestions(config); err != nil { + return err + } + case EksAuditExistingCaIamRole: + if err := promptAwsEksAuditExistingCrossAccountIamQuestions(config); err != nil { + return err + } + case EksAuditConfigureFh: + if err := promptAwsEksAuditFirehoseQuestions(config); err != nil { + return err + } + case EksAuditExistingCwIamRole: + if err := promptAwsEksAuditExistingCloudwatchIamQuestions(config); err != nil { + return err + } + case EksAuditConfigureSns: + if err := promptAwsEksAuditSnsQuestions(config); err != nil { + return err + } + case EksAuditIntegrationNameOpt: + if err := promptAwsEksAuditCustomIntegrationName(config); err != nil { + return err + } + case EksAuditAdvancedOptLocation: + if err := promptCustomizeEksAuditOutputLocation(extraState); err != nil { + return err + } + } + + // Re-prompt if not done + innerAskAgain := true + if answer == EksAuditAdvancedOptDone { + innerAskAgain = false + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Checks: []*bool{&innerAskAgain}, + Prompt: &survey.Confirm{Message: QuestionEksAuditAnotherAdvancedOpt, Default: false}, + Response: &innerAskAgain, + }); err != nil { + return err + } + + if !innerAskAgain { + answer = AwsAdvancedOptDone + } + } + + return nil +} + +func eksAuditConfigIsEmpty(g *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs) bool { + return g.AwsProfile == "" && + len(g.ParsedRegionClusterMap) == 0 && + g.ExistingCrossAccountIamRole == nil && + g.LaceworkProfile == "" +} + +func writeAwsEksAuditGenerationArgsCache(a *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs) { + if !eksAuditConfigIsEmpty(a) { + // If ExistingIamRole is partially set, don't write this to cache; the values won't work when loaded + if a.ExistingCrossAccountIamRole.IsPartial() { + a.ExistingCrossAccountIamRole = nil + } + cli.WriteAssetToCache(CachedAssetAwsEksAuditIacParams, time.Now().Add(time.Hour*1), a) + } +} + +// entry point for launching a survey to build out the required generation parameters +func promptAwsEksAuditGenerate( + config *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs, + existingIam *aws_eks_audit.ExistingCrossAccountIamRoleDetails, + extraState *AwsEksAuditGenerateCommandExtraState, +) error { + + // Cache for later use if generation is abandoned and in interactive mode + if cli.InteractiveMode() { + defer writeAwsEksAuditGenerationArgsCache(config) + defer extraState.writeCache() + } + + // Set ExistingCrossAccountIamRole details, if provided as cli flags; otherwise don't initialize + if existingIam.Arn != "" || + existingIam.ExternalId != "" { + config.ExistingCrossAccountIamRole = existingIam + } + + // These are the core questions that should be asked. + if err := promptAwsEksAuditAdditionalClusterRegionQuestions(config, extraState); err != nil { + return err + } + + // Find out if the customer wants to specify more advanced features + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionEksAuditConfigureAdvanced, + Default: extraState.AskAdvanced}, + Response: &extraState.AskAdvanced, + }); err != nil { + return err + } + + // Keep prompting for advanced options until the say done + if extraState.AskAdvanced { + if err := askAdvancedEksAuditOptions(config, extraState); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_azure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_azure.go new file mode 100644 index 000000000..34724f39d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_azure.go @@ -0,0 +1,895 @@ +package cmd + +import ( + "strconv" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/imdario/mergo" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/lwgenerate/azure" + "github.com/pkg/errors" +) + +var ( + // Define question text here so they can be reused in testing + QuestionAzureEnableConfig = "Enable Azure configuration integration?" + QuestionAzureConfigName = "Specify custom configuration integration name: (optional)" + QuestionEnableActivityLog = "Enable Azure Activity Log Integration?" + QuestionActivityLogName = "Specify custom Activity Log integration name: (optional)" + QuestionEnableEntraIdActivityLog = "Enable Azure Entra ID Activity Log Integration?" + QuestionEntraIdActivityLogName = "Specify custom EntraID Activity Log integration name: (optional)" + QuestionAddAzureSubscriptionID = "Set Azure Subscription ID?" + QuestionAzureSubscriptionID = "Specify the Azure Subscription ID to be used to provision " + + "Lacework resources: (optional)" + + QuestionAzureAnotherAdvancedOpt = "Configure another advanced integration option" + QuestionAzureConfigAdvanced = "Configure advanced integration options?" + QuestionAzureCustomizeOutputLocation = "Provide the location for the output to be written:" + + // EntraID Activity Log + QuestionEventHubLocation = "Specify Azure region where the event hub for logging will reside" + QuestionEventHubPartitionCount = "Specify the number of partitions in the event hub for logging" + + // Active Directory + QuestionEnableAdIntegration = "Create Active Directory Integration?" + QuestionADApplicationPass = "Specify the password of an existing Active Directory application" + QuestionADApplicationId = "Specify the ID of an existing Active Directory application" + QuestionADServicePrincpleId = "Specify the Service Principle ID of an existing Active Directory application" + + // Storage Account + QuestionUseExistingStorageAccount = "Use an existing Storage Account?" + QuestionAzureRegion = "Specify the Azure region to be used by Storage Account logging" + QuestionStorageAccountName = "Specify existing Storage Account name" + QuestionStorageAccountResourceGroup = "Specify existing Storage Account Resource Group" + + QuestionStorageLocation = "Specify Azure region where Storage Account for logging resides " + + // Subscriptions + QuestionEnableAllSubscriptions = "Enable all subscriptions?" + QuestionSubscriptionIds = "Specify list of subscription ids to enable logging" + + // Management Group + QuestionEnableManagementGroup = "Enable Management Group level Integration?" + QuestionManagementGroupId = "Specify Management Group ID" + + // Select options + AzureAdvancedOptDone = "Done" + AdvancedAdIntegration = "Configure Lacework integration with an existing Active Directory (optional)" + AzureExistingStorageAcount = "Configure Storage Account (optional)" + AzureSubscriptions = "Configure Subscriptions (optional)" + AzureManagmentGroup = "Configure Management Group (optional)" + AzureStorageGroup = "Configure Storage Group (optional)" + AzureUserIntegrationNames = "Customize integration name(s)" + AzureAdvancedOptLocation = "Customize output location (optional)" + AzureRegionStorage = "Customize Azure region for Storage Account (optional)" + AzureEntraIdAdvancedOpt = "Configure Entra ID activity log integration advanced options" + + GenerateAzureCommandState = &azure.GenerateAzureTfConfigurationArgs{} + GenerateAzureCommandExtraState = &AzureGenerateCommandExtraState{} + CachedAzureAssetIacParams = "iac-azure-generate-params" + CachedAzureAssetExtraState = "iac-azure-extra-state" + + // List of valid Azure Storage locations + validAzureLocations = map[string]bool{ + "East US": true, + "East US 2": true, + "South Central US": true, + "West US 2": true, + "West US 3": true, + "Australia East": true, + "Southeast Asia": true, + "North Europe": true, + "Sweden Central": true, + "UK South": true, + "West Europe": true, + "Central US": true, + "North Central US": true, + "West US": true, + "South Africa North": true, + "Central India": true, + "East Asia": true, + "Japan East": true, + "Jio India West": true, + "Korea Central": true, + "Canada Central": true, + "France Central": true, + "Germany West Central": true, + "Norway East": true, + "Switzerland North": true, + "UAE North": true, + "Brazil South": true, + "Central US (Stage)": true, + "East US (Stage)": true, + "East US 2 (Stage)": true, + "North Central US (Stage)": true, + "South Central US (Stage)": true, + "West US (Stage)": true, + "West US 2 (Stage)": true, + "Asia": true, + "Asia Pacific": true, + "Australia": true, + "Brazil": true, + "Canada": true, + "Europe": true, + "France": true, + "Germany": true, + "Global": true, + "India": true, + "Japan": true, + "Korea": true, + "Norway": true, + "South Africa": true, + "Switzerland": true, + "United Arab Emirates": true, + "United Kingdom": true, + "United States": true, + "United States EUAP": true, + "East Asia (Stage)": true, + "Southeast Asia (Stage)": true, + "Central US EUAP": true, + "East US 2 EUAP": true, + "West Central US": true, + "South Africa West": true, + "Australia Central": true, + "Australia Central 2": true, + "Australia Southeast": true, + "Japan West": true, + "Jio India Central": true, + "Korea South": true, + "South India": true, + "West India": true, + "Canada East": true, + "France South": true, + "Germany North": true, + "Norway West": true, + "Switzerland West": true, + "UK West": true, + "UAE Central": true, + "Brazil Southeast": true, + } + + // Azure command used to generate TF code for azure + generateAzureTfCommand = &cobra.Command{ + Use: "azure", + Aliases: []string{"az"}, + Short: "Generate and/or execute Terraform code for Azure integration", + Long: `Use this command to generate Terraform code for deploying Lacework into new Azure environment. + +By default, this command will function interactively, prompting for the required information to setup +the new cloud account. In interactive mode, this command will: + +* Prompt for the required information to setup the integration +* Generate new Terraform code using the inputs +* Optionally, run the generated Terraform code: + * If Terraform is already installed, the version will be confirmed suitable for use + * If Terraform is not installed, or the version installed is not suitable, a new version will be + installed into a temporary location + * Once Terraform is detected or installed, Terraform plan will be executed + * The command will prompt with the outcome of the plan and allow to view more details or continue + with Terraform apply + * If confirmed, Terraform apply will be run, completing the setup of the cloud account +`, + RunE: func(cmd *cobra.Command, args []string) error { + // Generate TF Code + cli.StartProgress("Generating Azure Terraform Code...") + + if cli.Profile != "default" { + GenerateAzureCommandState.LaceworkProfile = cli.Profile + } + + // Setup modifiers for NewTerraform constructor + mods := []azure.AzureTerraformModifier{ + azure.WithLaceworkProfile(GenerateAzureCommandState.LaceworkProfile), + azure.WithSubscriptionID(GenerateAzureCommandState.SubscriptionID), + azure.WithAllSubscriptions(GenerateAzureCommandState.AllSubscriptions), + azure.WithManagementGroup(GenerateAzureCommandState.ManagementGroup), + azure.WithExistingStorageAccount(GenerateAzureCommandState.ExistingStorageAccount), + azure.WithStorageAccountName(GenerateAzureCommandState.StorageAccountName), + azure.WithStorageLocation(GenerateAzureCommandState.StorageLocation), + azure.WithActivityLogIntegrationName(GenerateAzureCommandState.ActivityLogIntegrationName), + azure.WithConfigIntegrationName(GenerateAzureCommandState.ConfigIntegrationName), + azure.WithEntraIdActivityLogIntegrationName(GenerateAzureCommandState.EntraIdIntegrationName), + azure.WithEventHubLocation(GenerateAzureCommandState.EventHubLocation), + azure.WithEventHubPartitionCount(GenerateAzureCommandState.EventHubPartitionCount), + } + + // Check if AD Creation is required, need to set values for current integration + if !GenerateAzureCommandState.CreateAdIntegration { + mods = append(mods, azure.WithAdApplicationId(GenerateAzureCommandState.AdApplicationId)) + mods = append(mods, azure.WithAdApplicationPassword(GenerateAzureCommandState.AdApplicationPassword)) + mods = append(mods, azure.WithAdServicePrincipalId(GenerateAzureCommandState.AdServicePrincipalId)) + } + + // Check subscriptions + if !GenerateAzureCommandState.AllSubscriptions { + if len(GenerateAzureCommandState.SubscriptionIds) > 0 { + mods = append(mods, azure.WithSubscriptionIds(GenerateAzureCommandState.SubscriptionIds)) + } + } + + // Check management groups + if GenerateAzureCommandState.ManagementGroup { + mods = append(mods, azure.WithManagementGroupId(GenerateAzureCommandState.ManagementGroupId)) + } + + // Check storage account + if GenerateAzureCommandState.ExistingStorageAccount { + mods = append(mods, + azure.WithStorageAccountResourceGroup(GenerateAzureCommandState.StorageAccountResourceGroup)) + } + + // Create new struct + data := azure.NewTerraform( + GenerateAzureCommandState.Config, + GenerateAzureCommandState.ActivityLog, + GenerateAzureCommandState.EntraIdActivityLog, + GenerateAzureCommandState.CreateAdIntegration, + mods...) + + // Generate HCL for azure deployment + hcl, err := data.Generate() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "failed to generate terraform code") + } + + // Write-out generated code to location specified + dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "azure") + if err != nil { + return err + } + + // Prompt to execute, if the command line flag has not been set + if !GenerateAzureCommandExtraState.TerraformApply { + err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Default: GenerateAzureCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, + Response: &GenerateAzureCommandExtraState.TerraformApply, + }) + + if err != nil { + return errors.Wrap(err, "failed to prompt for terraform execution") + } + } + + locationDir, _ := determineOutputDirPath(dirname, "azure") + if GenerateAzureCommandExtraState.TerraformApply { + // Execution pre-run check + err := executionPreRunChecks(dirname, locationDir, "azure") + if err != nil { + return err + } + } + + // Output where code was generated + if !GenerateAzureCommandExtraState.TerraformApply { + cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) + } + + return nil + }, + PreRunE: func(cmd *cobra.Command, _ []string) error { + + // Validate output location is OK if supplied + dirname, err := cmd.Flags().GetString("output") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateOutputLocation(dirname); err != nil { + return err + } + + // Validate Storage Location + storageLocation, err := cmd.Flags().GetString("location") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateAzureLocation(storageLocation); storageLocation != "" && err != nil { + return err + } + + // Load any cached inputs if interactive + if cli.InteractiveMode() { + cachedOptions := &azure.GenerateAzureTfConfigurationArgs{} + iacParamsExpired := cli.ReadCachedAsset(CachedAzureAssetIacParams, &cachedOptions) + if iacParamsExpired { + cli.Log.Debug("loaded previously set values for Azure iac generation") + } + + extraState := &AzureGenerateCommandExtraState{} + extraStateParamsExpired := cli.ReadCachedAsset(CachedAzureAssetExtraState, &extraState) + if extraStateParamsExpired { + cli.Log.Debug("loaded previously set values for Azure iac generation (extra state)") + } + + // Determine if previously cached options exists; prompt user if they'd like to continue + answer := false + if !iacParamsExpired || !extraStateParamsExpired { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, + Response: &answer, + }); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs + // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get + // re-prompted. + if answer { + // Merge cached inputs to current options (current options win) + if err := mergo.Merge(GenerateAzureCommandState, cachedOptions); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + if err := mergo.Merge(GenerateAzureCommandExtraState, extraState); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + } + + // Collect and/or confirm parameters + err = promptAzureGenerate(GenerateAzureCommandState, GenerateAzureCommandExtraState) + if err != nil { + return errors.Wrap(err, "collecting/confirming parameters") + } + + return nil + }, + } +) + +type AzureGenerateCommandExtraState struct { + AskAdvanced bool + Output string + TerraformApply bool +} + +func (a *AzureGenerateCommandExtraState) isEmpty() bool { + return a.Output == "" && !a.TerraformApply +} + +// Flush current state of the struct to disk, provided it's not empty +func (a *AzureGenerateCommandExtraState) writeCache() { + if !a.isEmpty() { + cli.WriteAssetToCache(CachedAzureAssetExtraState, time.Now().Add(time.Hour*1), a) + } +} + +func validateAzureLocation(location string) error { + if !validAzureLocations[location] { + return errors.New("invalid Azure region prvovided") + } + return nil +} + +func initGenerateAzureTfCommandFlags() { + // Azure sub-command flags + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandState.ActivityLog, + "activity_log", + false, + "enable activity log integration") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.ActivityLogIntegrationName, + "activity_log_integration_name", + "", + "specify a custom activity log integration name") + + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandState.EntraIdActivityLog, + "entra_id_activity_log", + false, + "enable Entra ID activity log integration") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.EntraIdIntegrationName, + "entra_id_activity_log_integration_name", + "", + "specify a custom Entra ID activity log integration name") + + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandState.Config, + "configuration", + false, + "enable configuration integration") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.ConfigIntegrationName, + "configuration_name", + "", + "specify a custom configuration integration name") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.SubscriptionID, + "subscription_id", + "", + "specify the Azure Subscription ID to be used to provision Lacework resources") + + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandState.CreateAdIntegration, + "ad_create", + true, + "create new active directory integration") + + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandState.ManagementGroup, + "management_group", + false, + "management group level integration") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.ManagementGroupId, + "management_group_id", + "", + "specify management group id. Required if mgmt_group provided") + + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandState.ExistingStorageAccount, + "existing_storage", + false, + "use existing storage account") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.EventHubLocation, + "event_hub_location", + "", + "specify the location where the Event Hub for logging will reside") + + generateAzureTfCommand.PersistentFlags().IntVar( + &GenerateAzureCommandState.EventHubPartitionCount, + "event_hub_partition_count", + 1, + "specify the number of partitions for the Event Hub") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.StorageAccountName, + "storage_account_name", + "", + "specify storage account name") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.StorageAccountResourceGroup, + "storage_resource_group", + "", + "specify storage resource group") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.StorageLocation, + "location", + "", + "specify azure region where storage account logging resides") + + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandState.AllSubscriptions, + "all_subscriptions", + false, + "grant read access to ALL subscriptions within Tenant (overrides `subscription ids`)") + + generateAzureTfCommand.PersistentFlags().StringSliceVar( + &GenerateAzureCommandState.SubscriptionIds, + "subscription_ids", + []string{}, + `list of subscriptions to grant read access; format is id1,id2,id3`) + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.AdApplicationPassword, + "ad_pass", + "", + "existing active directory application password") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.AdApplicationId, + "ad_id", + "", + "existing active directory application id") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandState.AdServicePrincipalId, + "ad_pid", + "", + "existing active directory application service principle id") + + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandExtraState.TerraformApply, + "terraform-apply", + false, + "run terraform apply for the generated hcl") + + _ = generateAzureTfCommand.PersistentFlags().MarkHidden("terraform-apply") + + generateAzureTfCommand.PersistentFlags().BoolVar( + &GenerateAzureCommandExtraState.TerraformApply, + "apply", + false, + "run terraform apply for the generated hcl") + + generateAzureTfCommand.PersistentFlags().StringVar( + &GenerateAzureCommandExtraState.Output, + "output", + "", + "location to write generated content (default is ~/lacework/azure)", + ) +} + +func promptAzureIntegrationNameQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionAzureConfigName, Default: config.ConfigIntegrationName}, + Checks: []*bool{&config.Config}, + Response: &config.ConfigIntegrationName, + }, + { + Prompt: &survey.Input{Message: QuestionActivityLogName, Default: config.ActivityLogIntegrationName}, + Checks: []*bool{&config.ActivityLog}, + Response: &config.ActivityLogIntegrationName, + }, + { + Prompt: &survey.Input{Message: QuestionEntraIdActivityLogName, Default: config.EntraIdIntegrationName}, + Checks: []*bool{&config.EntraIdActivityLog}, + Response: &config.EntraIdIntegrationName, + }, + }); err != nil { + return err + } + return nil +} + +func promptAzureStorageAccountQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionUseExistingStorageAccount, Default: config.ExistingStorageAccount}, + Response: &config.ExistingStorageAccount, + }, + { + Prompt: &survey.Input{Message: QuestionStorageAccountName, Default: config.StorageAccountName}, + Required: true, + Response: &config.StorageAccountName, + }, + { + Prompt: &survey.Input{ + Message: QuestionStorageAccountResourceGroup, + Default: config.StorageAccountResourceGroup, + }, + Checks: []*bool{&config.ExistingStorageAccount}, + Required: true, + Response: &config.StorageAccountResourceGroup, + }, + }); err != nil { + return err + } + + return nil +} + +func promptAzureEntraIdActivityLogQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionEventHubLocation, Default: config.EventHubLocation}, + Required: true, + Response: &config.EventHubLocation, + }, + }); err != nil { + return err + } + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionEventHubPartitionCount, + Default: strconv.Itoa(config.EventHubPartitionCount)}, + Response: &config.EventHubPartitionCount, + }, + }); err != nil { + return err + } + + return nil +} + +func promptAzureSubscriptionQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionEnableAllSubscriptions, Default: config.AllSubscriptions}, + Response: &config.AllSubscriptions, + }, + }); err != nil { + return err + } + var idList string + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionSubscriptionIds, Default: strings.Join(config.SubscriptionIds, ",")}, + Checks: []*bool{allSubscriptionsDisabled(config)}, + Required: true, + Response: &idList, + }, + }); err != nil { + return err + } + config.SubscriptionIds = strings.Split(strings.ReplaceAll(idList, " ", ""), ",") + + return nil +} + +func promptAzureManagementGroupQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionEnableManagementGroup, Default: config.ManagementGroup}, + Response: &config.ManagementGroup, + }, + { + Prompt: &survey.Input{Message: QuestionManagementGroupId, Default: config.ManagementGroupId}, + Checks: []*bool{&config.ManagementGroup}, + Required: true, + Response: &config.ManagementGroupId, + }, + }); err != nil { + return err + } + return nil +} + +func promptAzureAdIntegrationQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionADApplicationPass, Default: config.AdApplicationPassword}, + Required: true, + Response: &config.AdApplicationPassword, + }, + { + Prompt: &survey.Input{Message: QuestionADApplicationId, Default: config.AdApplicationId}, + Required: true, + Response: &config.AdApplicationId, + }, + { + Prompt: &survey.Input{Message: QuestionADServicePrincpleId, Default: config.AdServicePrincipalId}, + Required: true, + Response: &config.AdServicePrincipalId, + }, + }); err != nil { + return err + } + return nil +} + +func promptCustomizeAzureOutputLocation(extraState *AzureGenerateCommandExtraState) error { + + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionAzureCustomizeOutputLocation, Default: extraState.Output}, + Response: &extraState.Output, + }, + }); err != nil { + return err + } + + return nil +} + +func promptCustomizeAzureStorageLoggingRegion(config *azure.GenerateAzureTfConfigurationArgs) error { + var region string + if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionStorageLocation, Default: config.StorageLocation}, + Response: ®ion, + }, + }); err != nil { + return err + } + if err := validateAzureLocation(region); err != nil { + return err + } + config.StorageLocation = region + return nil +} + +func askAzureSubscriptionID(config *azure.GenerateAzureTfConfigurationArgs) error { + var addSubscription bool + + // if subscription has been set by --subscription_id flag do not prompt + if config.SubscriptionID != "" { + return nil + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionAddAzureSubscriptionID, Default: false}, + Response: &addSubscription, + }); err != nil { + return err + } + + if addSubscription { + if err := survey.AskOne(&survey.Input{ + Message: QuestionAzureSubscriptionID, + }, &config.SubscriptionID); err != nil { + return err + } + } + + return nil +} + +func askAdvancedAzureOptions( + config *azure.GenerateAzureTfConfigurationArgs, extraState *AzureGenerateCommandExtraState, +) error { + answer := "" + + // Prompt for options + for answer != AzureAdvancedOptDone { + + // Set the initial options + options := []string{AzureUserIntegrationNames, AzureSubscriptions} + // Only ask about Active Directory information if one was requested to be created + if !config.CreateAdIntegration { + options = append(options, AdvancedAdIntegration) + } + + // Only show Region Storage options in the case of Activity Log integration + if config.ActivityLog { + options = append(options, AzureRegionStorage) + options = append(options, AzureExistingStorageAcount) + } + + // Only show management options in the case of Config integration + if config.Config { + options = append(options, AzureManagmentGroup) + } + + // Only show Entra ID options in the case of Entra ID integration + if config.EntraIdActivityLog { + options = append(options, AzureEntraIdAdvancedOpt) + } + + options = append(options, AzureAdvancedOptLocation, AzureAdvancedOptDone) + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: "Which options would you like to enable?", + Options: options, + }, + Response: &answer, + }); err != nil { + return err + } + + // Based on response, prompt for actions + switch answer { + case AzureUserIntegrationNames: + if err := promptAzureIntegrationNameQuestions(config); err != nil { + return err + } + case AzureExistingStorageAcount: + if err := promptAzureStorageAccountQuestions(config); err != nil { + return err + } + case AzureEntraIdAdvancedOpt: + if err := promptAzureEntraIdActivityLogQuestions(config); err != nil { + return err + } + case AzureSubscriptions: + if err := promptAzureSubscriptionQuestions(config); err != nil { + return err + } + case AzureManagmentGroup: + if err := promptAzureManagementGroupQuestions(config); err != nil { + return err + } + case AdvancedAdIntegration: + if err := promptAzureAdIntegrationQuestions(config); err != nil { + return err + } + case AzureRegionStorage: + if err := promptCustomizeAzureStorageLoggingRegion(config); err != nil { + return err + } + case AzureAdvancedOptLocation: + if err := promptCustomizeAzureOutputLocation(extraState); err != nil { + return err + } + return nil + } + + // Re-prompt if not done + innerAskAgain := true + if answer == AzureAdvancedOptDone { + innerAskAgain = false + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Checks: []*bool{&innerAskAgain}, + Prompt: &survey.Confirm{Message: QuestionAzureAnotherAdvancedOpt, Default: false}, + Response: &innerAskAgain, + }); err != nil { + return err + } + + if !innerAskAgain { + answer = AzureAdvancedOptDone + } + } + + return nil +} + +func azureConfigIsEmpty(config *azure.GenerateAzureTfConfigurationArgs) bool { + return !config.Config && !config.ActivityLog && config.LaceworkProfile == "" +} + +func allSubscriptionsDisabled(config *azure.GenerateAzureTfConfigurationArgs) *bool { + allSubscriptionsDisabled := !config.AllSubscriptions + return &allSubscriptionsDisabled +} + +func writeAzureGenerationArgsCache(config *azure.GenerateAzureTfConfigurationArgs) { + if !azureConfigIsEmpty(config) { + cli.WriteAssetToCache(CachedAzureAssetIacParams, time.Now().Add(time.Hour*1), config) + } +} + +// entry point for launching a survey to build out the required Azure generation parameters +func promptAzureGenerate( + config *azure.GenerateAzureTfConfigurationArgs, extraState *AzureGenerateCommandExtraState, +) error { + + // Cache for later use if generation is abandoned and in interactive mode + if cli.InteractiveMode() { + defer writeAzureGenerationArgsCache(config) + defer extraState.writeCache() + } + // These are the core questions that should be asked. + if err := SurveyMultipleQuestionWithValidation( + []SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionAzureEnableConfig, Default: config.Config}, + Response: &config.Config, + }, + { + Prompt: &survey.Confirm{Message: QuestionEnableActivityLog, Default: config.ActivityLog}, + Response: &config.ActivityLog, + }, + { + Prompt: &survey.Confirm{Message: QuestionEnableAdIntegration, Default: config.CreateAdIntegration}, + Response: &config.CreateAdIntegration, + }, + { + Prompt: &survey.Confirm{Message: QuestionEnableEntraIdActivityLog, Default: config.EntraIdActivityLog}, + Response: &config.EntraIdActivityLog, + }, + }); err != nil { + return err + } + + // Validate one of config or activity log was enabled; otherwise error out + if !config.Config && !config.ActivityLog && !config.EntraIdActivityLog { + return errors.New("must enable activity log or config") + } + + if err := askAzureSubscriptionID(config); err != nil { + return err + } + + // Find out if the customer wants to specify more advanced features + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionAzureConfigAdvanced, Default: extraState.AskAdvanced}, + Response: &extraState.AskAdvanced, + }); err != nil { + return err + } + + // Keep prompting for advanced options until the say done + if extraState.AskAdvanced { + if err := askAdvancedAzureOptions(config, extraState); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_cloud_account.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_cloud_account.go new file mode 100644 index 000000000..999ff9b23 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_cloud_account.go @@ -0,0 +1,31 @@ +package cmd + +import "github.com/spf13/cobra" + +var ( + generateCloudAccountCommand = &cobra.Command{ + Use: "cloud-account", + Aliases: []string{"cloud", "ca"}, + Short: "Generate cloud integration IaC", + Long: `Generate cloud-account IaC to deploy Lacework into a cloud environment. + +This command creates Infrastructure as Code (IaC) in the form of Terraform HCL, with the option of running +Terraform and deploying Lacework into AWS, Azure, GCP or OCI. +`, + } +) + +func init() { + generateTfCommand.AddCommand(generateCloudAccountCommand) + + // Uncomment when `cloud-account iac-generate` removed + // initGenerateAwsTfCommandFlags() + // initGenerateGcpTfCommandFlags() + // initGenerateAzureTfCommandFlags() + initGenerateOciTfCommandFlags() + + generateCloudAccountCommand.AddCommand(generateAwsTfCommand) + generateCloudAccountCommand.AddCommand(generateGcpTfCommand) + generateCloudAccountCommand.AddCommand(generateAzureTfCommand) + generateCloudAccountCommand.AddCommand(generateOciTfCommand) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_execute.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_execute.go new file mode 100644 index 000000000..594eae302 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_execute.go @@ -0,0 +1,477 @@ +package cmd + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/Masterminds/semver" + "github.com/abiosoft/colima/util/terminal" + "github.com/hashicorp/go-version" + "github.com/hashicorp/hc-install/product" + "github.com/hashicorp/hc-install/releases" + "github.com/hashicorp/terraform-exec/tfexec" + tfjson "github.com/hashicorp/terraform-json" + "github.com/pkg/errors" +) + +var ( + requiredTerraformVersion = ">= 0.15.1" + installTerraformVersion = "1.0.11" +) + +type terraformVersion struct { + Version string `json:"terraform_version"` +} + +// helper function to create new *tfexec.Terraform object and return useful error if not found +func newTf(workingDir string, execPath string) (*tfexec.Terraform, error) { + // Create new tf object + tf, err := tfexec.NewTerraform(workingDir, execPath) + if err != nil { + return nil, errors.Wrap(err, "could not locate terraform binary") + } + + return tf, nil +} + +// LocateOrInstallTerraform Determine if terraform is installed, if that version is new enough, +// and if not install a new ephemeral binary of the correct version into tmp location +// +// forceInstall: if set always install ephemeral binary +func LocateOrInstallTerraform(forceInstall bool, workingDir string) (*tfexec.Terraform, error) { + // find existing binary if not force install + execPath, _ := exec.LookPath("terraform") + if execPath != "" { + cli.Log.Debugf("existing terraform path %s", execPath) + } + + existingVersionOk := false + if !forceInstall && execPath != "" { + // Test if it's an OK version + requiredVersion := requiredTerraformVersion + constraint, _ := semver.NewConstraint(requiredVersion) + + // Extract tf version && check for unsupportedExistingVersion + out, err := exec.Command("terraform", "--version", "--json").Output() + if err != nil { + return nil, + errors.Wrap( + err, + fmt.Sprintf("failed to collect version from existing terraform install (%s)", execPath)) + } + + // If this version supports checking the version via --version --json, check if we can use it + var data terraformVersion + unsupportedVersionCheck := false + err = json.Unmarshal(out, &data) + if err != nil { + // If this version does not support checking version via --version --json, report and install new + cli.OutputHuman( + "Existing Terraform version cannot be used, version doesn't meet requirement %s, "+ + "installing short lived version\n", + requiredVersion) + unsupportedVersionCheck = true + } + cli.Log.Debugf("existing terraform version %s", data.Version) + + // Parse into new semver + if !unsupportedVersionCheck { + tfVersion, err := semver.NewVersion(data.Version) + if err != nil { + return nil, + errors.Wrap( + err, + fmt.Sprintf("version from existing terraform install is invalid (%s)", data.Version)) + } + + // Test if it matches + existingVersionOk, _ = constraint.Validate(tfVersion) + if !existingVersionOk { + cli.OutputHuman( + "Existing Terraform version cannot be used, version %s doesn't meet requirement %s, "+ + "installing short lived version\n", + data.Version, + requiredVersion) + } + cli.Log.Debug("using existing terraform install") + } + } + + if !existingVersionOk { + // If forceInstall was true or the existing version couldn't be used, install it + installer := &releases.ExactVersion{ + Product: product.Terraform, + Version: version.Must(version.NewVersion(installTerraformVersion)), + } + + cli.StartProgress("Installing Terraform") + installPath, err := installer.Install(context.Background()) + if err != nil { + return nil, errors.Wrap(err, "error installing terraform") + } + cli.StopProgress() + execPath = installPath + } + + // Return *tfexec.Terraform object + return newTf(workingDir, execPath) +} + +// used to create suitable response messages when showing actions tf will take as a result of a plan execution +func createOrDestroy(create bool, + destroy bool, + update bool, + read bool, + noop bool, + replace bool, + createBeforeDestroy bool, + destroyBeforeCreate bool, +) string { + switch { + case create: + return "created" + case destroy: + return "destroyed" + case update: + return "update" + case read: + return "read" + case noop: + return "(noop)" + case replace: + return "replaced" + case createBeforeDestroy: + return "created before destroy" + case destroyBeforeCreate: + return "destroyed before create" + default: + return "unchanged" + } +} + +type TfPlanChangesSummary struct { + plan *tfjson.Plan + create int + deleted int + update int + replace int +} + +// buildHumanReadablePlannedActions creates a summarized listing of expected changes from Terraform +func buildHumanReadablePlannedActions(workingDir string, execPath string, data []*tfjson.ResourceChange) string { + outputString := strings.Builder{} + outputString.WriteString("Resource details: \n") + + for _, c := range data { + outputString.WriteString(fmt.Sprintf(" %s.%s will be %s\n", c.Type, c.Name, + createOrDestroy( + c.Change.Actions.Create(), + c.Change.Actions.Delete(), + c.Change.Actions.Update(), + c.Change.Actions.Read(), + c.Change.Actions.NoOp(), + c.Change.Actions.Replace(), + c.Change.Actions.CreateBeforeDestroy(), + c.Change.Actions.DestroyBeforeCreate(), + ), + ), + ) + } + outputString.WriteString("\n") + outputString.WriteString(fmt.Sprintf( + "More details can be viewed by running:\n\n cd %s\n %s show tfplan.json\n", + workingDir, execPath, + )) + outputString.WriteString("\n") + return outputString.String() +} + +// DisplayTerraformPlanChanges used to display the results of a plan +// +// returns true if apply should run, false to exit +func DisplayTerraformPlanChanges(tf *tfexec.Terraform, data TfPlanChangesSummary) (bool, error) { + // Prompt for next steps + prompt := true + previewShown := false + var answer int + + // Displaying resources + for prompt { + id, err := promptForTerraformNextSteps(&previewShown, data) + if err != nil { + return false, errors.Wrap(err, "failed to run terraform") + } + + switch { + case id == 1 && !previewShown: + cli.OutputHuman(buildHumanReadablePlannedActions(tf.WorkingDir(), tf.ExecPath(), data.plan.ResourceChanges)) + default: + answer = id + prompt = false + } + + if id == 1 && !previewShown { + previewShown = true + } + } + + // Run apply + if answer == 0 { + return true, nil + } + + // Quit + return false, nil +} + +func parseTfPlanOutput(plan *tfjson.Plan) *TfPlanChangesSummary { + // Build output of changes + resourceCreate := 0 + resourceDelete := 0 + resourceUpdate := 0 + resourceReplace := 0 + + for _, c := range plan.ResourceChanges { + switch { + case c.Change.Actions.Create(): + resourceCreate++ + case c.Change.Actions.Delete(): + resourceDelete++ + case c.Change.Actions.Update(): + resourceUpdate++ + case c.Change.Actions.Replace(): + resourceReplace++ + } + } + + return &TfPlanChangesSummary{ + plan: plan, + create: resourceCreate, + deleted: resourceDelete, + update: resourceDelete, + replace: resourceReplace, + } +} + +func processTfPlanChangesSummary(tf *tfexec.Terraform) (*TfPlanChangesSummary, error) { + // Extract changes from tf plan + cli.StartProgress("Getting terraform plan details") + plan, err := tf.ShowPlanFile(context.Background(), "tfplan.json") + if err != nil { + return nil, errors.Wrap(err, "failed to inspect terraform plan") + } + cli.StopProgress() + + return parseTfPlanOutput(plan), nil +} + +func TerraformInit(tf *tfexec.Terraform) error { + cli.StartProgress("Running terraform init") + err := tf.Init(context.Background(), tfexec.Upgrade(true)) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "failed to execute terraform init") + } + + return nil +} + +// TerraformExecPlan Run terraform plan using the workingDir from *tfexec.Terraform +// +// - Run plan +// - Get plan file details (returned) +func TerraformExecPlan(tf *tfexec.Terraform) (*TfPlanChangesSummary, error) { + // Plan + cli.StartProgress("Running terraform plan") + _, err := tf.Plan(context.Background(), tfexec.Out("tfplan.json")) + cli.StopProgress() + if err != nil { + return nil, err + } + + // Gather changes from plan + return processTfPlanChangesSummary(tf) +} + +// TerraformExecApply Run terraform apply using the workingDir from *tfexec.Terraform +// +// - Run plan +// - Get plan file details (returned) +func TerraformExecApply(tf *tfexec.Terraform) error { + cli.StartProgress("Running terraform apply") + err := tf.Apply(context.Background()) + cli.StopProgress() + if err != nil { + return err + } + + return nil +} + +// Simple helper to prompt for next steps after TF plan +func promptForTerraformNextSteps(previewShown *bool, data TfPlanChangesSummary) (int, error) { + options := []string{ + "Continue with Terraform Apply", + } + + // Omit option to show details if we already have + if !*previewShown { + options = append(options, "Show details") + } + options = append(options, "Quit") + + var answer int + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: fmt.Sprintf( + "Terraform will create %d resources, delete %d resources, update %d resources, and replace %d resources.", + data.create, + data.deleted, + data.update, + data.replace), + Options: options, + }, + Response: &answer, + }) + + return answer, err +} + +// this helper function is called when terraform flow has been completely executed through apply +func provideGuidanceAfterSuccess(workingDir string, laceworkProfile string) string { + out := new(strings.Builder) + fmt.Fprintf(out, "Lacework integration was successful! Terraform code saved in %s\n\n", workingDir) + fmt.Fprintln(out, "To view integration status:") + + laceworkCmd := " lacework cloud-account list\n\n" + if laceworkProfile != "" { + laceworkCmd = fmt.Sprintf(" lacework -p %s cloud-account list\n\n", laceworkProfile) + } + fmt.Fprint(out, laceworkCmd) + + return out.String() +} + +func provideGuidanceAfterFailure(err error, workingDir string, binaryLocation string) string { + out := new(strings.Builder) + fmt.Fprintf(out, "\n\n%s\n\n", err.Error()) + fmt.Fprintln(out, strings.Repeat("-", 80)) + fmt.Fprint(out, "Terraform encountered an error (see above)\n\n") + fmt.Fprintf(out, "The Terraform code, state, and plan output have been saved in %s.\n\n", workingDir) + fmt.Fprintln(out, "Once the issues have been resolved, the integration can be continued using the following commands:") + fmt.Fprintf(out, " cd %s\n", workingDir) + fmt.Fprintf(out, " %s apply\n\n", binaryLocation) + fmt.Fprintln(out, "Should you simply want to clean up the failed deployment, use the following commands:") + fmt.Fprintf(out, " cd %s\n", workingDir) + fmt.Fprintf(out, " %s destroy\n\n", binaryLocation) + + return out.String() +} + +// this helper function is called when the entire generation/apply flow is not completed; it provides +// guidance on how to proceed from the last point of execution +func provideGuidanceAfterExit(initRun bool, planRun bool, workingDir string, binaryLocation string) string { + planNote := " and plan output" + if !planRun { + planNote = "" + } + + out := new(strings.Builder) + fmt.Fprintf(out, "Terraform code%s saved in %s\n\n", planNote, workingDir) + fmt.Fprintln(out, "The generated code can be executed at any point in the future using the following commands:") + fmt.Fprintf(out, " cd %s\n", workingDir) + + if !initRun { + fmt.Fprintf(out, " %s init\n", binaryLocation) + } + + fmt.Fprintf(out, " %s plan\n", binaryLocation) + fmt.Fprintf(out, " %s apply\n\n", binaryLocation) + return out.String() +} + +// Execute a terraform plan & execute +func TerraformPlanAndExecute(workingDir string) error { + // Ensure Terraform is installed + tf, err := LocateOrInstallTerraform(false, workingDir) + if err != nil { + return err + } + + vw := terminal.NewVerboseWriter(10) + tf.SetStdout(vw) + tf.SetStderr(vw) + + // Initialize tf project + if err := TerraformInit(tf); err != nil { + return err + } + + // Write plan + changes, err := TerraformExecPlan(tf) + if err != nil { + return err + } + + vw.Close() + + // Display changes and determine if apply should proceed + proceed, err := DisplayTerraformPlanChanges(tf, *changes) + if err != nil { + return err + } + + // If not proceed; display guidance on how to continue outside of this session + if !proceed { + cli.OutputHuman(provideGuidanceAfterExit(true, true, tf.WorkingDir(), tf.ExecPath())) + return nil + } + + vw = terminal.NewVerboseWriter(10) + tf.SetStdout(vw) + tf.SetStderr(vw) + + // Apply plan + if err := TerraformExecApply(tf); err != nil { + return errors.New(provideGuidanceAfterFailure(err, tf.WorkingDir(), tf.ExecPath())) + } + vw.Close() + + cli.OutputHuman(provideGuidanceAfterSuccess(tf.WorkingDir(), GenerateAwsCommandState.LaceworkProfile)) + return nil +} + +func TerraformExecutePreRunCheck(outputLocation string, cloud string) (bool, error) { + // If noninteractive, continue + if !cli.InteractiveMode() { + return true, nil + } + + dirname, err := determineOutputDirPath(outputLocation, cloud) + if err != nil { + return false, err + } + stateFile := filepath.FromSlash(fmt.Sprintf("%s/terraform.tfstate", dirname)) + + // If the file doesn't exist, carry on + if _, err := os.Stat(stateFile); os.IsNotExist(err) { + return true, nil + } + + // If it does exist; confirm overwrite + answer := false + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: fmt.Sprintf("Terraform state file %s already exists, continue?", stateFile)}, + Response: &answer, + }); err != nil { + return false, err + } + + return answer, nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gcp.go new file mode 100644 index 000000000..e2fecb48a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gcp.go @@ -0,0 +1,802 @@ +package cmd + +import ( + "regexp" + "strings" + "time" + + "github.com/imdario/mergo" + "github.com/spf13/cobra" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" + + "github.com/lacework/go-sdk/lwgenerate/gcp" +) + +var ( + // Define question text here to be reused in testing + QuestionGcpEnableAgentless = "Enable Agentless integration?" + QuestionGcpEnableConfiguration = "Enable Configuration integration?" + QuestionGcpEnableAuditLog = "Enable Audit Log integration?" + QuestionGcpOrganizationIntegration = "Organization integration?" + QuestionGcpOrganizationID = "Specify the GCP organization ID:" + QuestionGcpProjectID = "Specify the project ID to be used to provision Lacework resources:" + QuestionGcpServiceAccountCredsPath = "Specify service account credentials JSON path: (optional)" + + QuestionGcpConfigureAdvanced = "Configure advanced integration options?" + GcpAdvancedOptExistingServiceAccount = "Configure & use existing service account" + QuestionExistingServiceAccountName = "Specify an existing service account name:" + QuestionExistingServiceAccountPrivateKey = "Specify an existing service account private key (base64 encoded):" + + GcpAdvancedOptAgentless = "Configure additional Agentless options" + QuestionGcpProjectFilterList = "Specify a comma separated list of Google Cloud projects that " + + "you want to monitor: (optional)" + QuestionGcpRegions = "Specify a comma separated list of regions to deploy Agentless:" + + GcpAdvancedOptAuditLog = "Configure additional Audit Log options" + QuestionGcpUseExistingSink = "Use an existing sink?" + QuestionGcpExistingSinkName = "Specify the existing sink name" + + GcpAdvancedOptIntegrationName = "Customize integration name(s)" + QuestionGcpConfigurationIntegrationName = "Specify a custom configuration integration name: (optional)" + QuestionGcpAuditLogIntegrationName = "Specify a custom Audit Log integration name: (optional)" + + QuestionGcpAnotherAdvancedOpt = "Configure another advanced integration option" + GcpAdvancedOptLocation = "Customize output location" + GcpAdvancedOptProjects = "Configure multiple projects" + QuestionGcpCustomizeOutputLocation = "Provide the location for the output to be written:" + QuestionGcpCustomizeProjects = "Provide comma separated list of project ID" + QuestionGcpCustomFilter = "Specify a custom Audit Log filter which supersedes all other filter options" + GcpAdvancedOptDone = "Done" + + // GcpRegionRegex regex used for validating region input + GcpRegionRegex = `(asia|australia|europe|northamerica|southamerica|us)-(central|(north|south)?(east|west)?)\d` + + GenerateGcpCommandState = &gcp.GenerateGcpTfConfigurationArgs{} + GenerateGcpExistingServiceAccountDetails = &gcp.ExistingServiceAccountDetails{} + GenerateGcpCommandExtraState = &GcpGenerateCommandExtraState{} + CachedGcpAssetIacParams = "iac-gcp-generate-params" + CachedAssetGcpExtraState = "iac-gcp-extra-state" + + InvalidProjectIDMessage = "invalid GCP project ID. " + + "It must be 6 to 30 lowercase ASCII letters, digits, or hyphens. " + + "It must start with a letter. Trailing hyphens are prohibited. Example: tokyo-rain-123" + + // gcp command is used to generate TF code for gcp + generateGcpTfCommand = &cobra.Command{ + Use: "gcp", + Short: "Generate and/or execute Terraform code for GCP integration", + Long: `Use this command to generate Terraform code for deploying Lacework into an GCP environment. + +By default, this command interactively prompts for the required information to setup the new cloud account. +In interactive mode, this command will: + +* Prompt for the required information to setup the integration +* Generate new Terraform code using the inputs +* Optionally, run the generated Terraform code: + * If Terraform is already installed, the version is verified as compatible for use + * If Terraform is not installed, or the version installed is not compatible, a new version will be + installed into a temporary location + * Once Terraform is detected or installed, Terraform plan will be executed + * The command will prompt with the outcome of the plan and allow to view more details or continue with + Terraform apply + * If confirmed, Terraform apply will be run, completing the setup of the cloud account + +This command can also be run in noninteractive mode. +See help output for more details on the parameter value(s) required for Terraform code generation. +`, + RunE: func(cmd *cobra.Command, args []string) error { + // Generate TF Code + cli.StartProgress("Generating Terraform Code...") + + // Explicitly set Lacework profile if it was passed in main args + if cli.Profile != "default" { + GenerateGcpCommandState.LaceworkProfile = cli.Profile + } + + // Setup modifiers for NewTerraform constructor + mods := []gcp.GcpTerraformModifier{ + gcp.WithGcpServiceAccountCredentials(GenerateGcpCommandState.ServiceAccountCredentials), + gcp.WithOrganizationId(GenerateGcpCommandState.GcpOrganizationId), + gcp.WithProjectId(GenerateGcpCommandState.GcpProjectId), + gcp.WithExistingServiceAccount(GenerateGcpCommandState.ExistingServiceAccount), + gcp.WithConfigurationIntegrationName(GenerateGcpCommandState.ConfigurationIntegrationName), + gcp.WithAuditLogLabels(GenerateGcpCommandState.AuditLogLabels), + gcp.WithPubSubSubscriptionLabels(GenerateGcpCommandState.PubSubSubscriptionLabels), + gcp.WithPubSubTopicLabels(GenerateGcpCommandState.PubSubTopicLabels), + gcp.WithExistingLogSinkName(GenerateGcpCommandState.ExistingLogSinkName), + gcp.WithAuditLogIntegrationName(GenerateGcpCommandState.AuditLogIntegrationName), + gcp.WithLaceworkProfile(GenerateGcpCommandState.LaceworkProfile), + gcp.WithFoldersToInclude(GenerateGcpCommandState.FoldersToInclude), + gcp.WithFoldersToExclude(GenerateGcpCommandState.FoldersToExclude), + gcp.WithCustomFilter(GenerateGcpCommandState.CustomFilter), + gcp.WithGoogleWorkspaceFilter(GenerateGcpCommandState.GoogleWorkspaceFilter), + gcp.WithK8sFilter(GenerateGcpCommandState.K8sFilter), + gcp.WithPrefix(GenerateGcpCommandState.Prefix), + gcp.WithWaitTime(GenerateGcpCommandState.WaitTime), + gcp.WithMultipleProject(GenerateGcpCommandState.Projects), + gcp.WithProjectFilterList(GenerateGcpCommandState.ProjectFilterList), + gcp.WithRegions(GenerateGcpCommandState.Regions), + gcp.WithUsePubSubAudit(true), // always set to true, storage based integration deprecated + } + + if GenerateGcpCommandState.OrganizationIntegration { + mods = append(mods, gcp.WithOrganizationIntegration(GenerateGcpCommandState.OrganizationIntegration)) + } + + if len(GenerateGcpCommandState.FoldersToExclude) > 0 { + mods = append(mods, gcp.WithIncludeRootProjects(GenerateGcpCommandState.IncludeRootProjects)) + } + + // Create new struct + data := gcp.NewTerraform( + GenerateGcpCommandState.Agentless, + GenerateGcpCommandState.Configuration, + GenerateGcpCommandState.AuditLog, + GenerateGcpCommandState.UsePubSubAudit, + mods...) + + // Generate + hcl, err := data.Generate() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "failed to generate terraform code") + } + + // Write-out generated code to location specified + dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "gcp") + if err != nil { + return err + } + + // Prompt to execute + err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Default: GenerateGcpCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, + Response: &GenerateGcpCommandExtraState.TerraformApply, + }) + + if err != nil { + return errors.Wrap(err, "failed to prompt for terraform execution") + } + + locationDir, _ := determineOutputDirPath(dirname, "gcp") + if GenerateGcpCommandExtraState.TerraformApply { + // Execution pre-run check + err := executionPreRunChecks(dirname, locationDir, "gcp") + if err != nil { + return err + } + } + + // Output where code was generated + if !GenerateGcpCommandExtraState.TerraformApply { + cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) + } + + return nil + }, + PreRunE: func(cmd *cobra.Command, _ []string) error { + // Validate output location is OK if supplied + dirname, err := cmd.Flags().GetString("output") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateOutputLocation(dirname); err != nil { + return err + } + + // Validate gcp sa credentials file, if passed + gcpSaCredentials, err := cmd.Flags().GetString("service_account_credentials") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + + if gcpSaCredentials != "" { + if err := gcp.ValidateServiceAccountCredentialsFile(gcpSaCredentials); err != nil { + return err + } + } + + projectId, err := cmd.Flags().GetString("project_id") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if projectId == "" && !cli.InteractiveMode() { + return errors.New("project_id must be provided") + } + + // Load any cached inputs if interactive + if cli.InteractiveMode() { + cachedOptions := &gcp.GenerateGcpTfConfigurationArgs{} + iacParamsExpired := cli.ReadCachedAsset(CachedGcpAssetIacParams, &cachedOptions) + if iacParamsExpired { + cli.Log.Debug("loaded previously set values for GCP iac generation") + } + + extraState := &GcpGenerateCommandExtraState{} + extraStateParamsExpired := cli.ReadCachedAsset(CachedAssetGcpExtraState, &extraState) + if extraStateParamsExpired { + cli.Log.Debug("loaded previously set values for GCP iac generation (extra state)") + } + + // Determine if previously cached options exists; prompt user if they'd like to continue + answer := false + if !iacParamsExpired || !extraStateParamsExpired { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, + Response: &answer, + }); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs + // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get + // re-prompted. + if answer { + // Merge cached inputs to current options (current options win) + if err := mergo.Merge(GenerateGcpCommandState, cachedOptions); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + if err := mergo.Merge(GenerateGcpCommandExtraState, extraState); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + } + + // Collect and/or confirm parameters + if err := promptGcpGenerate( + GenerateGcpCommandState, + GenerateGcpExistingServiceAccountDetails, + GenerateGcpCommandExtraState, + ); err != nil { + return errors.Wrap(err, "collecting/confirming parameters") + } + + return nil + }, + } +) + +type GcpGenerateCommandExtraState struct { + AskAdvanced bool + Output string + UseExistingServiceAccount bool + UseExistingSink bool + TerraformApply bool +} + +func (gcp *GcpGenerateCommandExtraState) isEmpty() bool { + return gcp.Output == "" && + !gcp.AskAdvanced && + !gcp.UseExistingServiceAccount && + !gcp.UseExistingSink && + !gcp.TerraformApply +} + +// Flush current state of the struct to disk, provided it's not empty +func (gcp *GcpGenerateCommandExtraState) writeCache() { + if !gcp.isEmpty() { + cli.WriteAssetToCache(CachedAssetGcpExtraState, time.Now().Add(time.Hour*1), gcp) + } +} + +func initGenerateGcpTfCommandFlags() { + // add flags to sub commands + // TODO Share the help with the interactive generation + generateGcpTfCommand.PersistentFlags().BoolVar( + &GenerateGcpCommandState.Agentless, + "agentless", + false, + "enable agentless integration") + generateGcpTfCommand.PersistentFlags().BoolVar( + &GenerateGcpCommandState.AuditLog, + "audit_log", + false, + "enable audit log integration") + generateGcpTfCommand.PersistentFlags().BoolVar( + &GenerateGcpCommandState.Configuration, + "configuration", + false, + "enable configuration integration") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandState.ServiceAccountCredentials, + "service_account_credentials", + "", + "specify service account credentials JSON file path (leave blank to make use of google credential ENV vars)") + generateGcpTfCommand.PersistentFlags().BoolVar( + &GenerateGcpCommandState.OrganizationIntegration, + "organization_integration", + false, + "enable organization integration") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandState.GcpOrganizationId, + "organization_id", + "", + "specify the organization id (only set if agentless integration or organization_integration is set)") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandState.GcpProjectId, + "project_id", + "", + "specify the project id to be used to provision lacework resources (required)") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpExistingServiceAccountDetails.Name, + "existing_service_account_name", + "", + "specify existing service account name") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpExistingServiceAccountDetails.PrivateKey, + "existing_service_account_private_key", + "", + "specify existing service account private key (base64 encoded)") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandState.ConfigurationIntegrationName, + "configuration_integration_name", + "", + "specify a custom configuration integration name") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandState.ExistingLogSinkName, + "existing_sink_name", + "", + "specify existing sink name") + generateGcpTfCommand.PersistentFlags().StringSliceVar( + &GenerateGcpCommandState.ProjectFilterList, + "project_filter_list", + []string{}, + "List of GCP project IDs to monitor for Agentless integration") + generateGcpTfCommand.PersistentFlags().StringSliceVar( + &GenerateGcpCommandState.Regions, + "regions", + []string{}, + "List of GCP regions to deploy for Agentless integration") + + // --- + + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandState.CustomFilter, + "custom_filter", + "", + "Audit Log filter which supersedes all other filter options when defined") + generateGcpTfCommand.PersistentFlags().BoolVar( + &GenerateGcpCommandState.GoogleWorkspaceFilter, + "google_workspace_filter", + true, + "filter out Google Workspace login logs from GCP Audit Log sinks") + generateGcpTfCommand.PersistentFlags().BoolVar( + &GenerateGcpCommandState.K8sFilter, + "k8s_filter", + true, + "filter out GKE logs from GCP Audit Log sinks") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandState.Prefix, + "prefix", + "", + "prefix that will be used at the beginning of every generated resource") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandState.WaitTime, + "wait_time", + "", + "amount of time to wait before the next resource is provisioned") + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandState.AuditLogIntegrationName, + "audit_log_integration_name", + "", + "specify a custom audit log integration name") + generateGcpTfCommand.PersistentFlags().StringArrayVarP( + &GenerateGcpCommandState.FoldersToExclude, + "folders_to_exclude", + "e", + []string{}, + "List of root folders to exclude for an organization-level integration") + generateGcpTfCommand.PersistentFlags().BoolVar( + &GenerateGcpCommandState.IncludeRootProjects, + "include_root_projects", + true, + "Disables logic that includes root-level projects if excluding folders") + generateGcpTfCommand.PersistentFlags().StringArrayVarP( + &GenerateGcpCommandState.FoldersToInclude, + "folders_to_include", + "i", + []string{}, + "list of root folders to include for an organization-level integration") + generateGcpTfCommand.PersistentFlags().BoolVar( + &GenerateGcpCommandExtraState.TerraformApply, + "apply", + false, + "run terraform apply without executing plan or prompting", + ) + generateGcpTfCommand.PersistentFlags().StringVar( + &GenerateGcpCommandExtraState.Output, + "output", + "", + "location to write generated content (default is ~/lacework/gcp)", + ) + generateGcpTfCommand.PersistentFlags().BoolVar( + &GenerateGcpCommandState.UsePubSubAudit, + "use_pub_sub", + true, + "deprecated: pub/sub audit log integration is always used and only supported type") + generateGcpTfCommand.PersistentFlags().StringSliceVar( + &GenerateGcpCommandState.Projects, + "projects", + []string{}, + "list of project IDs to integrate with (project-level integrations)") +} + +func promptGcpAgentlessQuestions( + config *gcp.GenerateGcpTfConfigurationArgs, + extraState *GcpGenerateCommandExtraState, +) error { + projectFilterListInput := "" + + err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionGcpProjectFilterList, Default: strings.Join(config.ProjectFilterList, ",")}, + Response: &projectFilterListInput, + }, + }, config.Agentless) + + if projectFilterListInput != "" { + config.ProjectFilterList = strings.Split(projectFilterListInput, ",") + } + + return err +} + +func promptGcpAuditLogQuestions( + config *gcp.GenerateGcpTfConfigurationArgs, + extraState *GcpGenerateCommandExtraState, +) error { + + err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionGcpUseExistingSink, Default: extraState.UseExistingSink}, + Checks: []*bool{&config.AuditLog}, + Required: true, + Response: &extraState.UseExistingSink, + }, + { + Prompt: &survey.Input{Message: QuestionGcpExistingSinkName, Default: config.ExistingLogSinkName}, + Checks: []*bool{&config.AuditLog, &extraState.UseExistingSink}, + Required: true, + Response: &config.ExistingLogSinkName, + }, + { + Prompt: &survey.Input{Message: QuestionGcpCustomFilter, Default: config.CustomFilter}, + Checks: []*bool{&config.AuditLog}, + Response: &config.CustomFilter, + }, + }, config.AuditLog) + + return err +} + +func promptGcpExistingServiceAccountQuestions(config *gcp.GenerateGcpTfConfigurationArgs) error { + // ensure struct is initialized + if config.ExistingServiceAccount == nil { + config.ExistingServiceAccount = &gcp.ExistingServiceAccountDetails{} + } + + err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionExistingServiceAccountName, Default: config.ExistingServiceAccount.Name}, + Response: &config.ExistingServiceAccount.Name, + Opts: []survey.AskOpt{survey.WithValidator(survey.Required)}, + }, + { + Prompt: &survey.Input{ + Message: QuestionExistingServiceAccountPrivateKey, + Default: config.ExistingServiceAccount.PrivateKey, + }, + Response: &config.ExistingServiceAccount.PrivateKey, + Opts: []survey.AskOpt{ + survey.WithValidator(survey.Required), + survey.WithValidator(gcp.ValidateStringIsBase64), + }, + }}) + + return err +} + +func promptGcpIntegrationNameQuestions(config *gcp.GenerateGcpTfConfigurationArgs) error { + err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{ + Message: QuestionGcpConfigurationIntegrationName, + Default: config.ConfigurationIntegrationName, + }, + Checks: []*bool{&config.Configuration}, + Response: &config.ConfigurationIntegrationName, + }, + { + Prompt: &survey.Input{Message: QuestionGcpAuditLogIntegrationName, Default: config.AuditLogIntegrationName}, + Checks: []*bool{&config.AuditLog}, + Response: &config.AuditLogIntegrationName, + }}) + + return err +} + +func promptCustomizeGcpOutputLocation(extraState *GcpGenerateCommandExtraState) error { + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionGcpCustomizeOutputLocation, Default: extraState.Output}, + Response: &extraState.Output, + Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, + Required: true, + }) + + return err +} + +func promptCustomizeGcpProjects(config *gcp.GenerateGcpTfConfigurationArgs) error { + + validation := func(val interface{}) error { + switch value := val.(type) { + case string: + for _, id := range strings.Split(value, ",") { + err := validateGcpProjectId(strings.TrimSpace(id)) + if err != nil { + return err + } + } + default: + return errors.New("value must be a string") + } + + return nil + } + + var projects string + + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionGcpCustomizeProjects}, + Response: &projects, + Opts: []survey.AskOpt{survey.WithValidator(validation)}, + Required: true, + }) + + if err != nil { + return err + } + + for _, id := range strings.Split(projects, ",") { + config.Projects = append(config.Projects, strings.TrimSpace(id)) + } + + return nil +} + +func askAdvancedOptions(config *gcp.GenerateGcpTfConfigurationArgs, extraState *GcpGenerateCommandExtraState) error { + answer := "" + + // Prompt for options + for answer != GcpAdvancedOptDone { + // Construction of this slice is a bit strange at first look, but the reason for that is because we have to do + // string validation to know which option was selected due to how survey works; and doing it by index (also + // supported) is difficult when the options are dynamic (which they are) + var options []string + + // Only show Advanced Agentless options if Agentless integration is set to true + if config.Agentless { + options = append(options, GcpAdvancedOptAgentless) + } + + // Only show Advanced AuditLog options if AuditLog integration is set to true + if config.AuditLog { + options = append(options, GcpAdvancedOptAuditLog) + } + + options = append(options, + GcpAdvancedOptExistingServiceAccount, + GcpAdvancedOptIntegrationName, + GcpAdvancedOptLocation, + GcpAdvancedOptProjects, + GcpAdvancedOptDone) + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: "Which options would you like to configure?", + Options: options, + }, + Response: &answer, + }); err != nil { + return err + } + + // Based on response, prompt for actions + switch answer { + case GcpAdvancedOptAgentless: + if err := promptGcpAgentlessQuestions(config, extraState); err != nil { + return err + } + case GcpAdvancedOptAuditLog: + if err := promptGcpAuditLogQuestions(config, extraState); err != nil { + return err + } + case GcpAdvancedOptExistingServiceAccount: + if err := promptGcpExistingServiceAccountQuestions(config); err != nil { + return err + } + case GcpAdvancedOptIntegrationName: + if err := promptGcpIntegrationNameQuestions(config); err != nil { + return err + } + case GcpAdvancedOptLocation: + if err := promptCustomizeGcpOutputLocation(extraState); err != nil { + return err + } + case GcpAdvancedOptProjects: + if err := promptCustomizeGcpProjects(config); err != nil { + return err + } + } + + // Re-prompt if not done + innerAskAgain := true + if answer == GcpAdvancedOptDone { + innerAskAgain = false + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Checks: []*bool{&innerAskAgain}, + Prompt: &survey.Confirm{Message: QuestionGcpAnotherAdvancedOpt, Default: false}, + Response: &innerAskAgain, + }); err != nil { + return err + } + + if !innerAskAgain { + answer = GcpAdvancedOptDone + } + } + + return nil +} + +func gcpConfigIsEmpty(g *gcp.GenerateGcpTfConfigurationArgs) bool { + return !g.Agentless && + !g.AuditLog && + !g.Configuration && + g.ServiceAccountCredentials == "" && + g.GcpOrganizationId == "" && + g.LaceworkProfile == "" +} + +func writeGcpGenerationArgsCache(a *gcp.GenerateGcpTfConfigurationArgs) { + if !gcpConfigIsEmpty(a) { + // If ExistingServiceAccount is partially set, don't write this to cache; the values won't work when loaded + if a.ExistingServiceAccount.IsPartial() { + a.ExistingServiceAccount = nil + } + cli.WriteAssetToCache(CachedGcpAssetIacParams, time.Now().Add(time.Hour*1), a) + } +} + +// entry point for launching a survey to build out the required generation parameters +func promptGcpGenerate( + config *gcp.GenerateGcpTfConfigurationArgs, + existingServiceAccount *gcp.ExistingServiceAccountDetails, + extraState *GcpGenerateCommandExtraState, +) error { + // Cache for later use if generation is abandon and in interactive mode + if cli.InteractiveMode() { + defer writeGcpGenerationArgsCache(config) + defer extraState.writeCache() + } + + // Set ExistingIamRole details, if provided as cli flags; otherwise don't initialize + if existingServiceAccount.Name != "" || + existingServiceAccount.PrivateKey != "" { + config.ExistingServiceAccount = existingServiceAccount + } + + // These are the core questions that should be asked. + if err := SurveyMultipleQuestionWithValidation( + []SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionGcpEnableAgentless, Default: config.Agentless}, + Response: &config.Agentless, + }, + { + Prompt: &survey.Confirm{Message: QuestionGcpEnableConfiguration, Default: config.Configuration}, + Response: &config.Configuration, + }, + { + Prompt: &survey.Confirm{Message: QuestionGcpEnableAuditLog, Default: config.AuditLog}, + Response: &config.AuditLog, + }, + }); err != nil { + return err + } + + // Validate one of configuration or audit log was enabled; otherwise error out + if !config.Agentless && !config.Configuration && !config.AuditLog { + return errors.New("must enable agentless, audit log or configuration") + } + + configOrAuditLogEnabled := config.Configuration || config.AuditLog + regionsInput := "" + + if err := SurveyMultipleQuestionWithValidation( + []SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionGcpProjectID, Default: config.GcpProjectId}, + Opts: []survey.AskOpt{survey.WithValidator(validateGcpProjectId)}, + Required: true, + Response: &config.GcpProjectId, + }, + { + Prompt: &survey.Input{Message: QuestionGcpRegions, Default: strings.Join(config.Regions, ",")}, + Checks: []*bool{&config.Agentless}, + Response: ®ionsInput, + Required: true, + }, + { + Prompt: &survey.Confirm{Message: QuestionGcpOrganizationIntegration, Default: config.OrganizationIntegration}, + Response: &config.OrganizationIntegration, + }, + }); err != nil { + return err + } + + organizationIdRequired := config.Agentless || config.OrganizationIntegration + + if err := SurveyMultipleQuestionWithValidation( + []SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionGcpOrganizationID, Default: config.GcpOrganizationId}, + Checks: []*bool{&organizationIdRequired}, + Required: true, + Response: &config.GcpOrganizationId, + }, + { + Prompt: &survey.Input{Message: QuestionGcpServiceAccountCredsPath, Default: config.ServiceAccountCredentials}, + Opts: []survey.AskOpt{survey.WithValidator(gcp.ValidateServiceAccountCredentials)}, + Checks: []*bool{&configOrAuditLogEnabled}, + Response: &config.ServiceAccountCredentials, + }, + }); err != nil { + return err + } + + if regionsInput != "" { + config.Regions = strings.Split(regionsInput, ",") + } + + // Find out if the customer wants to specify more advanced features + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionGcpConfigureAdvanced, Default: extraState.AskAdvanced}, + Response: &extraState.AskAdvanced, + }); err != nil { + return err + } + + // Keep prompting for advanced options until the say done + if extraState.AskAdvanced { + if err := askAdvancedOptions(config, extraState); err != nil { + return err + } + } + + return nil +} + +func validateGcpProjectId(val interface{}) error { + switch value := val.(type) { + + case string: + match, err := regexp.MatchString("(^[a-z][a-z0-9-]{4,28}[a-z0-9]$|^$)", value) + if err != nil { + return err + } + + if !match { + return errors.New(InvalidProjectIDMessage) + } + default: + return errors.New("value must be a string") + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gke.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gke.go new file mode 100644 index 000000000..e2cdc0d2d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gke.go @@ -0,0 +1,505 @@ +package cmd + +import ( + "time" + + "github.com/lacework/go-sdk/lwgenerate/gcp" + + "github.com/AlecAivazis/survey/v2" + "github.com/imdario/mergo" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +type GkeGenerateCommandExtraState struct { + AskAdvanced bool + Output string + ConfigureNewBucketSettings bool + UseExistingServiceAccount bool + UseExistingSink bool + TerraformApply bool +} + +func (g *GkeGenerateCommandExtraState) isEmpty() bool { + return g.Output == "" && + !g.AskAdvanced && + !g.UseExistingServiceAccount && + !g.UseExistingSink && + !g.TerraformApply +} + +func (g *GkeGenerateCommandExtraState) writeCache() { + if !g.isEmpty() { + cli.WriteAssetToCache(CachedGkeAssetExtraState, time.Now().Add(time.Hour*1), g) + } +} + +var ( + QuestionGkeOrganizationIntegration = "Organization integration?" + QuestionGkeOrganizationID = "Specify the GCP organization ID:" + QuestionGkeProjectID = "Specify the project ID to be used to provision Lacework resources:" + QuestionGkeServiceAccountCredsPath = "Specify service account credentials JSON path: (optional)" + + QuestionGkeConfigureAdvanced = "Configure advanced integration options?" + GkeAdvancedOpt = "Configure additional options" + QuestionGkeUseExistingSink = "Use an existing sink?" + QuestionGkeExistingSinkName = "Specify the existing sink name" + GkeAdvancedOptIntegrationName = "Customize integration name(s)" + QuestionGkeIntegrationName = "Specify a custom integration name: (optional)" + + GkeAdvancedOptExistingServiceAccount = "Configure & use existing service account" + QuestionGkeExistingServiceAccountName = "Specify an existing service account name:" + QuestionGkeExistingServiceAccountPrivateKey = "Specify an existing service account private key" + + " (base64 encoded):" // guardrails-disable-line + + GkeAdvancedOptLocation = "Customize output location" + QuestionGkeCustomizeOutputLocation = "Provide the location for the output to be written:" + QuestionGkeAnotherAdvancedOpt = "Configure another advanced integration option" + GkeAdvancedOptDone = "Done" + + GenerateGkeCommandState = &gcp.GenerateGkeTfConfigurationArgs{} + GenerateGkeExistingServiceAccount = &gcp.ServiceAccount{} + GenerateGkeCommandExtraState = &GkeGenerateCommandExtraState{} + CachedGkeAssetIacParams = "iac-gke-generate-params" + CachedGkeAssetExtraState = "iac-gke-extra-state" + + generateGkeTfCommand = &cobra.Command{ + Use: "gke", + Short: "Generate and/or execute Terraform code for GKE integration", + Long: `Use this command to generate Terraform code for deploying Lacework into a GKE environment. + +By default, this command interactively prompts for the required information to setup the new cloud account. +In interactive mode, this command will: + +* Prompt for the required information to setup the integration +* Generate new Terraform code using the inputs +* Optionally, run the generated Terraform code: + * If Terraform is already installed, the version is verified as compatible for use + * If Terraform is not installed, or the version installed is not compatible, a new + version will be installed into a temporary location + * Once Terraform is detected or installed, Terraform plan will be executed + * The command will prompt with the outcome of the plan and allow to view more details + or continue with Terraform apply + * If confirmed, Terraform apply will be run, completing the setup of the cloud account + +This command can also be run in noninteractive mode. +See help output for more details on the parameter value(s) required for Terraform code generation. +`, + RunE: func(cmd *cobra.Command, args []string) error { + cli.StartProgress("Generating Terraform Code...") + + if cli.Profile != "default" { + GenerateGkeCommandState.LaceworkProfile = cli.Profile + } + + mods := []gcp.Modifier{ + gcp.WithGkeExistingServiceAccount(GenerateGkeCommandState.ExistingServiceAccount), + gcp.WithGkeExistingSinkName(GenerateGkeCommandState.ExistingSinkName), + gcp.WithGkeIntegrationName(GenerateGkeCommandState.IntegrationName), + gcp.WithGkeLabels(GenerateGkeCommandState.Labels), + gcp.WithGkeLaceworkProfile(GenerateGkeCommandState.LaceworkProfile), + gcp.WithGkeOrganizationId(GenerateGkeCommandState.OrganizationId), + gcp.WithGkeOrganizationIntegration(GenerateGkeCommandState.OrganizationIntegration), + gcp.WithGkePrefix(GenerateGkeCommandState.Prefix), + gcp.WithGkeProjectId(GenerateGkeCommandState.ProjectId), + gcp.WithGkePubSubSubscriptionLabels(GenerateGkeCommandState.PubSubSubscriptionLabels), + gcp.WithGkePubSubTopicLabels(GenerateGkeCommandState.PubSubTopicLabels), + gcp.WithGkeServiceAccountCredentials(GenerateGkeCommandState.ServiceAccountCredentials), + gcp.WithGkeWaitTime(GenerateGkeCommandState.WaitTime), + } + + hcl, err := gcp.NewGkeTerraform(mods...).Generate() + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "failed to generate terraform code") + } + + dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "gke") + if err != nil { + return err + } + + err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Default: GenerateGkeCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, + Response: &GenerateGkeCommandExtraState.TerraformApply, + }) + + if err != nil { + return errors.Wrap(err, "failed to prompt for terraform execution") + } + + locationDir, _ := determineOutputDirPath(dirname, "gke") + if GenerateGkeCommandExtraState.TerraformApply { + err := executionPreRunChecks(dirname, locationDir, "gke") + if err != nil { + return err + } + } + + if !GenerateGkeCommandExtraState.TerraformApply { + cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) + } + + return nil + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + var err error + + if err = gkeValidation(cmd); err != nil { + return err + } + + if cli.InteractiveMode() { + if err = gkeCaching(); err != nil { + return err + } + } + + err = promptGkeGenerate(GenerateGkeCommandState, GenerateGkeExistingServiceAccount, GenerateGkeCommandExtraState) + if err != nil { + return errors.Wrap(err, "collecting/confirming parameters") + } + + return nil + }, + } +) + +func gkeValidation(cmd *cobra.Command) error { + dirname, err := cmd.Flags().GetString("output") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + + if err := validateOutputLocation(dirname); err != nil { + return err + } + + gcpSaCredentials, err := cmd.Flags().GetString("service_account_credentials") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + + if gcpSaCredentials != "" { + if err := gcp.ValidateServiceAccountCredentialsFile(gcpSaCredentials); err != nil { + return err + } + } + + projectId, err := cmd.Flags().GetString("project_id") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + + if projectId == "" && !cli.InteractiveMode() { + return errors.New("project_id must be provided") + } + + return nil +} + +func gkeCaching() error { + cachedOptions := &gcp.GenerateGkeTfConfigurationArgs{} + iacParamsExpired := cli.ReadCachedAsset(CachedGkeAssetIacParams, &cachedOptions) + if iacParamsExpired { + cli.Log.Debug("loaded previously set values for GCP iac generation") + } + + extraState := &GkeGenerateCommandExtraState{} + extraStateParamsExpired := cli.ReadCachedAsset(CachedGkeAssetExtraState, &extraState) + if extraStateParamsExpired { + cli.Log.Debug("loaded previously set values for GCP iac generation (extra state)") + } + + answer := false + if !iacParamsExpired || !extraStateParamsExpired { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, + Response: &answer, + }); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + if answer { + if err := mergo.Merge(GenerateGkeCommandState, cachedOptions); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + if err := mergo.Merge(GenerateGkeCommandExtraState, extraState); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + return nil +} + +func initGenerateGkeTfCommandFlags() { + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeExistingServiceAccount.Name, + "existing_service_account_name", + "", + "specify existing service account name") + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeExistingServiceAccount.PrivateKey, + "existing_service_account_private_key", + "", + "specify existing service account private key (base64 encoded)") + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeCommandState.ExistingSinkName, + "existing_sink_name", + "", + "specify existing sink name") + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeCommandState.IntegrationName, + "integration_name", + "", + "specify a custom integration name") + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeCommandState.OrganizationId, + "organization_id", + "", + "specify the organization id (only set if organization_integration is set)") + generateGkeTfCommand.PersistentFlags().BoolVar( + &GenerateGkeCommandState.OrganizationIntegration, + "organization_integration", + false, + "enable organization integration") + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeCommandState.Prefix, + "prefix", + "", + "prefix that will be used at the beginning of every generated resource") + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeCommandState.ProjectId, + "project_id", + "", + "specify the project id to be used to provision lacework resources (required)") + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeCommandState.ServiceAccountCredentials, + "service_account_credentials", + "", + "specify service account credentials JSON file path (leave blank to make use of google credential ENV vars)") + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeCommandState.WaitTime, + "wait_time", + "", + "amount of time to wait before the next resource is provisioned") + generateGkeTfCommand.PersistentFlags().BoolVar( + &GenerateGkeCommandExtraState.TerraformApply, + "apply", + false, + "run terraform apply without executing plan or prompting", + ) + generateGkeTfCommand.PersistentFlags().StringVar( + &GenerateGkeCommandExtraState.Output, + "output", + "", + "location to write generated content (default is ~/lacework/gcp)", + ) +} + +func gkeConfigIsEmpty(g *gcp.GenerateGkeTfConfigurationArgs) bool { + return g.ServiceAccountCredentials == "" && + g.ProjectId == "" && + g.OrganizationId == "" && + g.LaceworkProfile == "" +} + +func writeGkeGenerationArgsCache(a *gcp.GenerateGkeTfConfigurationArgs) { + if !gkeConfigIsEmpty(a) { + if a.ExistingServiceAccount.IsPartial() { + a.ExistingServiceAccount = nil + } + cli.WriteAssetToCache(CachedGkeAssetIacParams, time.Now().Add(time.Hour*1), a) + } +} + +func promptGkeGenerate( + config *gcp.GenerateGkeTfConfigurationArgs, + existingServiceAccount *gcp.ServiceAccount, + extraState *GkeGenerateCommandExtraState, +) error { + if cli.InteractiveMode() { + defer writeGkeGenerationArgsCache(config) + defer extraState.writeCache() + } + + if existingServiceAccount.Name != "" || + existingServiceAccount.PrivateKey != "" { + config.ExistingServiceAccount = existingServiceAccount + } + + if err := SurveyMultipleQuestionWithValidation( + []SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionGkeProjectID, Default: config.ProjectId}, + Required: true, + Opts: []survey.AskOpt{survey.WithValidator(validateGcpProjectId)}, + Response: &config.ProjectId, + }, + { + Prompt: &survey.Confirm{Message: QuestionGkeOrganizationIntegration, Default: config.OrganizationIntegration}, + Response: &config.OrganizationIntegration, + }, + { + Prompt: &survey.Input{Message: QuestionGkeOrganizationID, Default: config.OrganizationId}, + Checks: []*bool{&config.OrganizationIntegration}, + Required: true, + Response: &config.OrganizationId, + }, + { + Prompt: &survey.Input{Message: QuestionGkeServiceAccountCredsPath, Default: config.ServiceAccountCredentials}, + Opts: []survey.AskOpt{survey.WithValidator(gcp.ValidateServiceAccountCredentials)}, + Response: &config.ServiceAccountCredentials, + }, + }); err != nil { + return err + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionGkeConfigureAdvanced, Default: extraState.AskAdvanced}, + Response: &extraState.AskAdvanced, + }); err != nil { + return err + } + + if extraState.AskAdvanced { + if err := promptGkeAdvancedOptions(config, extraState); err != nil { + return err + } + } + + return nil +} + +func promptGkeAdvancedOptions( + config *gcp.GenerateGkeTfConfigurationArgs, extraState *GkeGenerateCommandExtraState, +) error { + answer := "" + options := []string{ + GkeAdvancedOpt, + GkeAdvancedOptExistingServiceAccount, + GkeAdvancedOptIntegrationName, + GkeAdvancedOptLocation, + GkeAdvancedOptDone, + } + + for answer != GkeAdvancedOptDone { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: "Which options would you like to configure?", + Options: options, + }, + Response: &answer, + }); err != nil { + return err + } + + switch answer { + case GkeAdvancedOpt: + if err := promptGkeQuestions(config, extraState); err != nil { + return err + } + case GkeAdvancedOptExistingServiceAccount: + if err := promptGkeExistingServiceAccountQuestions(config); err != nil { + return err + } + case GkeAdvancedOptIntegrationName: + if err := promptGkeIntegrationNameQuestions(config); err != nil { + return err + } + case GkeAdvancedOptLocation: + if err := promptCustomizeGkeOutputLocation(extraState); err != nil { + return err + } + } + + innerAskAgain := true + if answer == GkeAdvancedOptDone { + innerAskAgain = false + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Checks: []*bool{&innerAskAgain}, + Prompt: &survey.Confirm{Message: QuestionGkeAnotherAdvancedOpt, Default: false}, + Response: &innerAskAgain, + }); err != nil { + return err + } + + if !innerAskAgain { + answer = GkeAdvancedOptDone + } + } + + return nil +} + +func promptGkeQuestions(config *gcp.GenerateGkeTfConfigurationArgs, extraState *GkeGenerateCommandExtraState) error { + err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionGkeUseExistingSink, Default: extraState.UseExistingSink}, + Required: true, + Response: &extraState.UseExistingSink, + }, + { + Prompt: &survey.Input{Message: QuestionGkeExistingSinkName, Default: config.ExistingSinkName}, + Checks: []*bool{&extraState.UseExistingSink}, + Required: true, + Response: &config.ExistingSinkName, + }, + }) + + return err +} + +func promptGkeExistingServiceAccountQuestions(config *gcp.GenerateGkeTfConfigurationArgs) error { + if config.ExistingServiceAccount == nil { + config.ExistingServiceAccount = &gcp.ServiceAccount{} + } + + err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{ + Message: QuestionGkeExistingServiceAccountName, + Default: config.ExistingServiceAccount.Name, + }, + Response: &config.ExistingServiceAccount.Name, + Opts: []survey.AskOpt{survey.WithValidator(survey.Required)}, + }, + { + Prompt: &survey.Input{ + Message: QuestionGkeExistingServiceAccountPrivateKey, + Default: config.ExistingServiceAccount.PrivateKey, + }, + Response: &config.ExistingServiceAccount.PrivateKey, + Opts: []survey.AskOpt{ + survey.WithValidator(survey.Required), + survey.WithValidator(gcp.ValidateStringIsBase64), + }, + }}) + + return err +} + +func promptGkeIntegrationNameQuestions(config *gcp.GenerateGkeTfConfigurationArgs) error { + err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Input{Message: QuestionGkeIntegrationName, Default: config.IntegrationName}, + Response: &config.IntegrationName, + }, + }) + + return err +} + +func promptCustomizeGkeOutputLocation(extraState *GkeGenerateCommandExtraState) error { + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionGkeCustomizeOutputLocation, Default: extraState.Output}, + Response: &extraState.Output, + Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, + Required: true, + }) + + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_k8s.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_k8s.go new file mode 100644 index 000000000..6660b88ec --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_k8s.go @@ -0,0 +1,24 @@ +package cmd + +import "github.com/spf13/cobra" + +var ( + generateK8sCommand = &cobra.Command{ + Use: "k8s", + Short: "Generate Kubernetes integration IaC", + Long: `Generate IaC to deploy Lacework into a Kubernetes platform. + +This command creates Infrastructure as Code (IaC) in the form of Terraform HCL, with the option of running +Terraform and deploying Lacework into GKE. +`, + } +) + +func init() { + generateTfCommand.AddCommand(generateK8sCommand) + + initGenerateGkeTfCommandFlags() + initGenerateAwsEksAuditTfCommandFlags() + generateK8sCommand.AddCommand(generateGkeTfCommand) + generateK8sCommand.AddCommand(generateAwsEksAuditTfCommand) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_oci.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_oci.go new file mode 100644 index 000000000..534064e5a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_oci.go @@ -0,0 +1,438 @@ +package cmd + +import ( + "time" + + "github.com/imdario/mergo" + "github.com/spf13/cobra" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/internal/validate" + "github.com/lacework/go-sdk/lwgenerate/oci" + "github.com/pkg/errors" +) + +type OciGenerateCommandExtraState struct { + AskAdvanced bool + Output string + TerraformApply bool +} + +var ( + // questions + QuestionOciEnableConfig = "Enable configuration integration?" + QuestionOciTenantOcid = "Specify the OCID of the tenant to be integrated" + QuestionOciUserEmail = "Specify the email address to associate with the integration OCI user" + QuestionOciConfigAdvanced = "Configure advanced integration options?" + QuestionOciConfigName = "Specify name of configuration integration (optional)" + QuestionOciCustomizeOutputLocation = "Provide the location for the output to be written:" + QuestionOciAnotherAdvancedOpt = "Configure another advanced integration option" + + // options + OciAdvancedOptDone = "Done" + OciAdvancedOptLocation = "Customize output location" + OciAdvancedOptIntegrationName = "Customize integration name" + + // state + GenerateOciCommandState = &oci.GenerateOciTfConfigurationArgs{} + GenerateOciCommandExtraState = &OciGenerateCommandExtraState{} + + // cache keys + CachedOciAssetIacParams = "iac-oci-generate-params" + CachedAssetOciExtraState = "iac-oci-extra-state" + + // oci command is used to generate TF code for OCI + generateOciTfCommand = &cobra.Command{ + Use: "oci", + Short: "Generate and/or execute Terraform code for OCI integration", + Long: `Use this command to generate Terraform code for deploying Lacework into an OCI tenant. + +By default, this command interactively prompts for the required information to setup the new cloud account. +In interactive mode, this command will: + +* Prompt for the required information to setup the integration +* Generate new Terraform code using the inputs +* Optionally, run the generated Terraform code: + * If Terraform is already installed, the version is verified as compatible for use + * If Terraform is not installed, or the version installed is not compatible, a new + version will be installed into a temporary location + * Once Terraform is detected or installed, Terraform plan will be executed + * The command will prompt with the outcome of the plan and allow to view more details + or continue with Terraform apply + * If confirmed, Terraform apply will be run, completing the setup of the cloud account + +This command can also be run in noninteractive mode. +See help output for more details on the parameter value(s) required for Terraform code generation. +`, + RunE: runGenerateOci, + PreRunE: preRunGenerateOci, + } +) + +func runGenerateOci(cmd *cobra.Command, args []string) error { + // Generate TF Code + cli.StartProgress("Generating Terraform Code...") + + // Explicitly set Lacework profile if it was passed in main args + if cli.Profile != "default" { + GenerateOciCommandState.LaceworkProfile = cli.Profile + } + + // Setup modifiers for NewTerraform constructor + mods := []oci.OciTerraformModifier{ + oci.WithLaceworkProfile(GenerateOciCommandState.LaceworkProfile), + oci.WithConfigName(GenerateOciCommandState.ConfigName), + oci.WithTenantOcid(GenerateOciCommandState.TenantOcid), + oci.WithUserEmail(GenerateOciCommandState.OciUserEmail), + } + + // Create new struct + data := oci.NewTerraform( + GenerateOciCommandState.Config, + mods...) + + // Generate + hcl, err := data.Generate() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "failed to generate terraform code") + } + + // Write-out generated code to location specified + dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "oci") + if err != nil { + return err + } + + // Prompt to execute + err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Default: GenerateOciCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, + Response: &GenerateOciCommandExtraState.TerraformApply, + }) + + if err != nil { + return errors.Wrap(err, "failed to prompt for terraform execution") + } + + locationDir, _ := determineOutputDirPath(dirname, "oci") + if GenerateOciCommandExtraState.TerraformApply { + // Execution pre-run check + err := executionPreRunChecks(dirname, locationDir, "oci") + if err != nil { + return err + } + } + + // Output where code was generated + if !GenerateOciCommandExtraState.TerraformApply { + cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) + } + + return nil +} + +func preRunGenerateOci(cmd *cobra.Command, _ []string) error { + // Validate output location is OK if supplied + dirname, err := cmd.Flags().GetString("output") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateOutputLocation(dirname); err != nil { + return err + } + + // Validate tenant OCID + tenantOcid, err := cmd.Flags().GetString("tenant_ocid") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateOciTenantOcid(tenantOcid); tenantOcid != "" && err != nil { + return err + } + + // Validate user email + ociUserEmail, err := cmd.Flags().GetString("oci_user_email") + if err != nil { + return errors.Wrap(err, "failed to load command flags") + } + if err := validateOciUserEmail(ociUserEmail); ociUserEmail != "" && err != nil { + return err + } + + // Load any cached inputs if interactive + if cli.InteractiveMode() { + cachedOptions := &oci.GenerateOciTfConfigurationArgs{} + iacParamsExpired := cli.ReadCachedAsset(CachedOciAssetIacParams, &cachedOptions) + if iacParamsExpired { + cli.Log.Debug("loaded previously set values for OCI iac generation") + } + + extraState := &OciGenerateCommandExtraState{} + extraStateParamsExpired := cli.ReadCachedAsset(CachedAssetOciExtraState, &extraState) + if extraStateParamsExpired { + cli.Log.Debug("loaded previously set values for OCI iac generation (extra state)") + } + + // Determine if previously cached options exists; prompt user if they'd like to continue + answer := false + if !iacParamsExpired || !extraStateParamsExpired { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, + Response: &answer, + }); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + + // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs + // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get + // re-prompted. + if answer { + // Merge cached inputs to current options (current options win) + if err := mergo.Merge(GenerateOciCommandState, cachedOptions); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + if err := mergo.Merge(GenerateOciCommandExtraState, extraState); err != nil { + return errors.Wrap(err, "failed to load saved options") + } + } + } + + // Collect and/or confirm parameters + err = promptOciGenerate(GenerateOciCommandState, GenerateOciCommandExtraState) + if err != nil { + return errors.Wrap(err, "collecting/confirming parameters") + } + + return nil +} + +func initGenerateOciTfCommandFlags() { + // add flags to sub commands + generateOciTfCommand.PersistentFlags().BoolVar( + &GenerateOciCommandState.Config, + "config", + false, + "enable configuration integration") + generateOciTfCommand.PersistentFlags().StringVar( + &GenerateOciCommandState.ConfigName, + "config_name", + "", + "specify name of configuration integration") + generateOciTfCommand.PersistentFlags().StringVar( + &GenerateOciCommandState.TenantOcid, + "tenant_ocid", + "", + "specify the OCID of the tenant to integrate") + generateOciTfCommand.PersistentFlags().StringVar( + &GenerateOciCommandState.OciUserEmail, + "oci_user_email", + "", + "specify the email address to associate with the integration OCI user") + generateOciTfCommand.PersistentFlags().BoolVar( + &GenerateOciCommandExtraState.TerraformApply, + "apply", + false, + "run terraform apply without executing plan or prompting", + ) + generateOciTfCommand.PersistentFlags().StringVar( + &GenerateOciCommandExtraState.Output, + "output", + "", + "location to write generated content (default is ~/lacework/oci)", + ) +} + +// basic validation of Tenant OCID format +func validateOciTenantOcid(val interface{}) error { + return validateStringWithRegex( + val, + // https://docs.oracle.com/en-us/iaas/Content/General/Concepts/identifiers.htm + `ocid1\.tenancy\.[^\.\s]*\.[^\.\s]*(\.[^\.\s]+)?\.[^\.\s]+`, + "invalid tenant OCID supplied", + ) +} + +// basic validation of email +func validateOciUserEmail(val interface{}) error { + return validate.EmailAddress(val) +} + +func promptCustomizeOciOutputLocation(extraState *OciGenerateCommandExtraState) error { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionOciCustomizeOutputLocation, Default: extraState.Output}, + Response: &extraState.Output, + Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, + Required: true, + }); err != nil { + return err + } + + return nil +} + +func promptCustomizeOciConfigOptions(config *oci.GenerateOciTfConfigurationArgs) error { + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionOciConfigName, Default: config.ConfigName}, + Checks: []*bool{&config.Config}, + Response: &config.ConfigName, + }); err != nil { + return err + } + + return nil +} + +func askAdvancedOciOptions(config *oci.GenerateOciTfConfigurationArgs, extraState *OciGenerateCommandExtraState) error { + answer := "" + + // Prompt for options + for answer != OciAdvancedOptDone { + var options []string + + // Determine if user specified name for Config is potentially required + if config.Config { + options = append(options, OciAdvancedOptIntegrationName) + } + + options = append(options, OciAdvancedOptLocation) + + options = append(options, OciAdvancedOptDone) + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: "Which options would you like to configure?", + Options: options, + }, + Response: &answer, + }); err != nil { + return err + } + + // Based on response, prompt for actions + switch answer { + case OciAdvancedOptLocation: + if err := promptCustomizeOciOutputLocation(extraState); err != nil { + return err + } + case OciAdvancedOptIntegrationName: + if err := promptCustomizeOciConfigOptions(config); err != nil { + return err + } + } + + // Re-prompt if not done + innerAskAgain := true + if answer == OciAdvancedOptDone { + innerAskAgain = false + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Checks: []*bool{&innerAskAgain}, + Prompt: &survey.Confirm{Message: QuestionOciAnotherAdvancedOpt, Default: false}, + Response: &innerAskAgain, + }); err != nil { + return err + } + + if !innerAskAgain { + answer = OciAdvancedOptDone + } + } + + return nil +} + +func configEnabled(config *oci.GenerateOciTfConfigurationArgs) *bool { + return &config.Config +} + +func (a *OciGenerateCommandExtraState) isEmpty() bool { + return a.Output == "" && + !a.TerraformApply && + !a.AskAdvanced +} + +// Flush current state of the struct to disk, provided it's not empty +func (a *OciGenerateCommandExtraState) writeCache() { + if !a.isEmpty() { + cli.WriteAssetToCache(CachedAssetOciExtraState, time.Now().Add(time.Hour*1), a) + } +} + +func ociConfigIsEmpty(g *oci.GenerateOciTfConfigurationArgs) bool { + return !g.Config && + g.ConfigName == "" && + g.LaceworkProfile == "" && + g.TenantOcid == "" && + g.OciUserEmail == "" +} + +func writeOciGenerationArgsCache(a *oci.GenerateOciTfConfigurationArgs) { + if !ociConfigIsEmpty(a) { + cli.WriteAssetToCache(CachedOciAssetIacParams, time.Now().Add(time.Hour*1), a) + } +} + +// entry point for launching a survey to build out the required generation parameters +func promptOciGenerate( + config *oci.GenerateOciTfConfigurationArgs, + extraState *OciGenerateCommandExtraState, +) error { + // Cache for later use if generation is abandon and in interactive mode + if cli.InteractiveMode() { + defer writeOciGenerationArgsCache(config) + defer extraState.writeCache() + } + + // These are the core questions that should be asked. + if err := SurveyMultipleQuestionWithValidation( + []SurveyQuestionWithValidationArgs{ + { + Prompt: &survey.Confirm{Message: QuestionOciEnableConfig, Default: config.Config}, + Response: &config.Config, + }, + }); err != nil { + return err + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionOciTenantOcid, Default: config.TenantOcid}, + Response: &config.TenantOcid, + Opts: []survey.AskOpt{survey.WithValidator(survey.Required), survey.WithValidator(validateOciTenantOcid)}, + Checks: []*bool{configEnabled(config)}, + }); err != nil { + return err + } + + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Input{Message: QuestionOciUserEmail, Default: config.OciUserEmail}, + Response: &config.OciUserEmail, + Opts: []survey.AskOpt{survey.WithValidator(survey.Required), survey.WithValidator(validateOciUserEmail)}, + Checks: []*bool{configEnabled(config)}, + }); err != nil { + return err + } + + // Validate that config was enabled. Otherwise throw error. + if !config.Config { + return errors.New("must enable configuration integration to continue") + } + + // Find out if the customer wants to specify more advanced features + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Confirm{Message: QuestionOciConfigAdvanced, Default: extraState.AskAdvanced}, + Response: &extraState.AskAdvanced, + }); err != nil { + return err + } + + // Keep prompting for advanced options until the say done + if extraState.AskAdvanced { + if err := askAdvancedOciOptions(config, extraState); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/grpc.go b/vendor/github.com/lacework/go-sdk/cli/cmd/grpc.go new file mode 100644 index 000000000..4a2ead21d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/grpc.go @@ -0,0 +1,50 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2024, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strings" + + "github.com/spf13/cobra" +) + +var ( + // grpcCmd is a hidden command that developers use to debug CDK components + grpcCmd = &cobra.Command{ + Use: "grpc", + Hidden: true, + Short: "Starts a CDK gRPC server (developer mode)", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + cli.OutputHuman("\nDevelopment mode for CDK components") + cli.OutputHuman("\n===================================\n\n") + cli.OutputHuman("When debugging a component, it might expect some environment variables and a\n") + cli.OutputHuman("running gRPC server, this command starts the CDK server and shows the variables\n") + cli.OutputHuman("that your component might need:\n\n") + vars := cli.envs() + cli.OutputHuman("export %s", strings.Join(vars, " \\\n ")) + cli.OutputHuman("\n\n'Ctrl+c' to stop the server. ") + return cli.Serve() + }, + } +) + +func init() { + rootCmd.AddCommand(grpcCmd) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/honeyvent.go b/vendor/github.com/lacework/go-sdk/cli/cmd/honeyvent.go new file mode 100644 index 000000000..407efcb7a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/honeyvent.go @@ -0,0 +1,225 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "os" + "regexp" + "runtime" + "strings" + "sync" + + "github.com/honeycombio/libhoney-go" + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/lwdomain" +) + +var ( + // HoneyDataset is the dataset in Honeycomb that we send tracing + // data this variable will be set depending on the environment we + // are running on. During development, we send all events and + // tracing data to a default dataset. + HoneyDataset = "lacework-cli-dev" +) + +const ( + // DisableTelemetry is an environment variable that can be used to + // disable telemetry sent to Honeycomb + DisableTelemetry = "LW_TELEMETRY_DISABLE" + + // HomebrewInstall is an environment variable that denotes the + // install method was via homebrew package manager + HomebrewInstall = "LW_HOMEBREW_INSTALL" + + // ChocolateyInstall is an environment variable that denotes the + // install method was via chocolatey package manager + ChocolateyInstall = "LW_CHOCOLATEY_INSTALL" + + // List of Features + // + // A feature within the Lacework CLI is any functionality that + // can't be traced or tracked by the default event sent to Honeycomb, + // it is a behavior that we, Lacework engineers, would like to + // trace and understand its usage and adoption. + // + // By default the Feature field within the Honeyvent is empty, + // define a new feature below and set it before sending a new + // Honeyvent. Additionally, there is a FeatureData field that + // any feature can use to inject any specific information + // related to that feature. + // + // Example: + // + // ```go + // cli.Event.Feature = featPollCtrScan + // cli.Event.AddFeatureField("key", "value") + // cli.SendHoneyvent() + // ``` + // + // Polling mechanism feature + featPollCtrScan = "poll_ctr_scan" + + // Daily version check feature + featDailyVerCheck = "daily_check" + + // Daily Component version check feature + featDailyCompVerCheck = "daily_comp_check" + + // Generate package manifest feature + featGenPkgManifest = "gen_pkg_manifest" + + // Split package manifest feature + featSplitPkgManifest = "split_pkg_manifest" + + // Migration API v1 -> v2 feature + featMigrateConfigV2 = "migrate_config_v2" +) + +// InitHoneyvent initialize honeycomb library and main Honeyvent, such event +// could be modified during a command execution to add extra parameters such +// as error message, feature data, etc. +func (c *cliState) InitHoneyvent() { + c.Event = &api.Honeyvent{ + Os: runtime.GOOS, + Arch: runtime.GOARCH, + Version: Version, + Profile: c.Profile, + Account: c.Account, + Subaccount: c.Subaccount, + ApiKey: c.KeyID, + CfgVersion: c.CfgVersion, + TraceID: newID(), + InstallMethod: installMethod(), + Dataset: HoneyDataset, + } +} + +// Wait should be called before finishing the execution of any CLI command, +// it waits for pending workers (a.k.a. honeyvents) to be transmitted +func (c *cliState) Wait() { + // wait for any missing worker + c.workers.Wait() + + // flush any pending calls to Honeycomb + libhoney.Close() + + // stop gRPC server gracefully + c.Stop() +} + +// SendHoneyvent is used throughout the CLI to send Honeyvents, these events +// have tracing data to understand how the commands are being executed, what +// features are used and the overall command flow. This function sends the +// events via goroutines so that we don't block the execution of the main process +// +// NOTE: the CLI will send at least one event per command execution +func (c *cliState) SendHoneyvent() { + if disabled := os.Getenv(DisableTelemetry); disabled != "" { + return + } + + if c.LwApi == nil { + c.Log.Debug("unable to send honeyvent", "error") + return + } + + if c.Event.SpanID == "" { + // root span of a trace which is defined by having its parent_id omitted + c.Event.SpanID = c.id + } else { + // parent_id is set always to the root span since this is a command-line + c.Event.ParentID = c.id + c.Event.SpanID = newID() + } + + if c.Event.ContextID == "" { + c.Event.ContextID = os.Getenv("LACEWORK_CONTEXT_ID") + } + + // Lacework accounts are NOT case-sensitive but some users configure them + // in uppercase and others in lowercase, therefore we will normalize all + // account to be lowercase so that we don't see different accounts in + // Honeycomb. + c.Event.Account = strings.ToLower(c.Event.Account) + + // Detect if the account has the full domain, if so, subtract the account + if match, _ := regexp.MatchString(".lacework.net", c.Account); match { + d, err := lwdomain.New(c.Account) + if err == nil { + c.Event.Account = strings.ToLower(d.String()) + } + } + + c.Log.Debugw("new honeyvent", "dataset", HoneyDataset, + "trace_id", c.Event.TraceID, + "span_id", c.Event.SpanID, + "parent_id", c.Event.ParentID, + "context_id", c.Event.ContextID, + ) + honeyvent := libhoney.NewEvent() + _ = honeyvent.Add(c.Event) + honeycombEvent := *c.Event + honeycombEvent.Dataset = c.Event.Dataset + + c.workers.Add(1) + go func(wg *sync.WaitGroup, event *libhoney.Event, honeycombEvent api.Honeyvent) { + defer wg.Done() + + c.Log.Debugw("sending honeyvent", "dataset", HoneyDataset) + + _, err := c.LwApi.V2.Metrics.Send(honeycombEvent) + if err != nil { + c.Log.Debugw("unable to send honeyvent", "error", err) + } + + }(&c.workers, honeyvent, honeycombEvent) + + // after adding a worker to submit a honeyvent, we remove + // all temporal fields such as feature, feature.data, error + c.Event.DurationMs = 0 + c.Event.Error = "" + c.Event.Feature = "" + c.Event.FeatureData = nil +} + +func installMethod() string { + if os.Getenv(HomebrewInstall) != "" { + return "HOMEBREW" + } + + if os.Getenv(ChocolateyInstall) != "" { + return "CHOCOLATEY" + } + return "" +} + +// parseFlags is a helper used to parse all the flags that the user provided +func parseFlags(args []string) (flags []string) { + for len(args) > 0 { + arg := args[0] + args = args[1:] + if len(arg) <= 1 || arg[0] != '-' { + // not a flag + continue + } + + flags = append(flags, strings.Split(arg, "=")[0]) + } + return +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws.go new file mode 100644 index 000000000..8abde774c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws.go @@ -0,0 +1,168 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createAwsConfigIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "role_arn", + Prompt: &survey.Input{Message: "Role ARN: "}, + Validate: survey.Required, + }, + { + Name: "external_id", + Prompt: &survey.Input{Message: "External ID: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + RoleArn string `survey:"role_arn"` + ExternalID string `survey:"external_id"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + awsCfg := api.NewCloudAccount(answers.Name, + api.AwsCfgCloudAccount, + api.AwsCfgData{ + Credentials: api.AwsCfgCredentials{ + RoleArn: answers.RoleArn, + ExternalID: answers.ExternalID, + }, + }, + ) + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(awsCfg) + cli.StopProgress() + return err +} + +func createAwsCloudTrailIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "role_arn", + Prompt: &survey.Input{Message: "Role ARN:"}, + Validate: survey.Required, + }, + { + Name: "external_id", + Prompt: &survey.Input{Message: "External ID:"}, + Validate: survey.Required, + }, + { + Name: "queue_url", + Prompt: &survey.Input{Message: "SQS Queue URL:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + RoleArn string `survey:"role_arn"` + ExternalID string `survey:"external_id"` + QueueUrl string `survey:"queue_url"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + awsCtSqsData := api.AwsCtSqsData{ + QueueUrl: answers.QueueUrl, + Credentials: api.AwsCtSqsCredentials{ + RoleArn: answers.RoleArn, + ExternalID: answers.ExternalID, + }, + } + // ask the user if they would like to configure an Account Mapping + mapping := false + err = survey.AskOne(&survey.Confirm{ + Message: "Configure an Account Mapping File? (org admins only)", + }, &mapping) + + if err != nil { + return err + } + + if mapping { + var content string + + err = survey.AskOne(&survey.Editor{ + Message: "Provide the Account Mapping File in JSON format", + FileName: "*.json", + }, &content) + + if err != nil { + return err + } + + awsCtSqsData.EncodeAccountMappingFile([]byte(content)) + } + + awsCtSqs := api.NewCloudAccount(answers.Name, api.AwsCtSqsCloudAccount, awsCtSqsData) + + cli.StartProgress(" Creating integration...") + defer cli.StopProgress() + + // if the user didn't provide an account mapping file, + // we just create the integration with a regular request + if !mapping { + _, err = cli.LwApi.V2.CloudAccounts.Create(awsCtSqs) + return err + } + + // but if it did provide one, then we need to elevate + // the user to the Organization level because Account + // Mappings are only allowed at that level, so we + // copy the client to make it an org client + orgLwClient, err := api.CopyClient(cli.LwApi, + api.WithOrgAccess(), + ) + if err != nil { + return err + } + _, err = orgLwClient.V2.CloudAccounts.Create(awsCtSqs) + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_cloudwatch.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_cloudwatch.go new file mode 100644 index 000000000..114932c32 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_cloudwatch.go @@ -0,0 +1,64 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createAwsCloudWatchAlertChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "arn", + Prompt: &survey.Input{Message: "Event Bus ARN: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Arn string + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + cloudwatch := api.NewAlertChannel(answers.Name, + api.CloudwatchEbAlertChannelType, + api.CloudwatchEbDataV2{ + EventBusArn: answers.Arn, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(cloudwatch) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_govcloud.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_govcloud.go new file mode 100644 index 000000000..82dda964f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_govcloud.go @@ -0,0 +1,140 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createAwsGovCloudConfigIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "account_id", + Prompt: &survey.Input{Message: "AWS Account ID: "}, + Validate: survey.Required, + }, + { + Name: "access_key_id", + Prompt: &survey.Input{Message: "Access Key ID: "}, + Validate: survey.Required, + }, + { + Name: "secret_access_key", + Prompt: &survey.Password{Message: "Secret Access Key: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + AccountID string `survey:"account_id"` + AccessKeyID string `survey:"access_key_id"` + SecretAccessKey string `survey:"secret_access_key"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + awsCfg := api.NewCloudAccount(answers.Name, + api.AwsUsGovCfgCloudAccount, + api.AwsUsGovCfgData{ + Credentials: api.AwsUsGovCfgCredentials{ + AwsAccountID: answers.AccountID, + AccessKeyID: answers.AccessKeyID, + SecretAccessKey: answers.SecretAccessKey, + }, + }, + ) + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(awsCfg) + cli.StopProgress() + return err +} + +func createAwsGovCloudCTIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "account_id", + Prompt: &survey.Input{Message: "AWS Account ID: "}, + Validate: survey.Required, + }, + { + Name: "access_key_id", + Prompt: &survey.Input{Message: "Access Key ID: "}, + Validate: survey.Required, + }, + { + Name: "secret_access_key", + Prompt: &survey.Password{Message: "Secret Access Key: "}, + Validate: survey.Required, + }, + { + Name: "queue_url", + Prompt: &survey.Input{Message: "SQS Queue URL:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + AccountID string `survey:"account_id"` + AccessKeyID string `survey:"access_key_id"` + SecretAccessKey string `survey:"secret_access_key"` + QueueUrl string `survey:"queue_url"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + awsCfg := api.NewCloudAccount(answers.Name, + api.AwsUsGovCtSqsCloudAccount, + api.AwsUsGovCtSqsData{ + QueueUrl: answers.QueueUrl, + Credentials: api.AwsUsGovCtSqsCredentials{ + AwsAccountID: answers.AccountID, + AccessKeyID: answers.AccessKeyID, + SecretAccessKey: answers.SecretAccessKey, + }, + }, + ) + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(awsCfg) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_s3_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_s3_channel.go new file mode 100644 index 000000000..cc7585cf4 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_s3_channel.go @@ -0,0 +1,80 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createAwsS3ChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "role_arn", + Prompt: &survey.Input{Message: "Role ARN:"}, + Validate: survey.Required, + }, + { + Name: "bucket_arn", + Prompt: &survey.Input{Message: "Bucket ARN:"}, + Validate: survey.Required, + }, + { + Name: "external_id", + Prompt: &survey.Input{Message: "External ID:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + RoleArn string `survey:"role_arn"` + BucketArn string `survey:"bucket_arn"` + ExternalID string `survey:"external_id"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + s3 := api.NewAlertChannel(answers.Name, + api.AwsS3AlertChannelType, + api.AwsS3DataV2{ + Credentials: api.AwsS3Credentials{ + RoleArn: answers.RoleArn, + BucketArn: answers.BucketArn, + ExternalID: answers.ExternalID, + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(s3) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_azure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_azure.go new file mode 100644 index 000000000..6fa67c40b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_azure.go @@ -0,0 +1,211 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createAzureConfigIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "client_id", + Prompt: &survey.Input{Message: "Client ID:"}, + Validate: survey.Required, + }, + { + Name: "client_secret", + Prompt: &survey.Input{Message: "Client Secret:"}, + Validate: survey.Required, + }, + { + Name: "tenant_id", + Prompt: &survey.Input{Message: "Tenant ID:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + ClientID string `survey:"client_id"` + ClientSecret string `survey:"client_secret"` + TenantID string `survey:"tenant_id"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + azure := api.NewCloudAccount(answers.Name, + api.AzureCfgCloudAccount, + api.AzureCfgData{ + TenantID: answers.TenantID, + Credentials: api.AzureCfgCredentials{ + ClientID: answers.ClientID, + ClientSecret: answers.ClientSecret, + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(azure) + cli.StopProgress() + return err +} + +func createAzureActivityLogIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "client_id", + Prompt: &survey.Input{Message: "Client ID:"}, + Validate: survey.Required, + }, + { + Name: "client_secret", + Prompt: &survey.Input{Message: "Client Secret:"}, + Validate: survey.Required, + }, + { + Name: "tenant_id", + Prompt: &survey.Input{Message: "Tenant ID:"}, + Validate: survey.Required, + }, + { + Name: "queue_url", + Prompt: &survey.Input{Message: "Queue URL:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + ClientID string `survey:"client_id"` + ClientSecret string `survey:"client_secret"` + TenantID string `survey:"tenant_id"` + QueueUrl string `survey:"queue_url"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + azure := api.NewCloudAccount(answers.Name, + api.AzureAlSeqCloudAccount, + api.AzureAlSeqData{ + QueueUrl: answers.QueueUrl, + TenantID: answers.TenantID, + Credentials: api.AzureAlSeqCredentials{ + ClientID: answers.ClientID, + ClientSecret: answers.ClientSecret, + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(azure) + cli.StopProgress() + return err +} + +func createAzureAdAlIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "client_id", + Prompt: &survey.Input{Message: "Client ID:"}, + Validate: survey.Required, + }, + { + Name: "client_secret", + Prompt: &survey.Input{Message: "Client Secret:"}, + Validate: survey.Required, + }, + { + Name: "tenant_id", + Prompt: &survey.Input{Message: "Tenant ID:"}, + Validate: survey.Required, + }, + { + Name: "event_hub_namespace", + Prompt: &survey.Input{Message: "Event Hub Fully Qualified Namespace:"}, + Validate: survey.Required, + }, + { + Name: "event_hub_name", + Prompt: &survey.Input{Message: "Event Hub Name:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + ClientID string `survey:"client_id"` + ClientSecret string `survey:"client_secret"` + TenantID string `survey:"tenant_id"` + EventHubNamespace string `survey:"event_hub_namespace"` + EventHubName string `survey:"event_hub_name"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + azure := api.NewCloudAccount(answers.Name, + api.AzureAdAlCloudAccount, + api.AzureAdAlData{ + TenantID: answers.TenantID, + EventHubNamespace: answers.EventHubNamespace, + EventHubName: answers.EventHubName, + Credentials: api.AzureAdAlCredentials{ + ClientID: answers.ClientID, + ClientSecret: answers.ClientSecret, + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(azure) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_cisco_webex.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_cisco_webex.go new file mode 100644 index 000000000..36759f6cc --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_cisco_webex.go @@ -0,0 +1,64 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createCiscoWebexChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "url", + Prompt: &survey.Input{Message: "Webhook URL: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Url string + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + webex := api.NewAlertChannel(answers.Name, + api.CiscoSparkWebhookAlertChannelType, + api.CiscoSparkWebhookDataV2{ + Webhook: answers.Url, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(webex) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ctr_reg_limits.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ctr_reg_limits.go new file mode 100644 index 000000000..1fc78862e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ctr_reg_limits.go @@ -0,0 +1,125 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strings" + + "github.com/AlecAivazis/survey/v2" +) + +func castStringToLimitByLabel(labels string) []map[string]string { + out := make([]map[string]string, 0) + + for _, label := range strings.Split(labels, "\n") { + kv := strings.Split(label, ":") + if len(kv) != 2 { + cli.Log.Warnw("malformed limit_by_label entry, ignoring", + "label", label, + "expected_format", "key:value", + ) + continue + } + out = append(out, map[string]string{kv[0]: kv[1]}) + } + + return out +} + +func askV2LimitByTags(answers interface{}) error { + custom := false + if err := survey.AskOne(&survey.Confirm{ + Message: "Configure limit of scans by tags?", + }, &custom); err != nil { + return err + } + + if custom { + questions := []*survey.Question{{ + Name: "limit_tags", + Prompt: &survey.Multiline{Message: "List of tags to scan:"}, + }} + + if err := survey.Ask(questions, answers, + survey.WithIcons(promptIconsFunc), + ); err != nil { + return err + } + } + + return nil +} + +func askV2LimitByLabels(answers interface{}) error { + custom := false + if err := survey.AskOne(&survey.Confirm{ + Message: "Configure limit of scans by labels?", + }, &custom); err != nil { + return err + } + + if custom { + questions := []*survey.Question{{ + Name: "limit_labels", + Prompt: &survey.Multiline{Message: "List of 'key:value' labels to scan:"}, + }} + + if err := survey.Ask(questions, answers, + survey.WithIcons(promptIconsFunc), + ); err != nil { + return err + } + } + + return nil +} + +func askV2LimitByRepositories(answers interface{}) error { + custom := false + if err := survey.AskOne(&survey.Confirm{ + Message: "Configure limit of scans by repositories?", + }, &custom); err != nil { + return err + } + + if custom { + questions := []*survey.Question{{ + Name: "limit_repos", + Prompt: &survey.Multiline{Message: "List of repositories to scan:"}, + }} + + if err := survey.Ask(questions, answers, + survey.WithIcons(promptIconsFunc), + ); err != nil { + return err + } + } + + return nil +} + +func askV2Limits(answers interface{}) error { + if err := askV2LimitByTags(answers); err != nil { + return err + } + if err := askV2LimitByLabels(answers); err != nil { + return err + } + return askV2LimitByRepositories(answers) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_datadog.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_datadog.go new file mode 100644 index 000000000..7105f3867 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_datadog.go @@ -0,0 +1,96 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createDatadogIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "datadog_site", + Prompt: &survey.Select{Message: "Datadog Site: ", + Options: []string{string(api.DatadogSiteEu), string(api.DatadogSiteCom)}, + Default: string(api.DatadogSiteCom), + }, + }, + { + Name: "datadog_type", + Prompt: &survey.Select{Message: "Datadog Type: ", + Options: []string{ + string(api.DatadogServiceLogsDetails), + string(api.DatadogServiceEventsSummary), + string(api.DatadogServiceLogsSummary), + }, + Default: string(api.DatadogServiceLogsDetails), + }, + }, + { + Name: "api_key", + Prompt: &survey.Input{Message: "Api Key: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + DatadogSite string `survey:"datadog_site"` + DatadogService string `survey:"datadog_type"` + ApiKey string `survey:"api_key"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + site, err := api.DatadogSite(answers.DatadogSite) + if err != nil { + return err + } + + service, err := api.DatadogService(answers.DatadogService) + if err != nil { + return err + } + + datadog := api.NewAlertChannel(answers.Name, + api.DatadogAlertChannelType, + api.DatadogDataV2{ + DatadogSite: site, + DatadogType: service, + ApiKey: answers.ApiKey, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(datadog) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_hub.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_hub.go new file mode 100644 index 000000000..b927386a1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_hub.go @@ -0,0 +1,128 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createDockerHubIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "username", + Prompt: &survey.Input{Message: "Username: "}, + Validate: survey.Required, + }, + { + Name: "password", + Prompt: &survey.Password{Message: "Password: "}, + Validate: survey.Required, + }, + { + Name: "non_os_package_support", + Prompt: &survey.Confirm{ + Message: "Enable scanning for Non-OS packages: "}, + }, + { + Name: "limit_tag", + Prompt: &survey.Input{ + Message: "Limit by Tag: ", + Default: "*", + }, + }, + { + Name: "limit_label", + Prompt: &survey.Input{ + Message: "Limit by Label: ", + Default: "*", + }, + }, + { + Name: "limit_repos", + Prompt: &survey.Input{Message: "Limit by Repository: "}, + }, + { + Name: "limit_max_images", + Prompt: &survey.Select{ + Message: "Limit Number of Images per Repo: ", + Options: []string{"5", "10", "15"}, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Username string + Password string + LimitTag string `survey:"limit_tag"` + LimitLabel string `survey:"limit_label"` + LimitRepos string `survey:"limit_repos"` + LimitMaxImages string `survey:"limit_max_images"` + NonOSPackageSupport bool `survey:"non_os_package_support"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) + if err != nil { + cli.Log.Warnw("unable to convert limit_max_images, using default", + "error", err, + "input", answers.LimitMaxImages, + "default", "5", + ) + limitMaxImages = 5 + } + + docker := api.NewContainerRegistry(answers.Name, + api.DockerhubContainerRegistry, + api.DockerhubData{ + Credentials: api.DockerhubCredentials{ + Username: answers.Username, + Password: answers.Password, + }, + NonOSPackageEval: answers.NonOSPackageSupport, + + LimitByTag: strings.Split(answers.LimitTag, "\n"), + LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), + LimitByRep: strings.Split(answers.LimitRepos, "\n"), + LimitNumImg: limitMaxImages, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.ContainerRegistries.Create(docker) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_v2.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_v2.go new file mode 100644 index 000000000..ba5ba8259 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_v2.go @@ -0,0 +1,112 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createDockerV2Integration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "domain", + Prompt: &survey.Input{Message: "Registry Domain: "}, + Validate: survey.Required, + }, + { + Name: "username", + Prompt: &survey.Input{Message: "Username: "}, + Validate: survey.Required, + }, + { + Name: "password", + Prompt: &survey.Password{Message: "Password: "}, + Validate: survey.Required, + }, + { + Name: "ssl", + Prompt: &survey.Confirm{Message: "Enable SSL?"}, + }, + { + Name: "non_os_package_support", + Prompt: &survey.Confirm{Message: "Enable scanning for Non-OS packages: "}, + }, + { + Name: "limit_tag", + Prompt: &survey.Input{ + Message: "Limit by Tag: ", + Default: "*", + }, + }, + { + Name: "limit_label", + Prompt: &survey.Input{ + Message: "Limit by Label: ", + Default: "*", + }, + }, + } + + answers := struct { + Name string + Domain string + Username string + Password string + SSL bool + NonOSPackageSupport bool `survey:"non_os_package_support"` + LimitTag string `survey:"limit_tag"` + LimitLabel string `survey:"limit_label"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + docker := api.NewContainerRegistry(answers.Name, + api.DockerhubV2ContainerRegistry, + api.DockerhubV2Data{ + Credentials: api.DockerhubV2Credentials{ + Username: answers.Username, + Password: answers.Password, + SSL: answers.SSL, + }, + RegistryDomain: answers.Domain, + NonOSPackageEval: answers.NonOSPackageSupport, + LimitByTag: strings.Split(answers.LimitTag, "\n"), + LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.ContainerRegistries.Create(docker) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ecr.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ecr.go new file mode 100644 index 000000000..b6ce42ce3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ecr.go @@ -0,0 +1,212 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" + + "github.com/lacework/go-sdk/api" +) + +func createAwsEcrIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "domain", + Prompt: &survey.Input{Message: "Registry Domain: "}, + Validate: survey.Required, + }, + { + Name: "non_os_package_support", + Prompt: &survey.Confirm{ + Message: "Enable scanning for Non-OS packages: "}, + }, + { + Name: "limit_tag", + Prompt: &survey.Input{ + Message: "Limit by Tag: ", + Default: "*", + }, + }, + { + Name: "limit_label", + Prompt: &survey.Input{ + Message: "Limit by Label: ", + Default: "*", + }, + }, + { + Name: "limit_repos", + Prompt: &survey.Input{Message: "Limit by Repository: "}, + }, + { + Name: "limit_max_images", + Prompt: &survey.Select{ + Message: "Limit Number of Images per Repo: ", + Options: []string{"5", "10", "15"}, + }, + Validate: survey.Required, + }, + { + Name: "aws_auth_type", + Prompt: &survey.Select{ + Message: "Authentication Type: ", + Options: []string{"AWS IAM Role", "AWS Access Key"}, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Domain string + AccessKeyID string `survey:"access_key_id"` + SecretAccessKey string `survey:"secret_access_key"` + LimitTag string `survey:"limit_tag"` + LimitLabel string `survey:"limit_label"` + LimitRepos string `survey:"limit_repos"` + LimitMaxImages string `survey:"limit_max_images"` + AwsAuthType string `survey:"aws_auth_type"` + NonOSPackageSupport bool `survey:"non_os_package_support"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) + if err != nil { + cli.Log.Warnw("unable to convert limit_max_images, using default", + "error", err, + "input", answers.LimitMaxImages, + "default", "5", + ) + limitMaxImages = 5 + } + + switch answers.AwsAuthType { + + case "AWS IAM Role": + ecrAuthAnswers := struct { + RoleArn string `survey:"role_arn"` + ExternalID string `survey:"external_id"` + }{} + + questionsAuth := []*survey.Question{ + { + Name: "role_arn", + Prompt: &survey.Input{Message: "Role ARN:"}, + Validate: survey.Required, + }, + { + Name: "external_id", + Prompt: &survey.Input{Message: "External ID:"}, + Validate: survey.Required, + }, + } + + err := survey.Ask(questionsAuth, &ecrAuthAnswers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + ecr := api.NewContainerRegistry(answers.Name, + api.AwsEcrContainerRegistry, + api.AwsEcrIamRoleData{ + CrossAccountCredentials: api.AwsEcrCrossAccountCredentials{ + RoleArn: ecrAuthAnswers.RoleArn, + ExternalID: ecrAuthAnswers.ExternalID, + }, + RegistryDomain: answers.Domain, + NonOSPackageEval: answers.NonOSPackageSupport, + LimitByTag: strings.Split(answers.LimitTag, "\n"), + LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), + LimitByRep: strings.Split(answers.LimitRepos, "\n"), + LimitNumImg: limitMaxImages, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.ContainerRegistries.Create(ecr) + cli.StopProgress() + return err + + case "AWS Access Key": + ecrAuthAnswers := struct { + AccessKeyID string `survey:"access_key_id"` + SecretAccessKey string `survey:"secret_access_key"` + }{} + + questionsAuth := []*survey.Question{ + { + Name: "access_key_id", + Prompt: &survey.Input{Message: "Access Key ID: "}, + Validate: survey.Required, + }, + { + Name: "secret_access_key", + Prompt: &survey.Password{Message: "Secret Access Key: "}, + Validate: survey.Required, + }, + } + + err := survey.Ask(questionsAuth, &ecrAuthAnswers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + ecr := api.NewContainerRegistry(answers.Name, + api.AwsEcrContainerRegistry, + api.AwsEcrAccessKeyData{ + AccessKeyCredentials: api.AwsEcrAccessKeyCredentials{ + AccessKeyID: ecrAuthAnswers.AccessKeyID, + SecretAccessKey: ecrAuthAnswers.SecretAccessKey, + }, + RegistryDomain: answers.Domain, + LimitByTag: strings.Split(answers.LimitTag, "\n"), + LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), + LimitByRep: strings.Split(answers.LimitRepos, "\n"), + LimitNumImg: limitMaxImages, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.ContainerRegistries.Create(ecr) + cli.StopProgress() + return err + + default: + return errors.New("unknown ECR authentication method") + } +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_email.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_email.go new file mode 100644 index 000000000..ee2273c27 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_email.go @@ -0,0 +1,68 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createEmailAlertChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "recipients", + Prompt: &survey.Multiline{Message: "List of Recipients: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Recipients string + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + emailAlertChan := api.NewAlertChannel(answers.Name, + api.EmailUserAlertChannelType, + api.EmailUserData{ + ChannelProps: api.EmailUserChannelProps{ + Recipients: strings.Split(answers.Recipients, "\n"), + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(emailAlertChan) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gar.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gar.go new file mode 100644 index 000000000..7ee1b93fb --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gar.go @@ -0,0 +1,166 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createGarIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "domain", + Prompt: &survey.Select{ + Message: "Registry Domain:", + Options: []string{ + "northamerica-northeast1-docker.pkg.dev", + "us-central1-docker.pkg.dev", + "us-east1-docker.pkg.dev", + "us-east4-docker.pkg.dev", + "us-west1-docker.pkg.dev", + "us-west2-docker.pkg.dev", + "us-west3-docker.pkg.dev", + "us-west4-docker.pkg.dev", + "southamerica-east1-docker.pkg.dev", + "europe-north1-docker.pkg.dev", + "europe-west1-docker.pkg.dev", + "europe-west2-docker.pkg.dev", + "europe-west3-docker.pkg.dev", + "europe-west4-docker.pkg.dev", + "europe-west6-docker.pkg.dev", + "asia-east1-docker.pkg.dev", + "asia-east2-docker.pkg.dev", + "asia-northeast1-docker.pkg.dev", + "asia-northeast2-docker.pkg.dev", + "asia-northeast3-docker.pkg.dev", + "asia-south1-docker.pkg.dev", + "asia-southeast1-docker.pkg.dev", + "asia-southeast2-docker.pkg.dev", + "australia-southeast1-docker.pkg.dev", + "asia-docker.pkg.dev", + "europe-docker.pkg.dev", + "us-docker.pkg.dev", + }, + Default: "us-west1-docker.pkg.dev", + }, + Validate: survey.Required, + }, + { + Name: "client_id", + Prompt: &survey.Input{Message: "Client ID:"}, + Validate: survey.Required, + }, + { + Name: "private_key_id", + Prompt: &survey.Input{Message: "Private Key ID:"}, + Validate: survey.Required, + }, + { + Name: "client_email", + Prompt: &survey.Input{Message: "Client Email:"}, + Validate: survey.Required, + }, + { + Name: "private_key", + Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, + Validate: survey.Required, + }, + { + Name: "non_os_package_support", + Prompt: &survey.Confirm{ + Message: "Enable scanning for Non-OS packages: "}, + }, + { + Name: "limit_max_images", + Prompt: &survey.Select{ + Message: "Limit number of images per repository: ", + Options: []string{"5", "10", "15"}, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Domain string + ClientID string `survey:"client_id"` + PrivateKeyID string `survey:"private_key_id"` + ClientEmail string `survey:"client_email"` + PrivateKey string `survey:"private_key"` + NonOSPackageSupport bool `survey:"non_os_package_support"` + LimitTags string `survey:"limit_tags"` + LimitLabels string `survey:"limit_labels"` + LimitRepos string `survey:"limit_repos"` + LimitMaxImages string `survey:"limit_max_images"` + }{} + + if err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ); err != nil { + return err + } + + limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) + if err != nil { + cli.Log.Warnw("unable to convert limit_max_images, using default", + "error", err, + "input", answers.LimitMaxImages, + "default", "5", + ) + limitMaxImages = 5 + } + + // @afiune these are the new API v2 limits + if err := askV2Limits(&answers); err != nil { + return err + } + + gar := api.NewContainerRegistry(answers.Name, + api.GcpGarContainerRegistry, + api.GcpGarData{ + Credentials: api.GcpCredentialsV2{ + ClientEmail: answers.ClientEmail, + ClientID: answers.ClientID, + PrivateKey: answers.PrivateKey, + PrivateKeyID: answers.PrivateKeyID, + }, + RegistryDomain: answers.Domain, + NonOSPackageEval: answers.NonOSPackageSupport, + LimitByTag: strings.Split(answers.LimitTags, "\n"), + LimitByLabel: castStringToLimitByLabel(answers.LimitLabels), + LimitByRep: strings.Split(answers.LimitRepos, "\n"), + LimitNumImg: limitMaxImages, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.ContainerRegistries.Create(gar) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp.go new file mode 100644 index 000000000..4936f84f3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp.go @@ -0,0 +1,196 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createGcpConfigIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "client_id", + Prompt: &survey.Input{Message: "Client ID:"}, + Validate: survey.Required, + }, + { + Name: "private_key_id", + Prompt: &survey.Input{Message: "Private Key ID:"}, + Validate: survey.Required, + }, + { + Name: "client_email", + Prompt: &survey.Input{Message: "Client Email:"}, + Validate: survey.Required, + }, + { + Name: "private_key", + Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, + Validate: survey.Required, + }, + { + Name: "integration_level", + Prompt: &survey.Select{ + Message: "Integration Level:", + Options: []string{ + api.GcpProjectIntegration.String(), + api.GcpOrganizationIntegration.String(), + }, + }, + Validate: survey.Required, + }, + { + Name: "org_project_id", + Prompt: &survey.Input{Message: "Organization/Project ID:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + ClientID string `survey:"client_id"` + PrivateKeyID string `survey:"private_key_id"` + ClientEmail string `survey:"client_email"` + PrivateKey string `survey:"private_key"` + IntegrationLevel string `survey:"integration_level"` + OrgProjectID string `survey:"org_project_id"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + gcp := api.NewCloudAccount(answers.Name, + api.GcpCfgCloudAccount, + api.GcpCfgData{ + ID: answers.OrgProjectID, + IDType: answers.IntegrationLevel, + Credentials: api.GcpCfgCredentials{ + ClientID: answers.ClientID, + ClientEmail: answers.ClientEmail, + PrivateKeyID: answers.PrivateKeyID, + PrivateKey: answers.PrivateKey, + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(gcp) + cli.StopProgress() + return err +} + +func createGcpAuditLogIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "client_id", + Prompt: &survey.Input{Message: "Client ID:"}, + Validate: survey.Required, + }, + { + Name: "private_key_id", + Prompt: &survey.Input{Message: "Private Key ID:"}, + Validate: survey.Required, + }, + { + Name: "client_email", + Prompt: &survey.Input{Message: "Client Email:"}, + Validate: survey.Required, + }, + { + Name: "private_key", + Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, + Validate: survey.Required, + }, + { + Name: "integration_level", + Prompt: &survey.Select{ + Message: "Integration Level:", + Options: []string{ + api.GcpProjectIntegration.String(), + api.GcpOrganizationIntegration.String(), + }, + }, + Validate: survey.Required, + }, + { + Name: "org_project_id", + Prompt: &survey.Input{Message: "Organization/Project ID:"}, + Validate: survey.Required, + }, + { + Name: "subscription_name", + Prompt: &survey.Input{Message: "Subscription Name:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + ClientID string `survey:"client_id"` + PrivateKeyID string `survey:"private_key_id"` + ClientEmail string `survey:"client_email"` + PrivateKey string `survey:"private_key"` + IntegrationLevel string `survey:"integration_level"` + OrgProjectID string `survey:"org_project_id"` + SubscriptionName string `survey:"subscription_name"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + gcp := api.NewCloudAccount(answers.Name, + api.GcpAtSesCloudAccount, + api.GcpAtSesData{ + ID: answers.OrgProjectID, + IDType: answers.IntegrationLevel, + SubscriptionName: answers.SubscriptionName, + Credentials: api.GcpAtSesCredentials{ + ClientID: answers.ClientID, + ClientEmail: answers.ClientEmail, + PrivateKeyID: answers.PrivateKeyID, + PrivateKey: answers.PrivateKey, + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(gcp) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_audit.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_audit.go new file mode 100644 index 000000000..49414aade --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_audit.go @@ -0,0 +1,121 @@ +// +// Author:: David McTavish () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createGcpPubSubAuditLogIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "client_id", + Prompt: &survey.Input{Message: "Client ID:"}, + Validate: survey.Required, + }, + { + Name: "private_key_id", + Prompt: &survey.Input{Message: "Private Key ID:"}, + Validate: survey.Required, + }, + { + Name: "client_email", + Prompt: &survey.Input{Message: "Client Email:"}, + Validate: survey.Required, + }, + { + Name: "private_key", + Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, + Validate: survey.Required, + }, + { + Name: "integration_level", + Prompt: &survey.Select{ + Message: "Integration Level:", + Options: []string{ + api.GcpProjectIntegration.String(), + api.GcpOrganizationIntegration.String(), + }, + }, + Validate: survey.Required, + }, + { + Name: "org_project_id", + Prompt: &survey.Input{Message: "Organization/Project ID:"}, + Validate: survey.Required, + }, + { + Name: "subscription_id", + Prompt: &survey.Input{Message: "Subscription ID:"}, + Validate: survey.Required, + }, + { + Name: "topic_id", + Prompt: &survey.Input{Message: "Topic ID:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + ClientID string `survey:"client_id"` + PrivateKeyID string `survey:"private_key_id"` + ClientEmail string `survey:"client_email"` + PrivateKey string `survey:"private_key"` + IntegrationLevel string `survey:"integration_level"` + OrgProjectID string `survey:"org_project_id"` + SubscriptionName string `survey:"subscription_id"` + TopicID string `survey:"topic_id"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + gcp := api.NewCloudAccount(answers.Name, + api.GcpAlPubSubCloudAccount, + api.GcpAlPubSubSesData{ + ProjectID: answers.OrgProjectID, + IntegrationType: answers.IntegrationLevel, + SubscriptionName: answers.SubscriptionName, + TopicID: answers.TopicID, + Credentials: api.GcpAlPubSubCredentials{ + ClientID: answers.ClientID, + ClientEmail: answers.ClientEmail, + PrivateKeyID: answers.PrivateKeyID, + PrivateKey: answers.PrivateKey, + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(gcp) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_channel.go new file mode 100644 index 000000000..4936407e5 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_channel.go @@ -0,0 +1,109 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createGcpPubSubChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "topic_id", + Prompt: &survey.Input{Message: "Topic ID:"}, + Validate: survey.Required, + }, + { + Name: "project_id", + Prompt: &survey.Input{Message: "Project ID:"}, + Validate: survey.Required, + }, + { + Name: "client_id", + Prompt: &survey.Input{Message: "Client ID:"}, + Validate: survey.Required, + }, + { + Name: "client_email", + Prompt: &survey.Input{Message: "Client Email:"}, + Validate: survey.Required, + }, + { + Name: "private_key_id", + Prompt: &survey.Input{Message: "Private Key ID:"}, + Validate: survey.Required, + }, + { + Name: "private_key", + Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, + Validate: survey.Required, + }, + { + Name: "issue_grouping", + Prompt: &survey.Select{Message: "Issue Grouping:", + Options: []string{"Events", "Resources"}, + }, + }, + } + + answers := struct { + Name string + ClientID string `survey:"client_id"` + PrivateKeyID string `survey:"private_key_id"` + ClientEmail string `survey:"client_email"` + PrivateKey string `survey:"private_key"` + ProjectID string `survey:"project_id"` + TopicID string `survey:"topic_id"` + IssueGrouping string `survey:"issue_grouping"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + gcp := api.NewAlertChannel(answers.Name, + api.GcpPubSubAlertChannelType, + api.GcpPubSubDataV2{ + ProjectID: answers.ProjectID, + TopicID: answers.TopicID, + IssueGrouping: answers.IssueGrouping, + Credentials: api.GcpPubSubCredentials{ + ClientID: answers.ClientID, + ClientEmail: answers.ClientEmail, + PrivateKeyID: answers.PrivateKeyID, + PrivateKey: answers.PrivateKey, + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(gcp) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcr.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcr.go new file mode 100644 index 000000000..d412f42cc --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcr.go @@ -0,0 +1,156 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createGcrIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "domain", + Prompt: &survey.Select{ + Message: "Registry Domain:", + Options: []string{ + "gcr.io", + "us.gcr.io", + "eu.gcr.io", + "asia.gcr.io", + }, + }, + Validate: survey.Required, + }, + { + Name: "client_id", + Prompt: &survey.Input{Message: "Client ID:"}, + Validate: survey.Required, + }, + { + Name: "private_key_id", + Prompt: &survey.Input{Message: "Private Key ID:"}, + Validate: survey.Required, + }, + { + Name: "client_email", + Prompt: &survey.Input{Message: "Client Email:"}, + Validate: survey.Required, + }, + { + Name: "private_key", + Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, + Validate: survey.Required, + }, + { + Name: "non_os_package_support", + Prompt: &survey.Confirm{ + Message: "Enable scanning for Non-OS packages: "}, + }, + { + Name: "limit_tag", + Prompt: &survey.Input{ + Message: "Limit by Tag: ", + Default: "*", + }, + }, + { + Name: "limit_label", + Prompt: &survey.Input{ + Message: "Limit by Label: ", + Default: "*", + }, + }, + { + Name: "limit_repos", + Prompt: &survey.Input{Message: "Limit by Repository: "}, + }, + { + Name: "limit_max_images", + Prompt: &survey.Select{ + Message: "Limit Number of Images per Repo: ", + Options: []string{"5", "10", "15"}, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Domain string + ClientID string `survey:"client_id"` + PrivateKeyID string `survey:"private_key_id"` + ClientEmail string `survey:"client_email"` + PrivateKey string `survey:"private_key"` + NonOSPackageSupport bool `survey:"non_os_package_support"` + LimitTag string `survey:"limit_tag"` + LimitLabel string `survey:"limit_label"` + LimitRepos string `survey:"limit_repos"` + LimitMaxImages string `survey:"limit_max_images"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) + if err != nil { + cli.Log.Warnw("unable to convert limit_max_images, using default", + "error", err, + "input", answers.LimitMaxImages, + "default", "5", + ) + limitMaxImages = 5 + } + + gcr := api.NewContainerRegistry(answers.Name, + api.GcpGcrContainerRegistry, + api.GcpGcrData{ + Credentials: api.GcpCredentialsV2{ + ClientEmail: answers.ClientEmail, + ClientID: answers.ClientID, + PrivateKey: answers.PrivateKey, + PrivateKeyID: answers.PrivateKeyID, + }, + RegistryDomain: answers.Domain, + NonOSPackageEval: answers.NonOSPackageSupport, + LimitByTag: strings.Split(answers.LimitTag, "\n"), + LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), + LimitByRep: strings.Split(answers.LimitRepos, "\n"), + LimitNumImg: limitMaxImages, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.ContainerRegistries.Create(gcr) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ghcr.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ghcr.go new file mode 100644 index 000000000..ea48a5566 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ghcr.go @@ -0,0 +1,124 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createGhcrIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "username", + Prompt: &survey.Input{Message: "Username:"}, + Validate: survey.Required, + }, + { + Name: "password", + Prompt: &survey.Password{Message: "Password:"}, + Validate: survey.Required, + }, + { + Name: "ssl", + Prompt: &survey.Confirm{Message: "Enable SSL?"}, + }, + { + Name: "notifications", + Prompt: &survey.Confirm{Message: "Subscribe to Registry Notifications?"}, + }, + { + Name: "non_os_package_support", + Prompt: &survey.Confirm{ + Message: "Enable scanning for Non-OS packages: "}, + }, + { + Name: "limit_max_images", + Prompt: &survey.Select{ + Message: "Limit number of images per repository: ", + Options: []string{"5", "10", "15"}, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Username string + Password string + SSL bool + Notifications bool + NonOSPackageSupport bool `survey:"non_os_package_support"` + LimitTags string `survey:"limit_tags"` + LimitLabels string `survey:"limit_labels"` + LimitRepos string `survey:"limit_repos"` + LimitMaxImages string `survey:"limit_max_images"` + }{} + + if err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ); err != nil { + return err + } + + limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) + if err != nil { + cli.Log.Warnw("unable to convert limit_max_images, using default", + "error", err, + "input", answers.LimitMaxImages, + "default", "5", + ) + limitMaxImages = 5 + } + + // @afiune these are the new API v2 limits + if err := askV2Limits(&answers); err != nil { + return err + } + + ghcr := api.NewContainerRegistry(answers.Name, + api.GhcrContainerRegistry, + api.GhcrData{ + Credentials: api.GhcrCredentials{ + Username: answers.Username, + Password: answers.Password, + Ssl: answers.SSL, + }, + NonOSPackageEval: answers.NonOSPackageSupport, + LimitByTag: strings.Split(answers.LimitTags, "\n"), + LimitByLabel: castStringToLimitByLabel(answers.LimitLabels), + LimitByRep: strings.Split(answers.LimitRepos, "\n"), + LimitNumImg: limitMaxImages, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.ContainerRegistries.Create(ghcr) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_inline_scanner.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_inline_scanner.go new file mode 100644 index 000000000..7647a7d9d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_inline_scanner.go @@ -0,0 +1,93 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strconv" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createInlineScannerIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "identifier_tag", + Prompt: &survey.Multiline{ + Message: "List of 'key:value' tags:", + Default: "*", + }, + }, + { + Name: "limit_num_scan", + Prompt: &survey.Select{ + Message: "Limit number of scans: ", + Default: "60", + Options: []string{ + "5", "10", "15", + "20", "25", "30", + "35", "40", "45", + "50", "55", "60", + }, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + IdentifierTag string `survey:"identifier_tag"` + LimitNumScan string `survey:"limit_num_scan"` + }{} + + if err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ); err != nil { + return err + } + + limitNumScan, err := strconv.Atoi(answers.LimitNumScan) + if err != nil { + cli.Log.Warnw("unable to convert limit_num_scan, using default", + "error", err, + "input", answers.LimitNumScan, + "default", "60", + ) + limitNumScan = 60 + } + + inline := api.NewContainerRegistry(answers.Name, + api.InlineScannerContainerRegistry, + api.InlineScannerData{ + IdentifierTag: castStringToLimitByLabel(answers.IdentifierTag), + LimitNumScan: strconv.Itoa(limitNumScan), + }, + ) + + cli.StartProgress("Creating integration...") + _, err = cli.LwApi.V2.ContainerRegistries.Create(inline) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_jira.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_jira.go new file mode 100644 index 000000000..d84c8326d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_jira.go @@ -0,0 +1,171 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "sort" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +type jiraAlertChannelIntegrationSurvey struct { + Name string + Url string + Issue string + Project string + Username string + Token string + Password string + Grouping string + Bidirectional bool +} + +func getJiraGroupingOptions() []string { + options := make([]string, 0, len(api.JiraIssueGroupingsSurvey)) + + for option := range api.JiraIssueGroupingsSurvey { + options = append(options, option) + } + + sort.SliceStable(options, func(i, j int) bool { + return api.JiraIssueGroupingsSurvey[options[i]] < api.JiraIssueGroupingsSurvey[options[j]] + }) + + return options +} + +func createJiraAlertChannelIntegration(jiraType string) error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "bidirectional", + Prompt: &survey.Confirm{ + Message: "Would you like a bidirectional integration?", + Default: false, + Help: "See https://docs.lacework.com/onboarding/jira#bidirectional-integration for more detail.", + }, + Validate: survey.Required, + }, + { + Name: "grouping", + Prompt: &survey.Select{ + Message: "Group Issues by:", + Options: getJiraGroupingOptions(), + }, + Validate: survey.Required, + }, + { + Name: "url", + Prompt: &survey.Input{Message: "Jira URL: "}, + Validate: survey.Required, + }, + { + Name: "issue", + Prompt: &survey.Input{Message: "Issue Type: "}, + Validate: survey.Required, + }, + { + Name: "project", + Prompt: &survey.Input{Message: "Project Key: "}, + Validate: survey.Required, + }, + { + Name: "username", + Prompt: &survey.Input{Message: "Username: "}, + Validate: survey.Required, + }, + } + + switch jiraType { + case api.JiraCloudAlertType, "": + jiraType = api.JiraCloudAlertType + questions = append(questions, &survey.Question{ + Name: "token", + Prompt: &survey.Password{Message: "API Token: "}, + Validate: survey.Required, + }) + case api.JiraServerAlertType: + questions = append(questions, &survey.Question{ + Name: "password", + Prompt: &survey.Password{Message: "Password: "}, + Validate: survey.Required, + }) + } + + var answers jiraAlertChannelIntegrationSurvey + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + grouping := api.JiraIssueGroupingsSurvey[answers.Grouping] + jira := api.JiraDataV2{ + ApiToken: answers.Token, + IssueGrouping: grouping.String(), + IssueType: answers.Issue, + JiraType: jiraType, + JiraUrl: answers.Url, + ProjectID: answers.Project, + Username: answers.Username, + Password: answers.Password, + } + if answers.Bidirectional { + jira.Configuration = api.BidirectionalJiraConfiguration + } + + // ask the user if they would like to configure a Custom Template + custom := false + err = survey.AskOne(&survey.Confirm{ + Message: "Configure a Custom Template File?", + }, &custom) + + if err != nil { + return err + } + + if custom { + var content string + + err = survey.AskOne(&survey.Editor{ + Message: "Provide the Custom Template File in JSON format", + FileName: "*.json", + }, &content) + + if err != nil { + return err + } + + jira.EncodeCustomTemplateFile(content) + } + + jiraCloudAlertChan := api.NewAlertChannel(answers.Name, api.JiraAlertChannelType, jira) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(jiraCloudAlertChan) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_microsoft_teams.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_microsoft_teams.go new file mode 100644 index 000000000..3c1a3a9c3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_microsoft_teams.go @@ -0,0 +1,64 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createMicrosoftTeamsChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "webhook_url", + Prompt: &survey.Input{Message: "Webhook URL: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + WebhookUrl string `survey:"webhook_url"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + teams := api.NewAlertChannel(answers.Name, + api.MicrosoftTeamsAlertChannelType, + api.MicrosoftTeamsData{ + TeamsURL: answers.WebhookUrl, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(teams) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_new_relic_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_new_relic_channel.go new file mode 100644 index 000000000..5e26d6705 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_new_relic_channel.go @@ -0,0 +1,71 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createNewRelicAlertChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "account_id", + Prompt: &survey.Input{Message: "Account ID:"}, + Validate: survey.Required, + }, + { + Name: "insert_key", + Prompt: &survey.Input{Message: "Insert API Key:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + AccountID int `survey:"account_id"` + InsertKey string `survey:"insert_key"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + relic := api.NewAlertChannel(answers.Name, + api.NewRelicInsightsAlertChannelType, + api.NewRelicInsightsDataV2{ + AccountID: answers.AccountID, + InsertKey: answers.InsertKey, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(relic) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_oci.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_oci.go new file mode 100644 index 000000000..abf6872cf --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_oci.go @@ -0,0 +1,107 @@ +// +// Author:: Kolbeinn Karlsson () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "os" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" +) + +func createOciConfigIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "tenant_id", + Prompt: &survey.Input{Message: "Tenant ID:"}, + Validate: survey.Required, + }, + { + Name: "tenant_name", + Prompt: &survey.Input{Message: "Tenant Name:"}, + Validate: survey.Required, + }, + { + Name: "home_region", + Prompt: &survey.Input{Message: "Home Region:"}, + Validate: survey.Required, + }, + { + Name: "user_ocid", + Prompt: &survey.Input{Message: "User OCID:"}, + Validate: survey.Required, + }, + { + Name: "fingerprint", + Prompt: &survey.Input{Message: "Public Key Fingerprint:"}, + Validate: survey.Required, + }, + { + Name: "private_key_file", + Prompt: &survey.Input{Message: "Path to private key file:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + TenantID string `survey:"tenant_id"` + TenantName string `survey:"tenant_name"` + HomeRegion string `survey:"home_region"` + UserOCID string `survey:"user_ocid"` + Fingerprint string `survey:"fingerprint"` + PrivateKeyFile string `survey:"private_key_file"` + }{} + + err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)) + if err != nil { + return err + } + + privateKeyBytes, err := os.ReadFile(answers.PrivateKeyFile) + if err != nil { + return errors.Wrap(err, "error reading private key file") + } + + oci := api.NewCloudAccount( + answers.Name, + api.OciCfgCloudAccount, + api.OciCfgData{ + TenantID: answers.TenantID, + TenantName: answers.TenantName, + HomeRegion: answers.HomeRegion, + UserOCID: answers.UserOCID, + Credentials: api.OciCfgCredentials{ + Fingerprint: answers.Fingerprint, + PrivateKey: string(privateKeyBytes), + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.CloudAccounts.Create(oci) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_pagerduty.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_pagerduty.go new file mode 100644 index 000000000..95bbcca7c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_pagerduty.go @@ -0,0 +1,64 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createPagerDutyAlertChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "key", + Prompt: &survey.Input{Message: "Integration Key: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Key string + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + alert := api.NewAlertChannel(answers.Name, + api.PagerDutyApiAlertChannelType, + api.PagerDutyApiDataV2{ + IntegrationKey: answers.Key, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(alert) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_proxy_scanner.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_proxy_scanner.go new file mode 100644 index 000000000..3509e0cf1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_proxy_scanner.go @@ -0,0 +1,110 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createProxyScannerIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "limit_tag", + Prompt: &survey.Input{ + Message: "Limit by Tag: ", + Default: "*", + }, + }, + { + Name: "limit_label", + Prompt: &survey.Multiline{ + Message: "List of 'key:value' labels:", + Default: "*", + }, + }, + { + Name: "limit_repos", + Prompt: &survey.Input{ + Message: "Limit by Repository: ", + Default: "*", + }, + }, + { + Name: "limit_max_images", + Prompt: &survey.Select{ + Message: "Limit Number of Images per Repo: ", + Options: []string{ + "5", "10", "15", + }, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + LimitTag string `survey:"limit_tag"` + LimitLabel string `survey:"limit_label"` + LimitRepos string `survey:"limit_repos"` + LimitMaxImages string `survey:"limit_max_images"` + }{} + + if err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ); err != nil { + return err + } + + limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) + if err != nil { + cli.Log.Warnw("unable to convert limit_max_images, using default", + "error", err, + "input", answers.LimitMaxImages, + "default", "5", + ) + limitMaxImages = 5 + } + + proxy := api.NewContainerRegistry( + answers.Name, + api.ProxyScannerContainerRegistry, + api.ProxyScannerData{ + RegistryType: api.ProxyScannerContainerRegistry.String(), + LimitByTag: strings.Split(answers.LimitTag, "\n"), + LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), + LimitByRep: strings.Split(answers.LimitRepos, "\n"), + LimitNumImg: limitMaxImages, + }, + ) + + cli.StartProgress("Creating integration...") + _, err = cli.LwApi.V2.ContainerRegistries.Create(proxy) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_qradar_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_qradar_channel.go new file mode 100644 index 000000000..475c018ca --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_qradar_channel.go @@ -0,0 +1,85 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createQRadarAlertChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "host_url", + Prompt: &survey.Input{Message: "Host Url:"}, + Validate: survey.Required, + }, + { + Name: "host_port", + Prompt: &survey.Input{Message: "Host Port:"}, + Validate: survey.Required, + }, + { + Name: "communication_type", + Prompt: &survey.Select{Message: "Communication Type:", + Options: []string{string(api.QRadarCommHttps), string(api.QRadarCommHttpsSelfSigned)}, + Default: string(api.QRadarCommHttps), + }, + }, + } + + answers := struct { + Name string + HostURL string `survey:"host_url"` + HostPort int `survey:"host_port"` + CommunicationType string `survey:"communication_type"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + commType, err := api.QRadarComm(answers.CommunicationType) + if err != nil { + return err + } + + qradar := api.NewAlertChannel(answers.Name, + api.IbmQRadarAlertChannelType, + api.IbmQRadarDataV2{ + HostURL: answers.HostURL, + HostPort: answers.HostPort, + QRadarCommType: commType, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(qradar) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_service_now_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_service_now_channel.go new file mode 100644 index 000000000..7f3db8cde --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_service_now_channel.go @@ -0,0 +1,112 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createServiceNowAlertChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name:"}, + Validate: survey.Required, + }, + { + Name: "instance_url", + Prompt: &survey.Input{Message: "InstanceURL:"}, + Validate: survey.Required, + }, + { + Name: "username", + Prompt: &survey.Input{Message: "Username:"}, + Validate: survey.Required, + }, + { + Name: "password", + Prompt: &survey.Password{Message: "Password:"}, + Validate: survey.Required, + }, + { + Name: "issue_grouping", + Prompt: &survey.Select{Message: "Issue Grouping:", + Options: []string{"Events", "Resources"}, + }, + }, + } + + answers := struct { + Name string + InstanceURL string `survey:"instance_url"` + Username string `survey:"username"` + Password string `survey:"password"` + IssueGrouping string `survey:"issue_grouping"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + snow := api.ServiceNowRestDataV2{ + InstanceURL: answers.InstanceURL, + Username: answers.Username, + Password: answers.Password, + IssueGrouping: answers.IssueGrouping, + } + + // ask the user if they would like to configure a Custom Template + custom := false + err = survey.AskOne(&survey.Confirm{ + Message: "Configure a Custom Template File?", + }, &custom) + + if err != nil { + return err + } + + if custom { + var content string + + err = survey.AskOne(&survey.Editor{ + Message: "Provide the Custom Template File in JSON format", + FileName: "*.json", + }, &content) + + if err != nil { + return err + } + + snow.EncodeCustomTemplateFile(content) + } + + snowAlert := api.NewAlertChannel(answers.Name, + api.ServiceNowRestAlertChannelType, + snow) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(snowAlert) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_slack_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_slack_channel.go new file mode 100644 index 000000000..64a3f4a44 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_slack_channel.go @@ -0,0 +1,64 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createSlackAlertChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "url", + Prompt: &survey.Input{Message: "Slack URL: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Url string + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + slack := api.NewAlertChannel(answers.Name, + api.SlackChannelAlertChannelType, + api.SlackChannelDataV2{ + SlackUrl: answers.Url, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(slack) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_splunk.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_splunk.go new file mode 100644 index 000000000..c0eca6cea --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_splunk.go @@ -0,0 +1,106 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createSplunkIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "channel", + Prompt: &survey.Input{Message: "Channel: "}, + }, + { + Name: "hec_token", + Prompt: &survey.Input{Message: "Hec Token: "}, + Validate: survey.Required, + }, + { + Name: "host", + Prompt: &survey.Input{Message: "Host: "}, + Validate: survey.Required, + }, + { + Name: "port", + Prompt: &survey.Input{Message: "Port: "}, + Validate: survey.Required, + }, + { + Name: "source", + Prompt: &survey.Input{Message: "Source: "}, + Validate: survey.Required, + }, + { + Name: "index", + Prompt: &survey.Input{Message: "Index: "}, + Validate: survey.Required, + }, + { + Name: "ssl", + Prompt: &survey.Confirm{Message: "Enable SSL?"}, + }, + } + + answers := struct { + Name string + Channel string + HecToken string `survey:"hec_token"` + Host string + Port int + Source string + Index string + Ssl bool + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + splunk := api.NewAlertChannel(answers.Name, + api.SplunkHecAlertChannelType, + api.SplunkHecDataV2{ + Channel: answers.Channel, + HecToken: answers.HecToken, + Host: answers.Host, + Port: answers.Port, + Ssl: answers.Ssl, + EventData: api.SplunkHecEventDataV2{ + Index: answers.Index, + Source: answers.Source, + }, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(splunk) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_victorops.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_victorops.go new file mode 100644 index 000000000..01c54f295 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_victorops.go @@ -0,0 +1,64 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createVictorOpsChannelIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "url", + Prompt: &survey.Input{Message: "VictorOps URL: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Url string + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + victorops := api.NewAlertChannel(answers.Name, + api.VictorOpsAlertChannelType, + api.VictorOpsDataV2{ + Url: answers.Url, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(victorops) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_webhook.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_webhook.go new file mode 100644 index 000000000..0a27b97a0 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_webhook.go @@ -0,0 +1,64 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createWebhookIntegration() error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "url", + Prompt: &survey.Input{Message: "Webhook URL: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string + Url string + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return err + } + + webhook := api.NewAlertChannel(answers.Name, + api.WebhookAlertChannelType, + api.WebhookDataV2{ + WebhookUrl: answers.Url, + }, + ) + + cli.StartProgress(" Creating integration...") + _, err = cli.LwApi.V2.AlertChannels.Create(webhook) + cli.StopProgress() + return err +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql.go new file mode 100644 index 000000000..e46f9a45f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql.go @@ -0,0 +1,590 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "io" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/failon" + "github.com/lacework/go-sdk/lwtime" +) + +var ( + queryCmdState = struct { + End string + File string + Limit int + Range string + Start string + URL string + ValidateOnly bool + FailOnCount string + EmptyTemplate bool + // create, update validate from library + CURVFromLibrary string + }{} + + // queryCmd represents the lql parent command + queryCmd = &cobra.Command{ + Use: "query", + Aliases: []string{"lql", "queries"}, + Short: "Run and manage queries", + Long: `Run and manage Lacework Query Language (LQL) queries. + +LQL is a SQL-like query language for specifying the selection, filtering, and +manipulation of data. Queries let you interactively request information from +specified curated datasources. + +Lacework ships a set of default LQL queries that are available in your account. + +For more information about LQL, visit: + + https://docs.lacework.com/lql-overview + +To view all LQL queries in your Lacework account. + + lacework query ls + +To show a query. + + lacework query show + +To execute a query. + + lacework query run + +**Note: LQL syntax may change.** +`, + } + + // queryRunCmd represents the lql run command + queryRunCmd = &cobra.Command{ + Aliases: []string{"execute"}, + Use: "run [query_id]", + Short: "Run a query", + Long: `Run an LQL query via editor: + + lacework query run --range today + +Run a query via ID (uses active profile): + + lacework query run MyQuery --start "-1w@w" --end "@w" + +Start and end times are required to run a query: + +1. Specify start and end times in one of the following formats: + + A. A relative time specifier + B. RFC3339 date and time + C. Epoch time in milliseconds + +2. Specify start and end times in one of the following ways: + + A. As StartTimeRange and EndTimeRange in the ParamInfo block within the query + B. As start_time_range and end_time_range if specifying JSON + C. As --start and --end CLI flags + +3. Start and End time precedence: + + A. CLI flags take precedence over JSON specifications`, + Args: cobra.MaximumNArgs(1), + PreRunE: func(_ *cobra.Command, _ []string) error { + // default is 0 hence the '< 0' comparison + if queryCmdState.Limit < 0 { + return errors.New("limit must be at least 1") + } + + if queryCmdState.FailOnCount != "" { + var co failon.CountOperation + if err := co.Parse(queryCmdState.FailOnCount); err != nil { + return err + } + + if _, err := co.IsFail(0); err != nil { + return err + } + } + return nil + }, + RunE: runQuery, + } +) + +func init() { + // add the lql command + rootCmd.AddCommand(queryCmd) + + // add sub-commands to the lql command + queryCmd.AddCommand(queryRunCmd) + + if cli.isLCLInstalled() { + queryRunCmd.Flags().StringVarP( + &queryCmdState.CURVFromLibrary, + "library", "l", "", + "run query from Lacework Content Library", + ) + } + + // run specific flags + setQuerySourceFlags(queryRunCmd) + + // limit flag + queryRunCmd.Flags().IntVar( + &queryCmdState.Limit, + "limit", 0, + "result limit for query (default 0)", + ) + + // range time flag + queryRunCmd.Flags().StringVar( + &queryCmdState.Range, + "range", "", + "natural time range for query", + ) + + // start time flag + queryRunCmd.Flags().StringVar( + &queryCmdState.Start, + "start", "-24h", + "start time for query", + ) + // end time flag + queryRunCmd.Flags().StringVar( + &queryCmdState.End, + "end", "now", + "end time for query", + ) + queryRunCmd.Flags().BoolVar( + &queryCmdState.ValidateOnly, + "validate_only", false, + "validate query only (do not run)", + ) + // fail on count + queryRunCmd.Flags().StringVar( + &queryCmdState.FailOnCount, + "fail_on_count", "", + "fail if the results from a query match the provided expression (e.g. '>0')", + ) + // empty template flag + queryRunCmd.Flags().BoolVar( + &queryCmdState.EmptyTemplate, + "empty", false, + "start $EDITOR with empty file", + ) +} + +func setQuerySourceFlags(cmds ...*cobra.Command) { + for _, cmd := range cmds { + if cmd != nil { + action := strings.Split(cmd.Use, " ")[0] + + // file flag to specify a query from disk + cmd.Flags().StringVarP( + &queryCmdState.File, + "file", "f", "", + fmt.Sprintf("path to a query to %s", action), + ) + // url flag to specify a query from url + cmd.Flags().StringVarP( + &queryCmdState.URL, + "url", "u", "", + fmt.Sprintf("url to a query to %s", action), + ) + } + } +} + +// for commands that take a query as input +func inputQuery(cmd *cobra.Command) (string, error) { + // if running via library (CUV) + if queryCmdState.CURVFromLibrary != "" { + return inputQueryFromLibrary(queryCmdState.CURVFromLibrary) + } + // if running via file + if queryCmdState.File != "" { + return inputQueryFromFile(queryCmdState.File) + } + // if running via URL + if queryCmdState.URL != "" { + return inputQueryFromURL(queryCmdState.URL) + } + // if running via stdin + stat, err := os.Stdin.Stat() + if err != nil { + cli.Log.Debugw("error retrieving stdin mode", "error", err.Error()) + } else if (stat.Mode() & os.ModeCharDevice) == 0 { + bytes, err := io.ReadAll(os.Stdin) + return string(bytes), err + } + // if running via editor + action := "validate" + if !queryCmdState.ValidateOnly { + action = strings.Split(cmd.Use, " ")[0] + } + return inputQueryFromEditor(action) +} + +func inputQueryFromLibrary(id string) (string, error) { + var ( + lcl *LaceworkContentLibrary + err error + ) + if lcl, err = cli.LoadLCL(); err != nil { + return "", err + } + return lcl.GetQuery(id) +} + +func inputQueryFromFile(filePath string) (string, error) { + fileData, err := os.ReadFile(filePath) + + if err != nil { + return "", errors.Wrap(err, "unable to read file") + } + + return string(fileData), nil +} + +func inputQueryFromURL(url string) (query string, err error) { + msg := "unable to access URL" + + response, err := http.Get(url) + if err != nil { + err = errors.Wrap(err, msg) + return + } + defer response.Body.Close() + + if response.StatusCode != 200 { + err = errors.Wrap(errors.New(response.Status), msg) + return + } + + body, err := io.ReadAll(response.Body) + if err != nil { + err = errors.Wrap(err, msg) + return + } + query = string(body) + return +} + +func inputQueryFromEditor(action string) (query string, err error) { + prompt := &survey.Editor{ + Message: fmt.Sprintf("Type a query to %s", action), + FileName: "query*.yaml", + } + + if (action == "create" || action == "run") && !queryCmdState.EmptyTemplate { + prompt.Default = `queryId: YourQueryID +queryText: |- + { + source { + --- Select a datasource. To list all available datasources, use 'lacework query sources'. + } + filter { + --- Add query filter(s), if any. If not, remove this block. + } + return { + --- List fields to return from the selected source. Use 'lacework query describe '. + } + }` + prompt.HideDefault = true + prompt.AppendDefault = true + } else if (action == "create" || action == "run") && queryCmdState.EmptyTemplate { + prompt.Default = `` + prompt.HideDefault = true + prompt.AppendDefault = true + } + + err = survey.AskOne(prompt, &query) + return +} + +func parseQueryTime(s string) (time.Time, error) { + // empty + if s == "" { + return time.Time{}, errors.New(fmt.Sprintf("unable to parse time (%s)", s)) + } + // parse time as relative + if t, err := lwtime.ParseRelative(s); err == nil { + return t, err + } + // parse time as RFC3339 + if t, err := time.Parse(time.RFC3339, s); err == nil { + return t, err + } + // parse time as millis + if i, err := strconv.ParseInt(s, 10, 64); err == nil { + return time.Unix(0, i*int64(time.Millisecond)), err + } + return time.Time{}, errors.New(fmt.Sprintf("unable to parse time (%s)", s)) +} + +func queryErrorCrumbs(q string) error { + // smells like json + q = strings.TrimSpace(q) + if strings.HasPrefix(q, "[") || + strings.HasPrefix(q, "{") { + + return errors.New(`invalid query + +It looks like you attempted to submit a query in JSON format. +Verify that the JSON is formatted properly and adheres to the following schema: + +{ + "queryId": "MyLQL", + "queryText": "{ source { CloudTrailRawEvents } filter { EVENT_SOURCE = 's3.amazonaws.com' } return { INSERT_ID } }" +} +`) + } + // smells like plain text + return errors.New(`invalid query + +It looks like you attempted to submit a query in YAML format. +Verify that the text adheres to the following schema: + +queryId: MyLQL +queryText: |- + { + source { + CloudTrailRawEvents + } + filter { + EVENT_SOURCE = 's3.amazonaws.com' + } + return { + INSERT_ID + } + } +`) +} + +func runQuery(cmd *cobra.Command, args []string) error { + var ( + err error + start time.Time + end time.Time + response api.ExecuteQueryResponse + msg string = "unable to run query" + hasCmdArgs bool = len(args) != 0 && args[0] != "" + ) + + // check use of with other flags + if hasCmdArgs { + var naFlag string + + if queryCmdState.File != "" { + naFlag = "file" + } + if queryCmdState.CURVFromLibrary != "" { + naFlag = "library" + } + if queryCmdState.URL != "" { + naFlag = "url" + } + if queryCmdState.ValidateOnly { + naFlag = "validate_only" + } + if queryCmdState.EmptyTemplate { + naFlag = "empty" + } + if naFlag != "" { + return errors.New( + fmt.Sprintf( + "flag --%s not applicable when specifying query_id argument", + naFlag, + ), + ) + } + } + + // validate_only + if queryCmdState.ValidateOnly { + return validateQuery(cmd, args) + } + + // use of if/else intentional here based on logic paths for determining start and end time.Time values + // if cli user has specified a range we use ParseNatural which gives us start and end time.Time values + // otherwise we need to convert queryCmdState start and end strings to time.Time values using parseQueryTime + if queryCmdState.Range != "" { + cli.Log.Debugw("retrieving natural time range") + + start, end, err = lwtime.ParseNatural(queryCmdState.Range) + if err != nil { + return errors.Wrap(err, msg) + } + } else { + // parse start + start, err = parseQueryTime(queryCmdState.Start) + if err != nil { + return errors.Wrap(err, msg) + } + // parse end + end, err = parseQueryTime(queryCmdState.End) + if err != nil { + return errors.Wrap(err, msg) + } + } + + queryArgs := []api.ExecuteQueryArgument{ + { + Name: api.QueryStartTimeRange, + Value: start.UTC().Format(lwtime.RFC3339Milli), + }, + { + Name: api.QueryEndTimeRange, + Value: end.UTC().Format(lwtime.RFC3339Milli), + }, + } + + if hasCmdArgs { + // query by id + response, err = runQueryByID(args[0], queryArgs) + } else { + // adhoc query + response, err = runAdhocQuery(cmd, queryArgs) + } + + if err != nil { + return errors.Wrap(err, "unable to run query") + } + + // output + if err = cli.OutputJSON(response.Data); err != nil { + return err + } + + // fail_on_count post + if queryCmdState.FailOnCount != "" { + cli.Log.Infow("enforce failure flag(s)", + "fail_on_count", queryCmdState.FailOnCount, + ) + + queryFailonError := NewQueryFailonError( + queryCmdState.FailOnCount, + len(response.Data), + ) + if queryFailonError.NonCompliant() { + cmd.SilenceUsage = true + return queryFailonError + } + } + return nil +} + +func runQueryByID(id string, args []api.ExecuteQueryArgument) ( + api.ExecuteQueryResponse, + error, +) { + cli.Log.Debugw("running query", "query", id) + + cli.StartProgress(getRunStartProgressMessage(args)) + defer cli.StopProgress() + + opts := api.ExecuteQueryOptions{} + // only add limit if > 0 + if queryCmdState.Limit > 0 { + opts.Limit = &queryCmdState.Limit + } + + request := api.ExecuteQueryByIDRequest{ + QueryID: id, + Options: opts, + Arguments: args, + } + return cli.LwApi.V2.Query.ExecuteByID(request) +} + +func runAdhocQuery(cmd *cobra.Command, args []api.ExecuteQueryArgument) ( + response api.ExecuteQueryResponse, + err error, +) { + // input query + queryString, err := inputQuery(cmd) + if err != nil { + return + } + // parse query + newQuery, err := api.ParseNewQuery(queryString) + if err != nil { + err = queryErrorCrumbs(queryString) + return + } + + opts := api.ExecuteQueryOptions{} + // only add limit if > 0 + if queryCmdState.Limit > 0 { + opts.Limit = &queryCmdState.Limit + } + + cli.StartProgress(getRunStartProgressMessage(args)) + defer cli.StopProgress() + + // execute query + executeQuery := api.ExecuteQueryRequest{ + Query: api.ExecuteQuery{ + QueryText: newQuery.QueryText, + }, + Options: opts, + Arguments: args, + } + + cli.Log.Debugw("running query", "query", queryString) + response, err = cli.LwApi.V2.Query.Execute(executeQuery) + return +} + +func getRunStartProgressMessage(args []api.ExecuteQueryArgument) string { + var ( + startTime, endTime time.Time + startErr error = errors.New("StartTimeRange not present in ExecuteQueryArgument list") + endErr error = errors.New("EndTimeRange not present in ExecuteQueryArgument list") + ) + for _, arg := range args { + switch arg.Name { + case api.QueryStartTimeRange: + startTime, startErr = time.Parse(time.RFC3339, arg.Value) + case api.QueryEndTimeRange: + endTime, endErr = time.Parse(time.RFC3339, arg.Value) + } + } + + msg := "Executing query" + if startErr == nil && endErr == nil { + msg = fmt.Sprintf( + "%s in the time range %s - %s", + msg, + startTime.Format("2006-Jan-2 15:04:05 MST"), + endTime.Format("2006-Jan-2 15:04:05 MST"), + ) + } + return msg +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_create.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_create.go new file mode 100644 index 000000000..1f6f00d04 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_create.go @@ -0,0 +1,156 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + // queryCreateCmd represents the lql create command + queryCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a query", + Long: ` +There are multiple ways you can create a query: + + * Typing the query into your default editor (via $EDITOR) + * Piping a query to the Lacework CLI command (via $STDIN) + * From a local file on disk using the flag '--file' + * From a URL using the flag '--url' + +There are also multiple formats you can use to define a query: + + * Javascript Object Notation (JSON) + * YAML Ain't Markup Language (YAML) + +To launch your default editor and create a new query. + + lacework lql create + +The following example checks for unrestricted ingress to TCP port 445: + + --- + queryId: LW_Custom_UnrestrictedIngressToTCP445 + queryText: |- + { + source { + LW_CFG_AWS_EC2_SECURITY_GROUPS a, + array_to_rows(a.RESOURCE_CONFIG:IpPermissions) as (ip_permissions), + array_to_rows(ip_permissions:IpRanges) as (ip_ranges) + } + filter { + ip_permissions:IpProtocol = 'tcp' + and ip_permissions:FromPort = 445 + and ip_permissions:ToPort = 445 + and ip_ranges:CidrIp = '0.0.0.0/0' + } + return distinct { + ACCOUNT_ALIAS, + ACCOUNT_ID, + ARN as RESOURCE_KEY, + RESOURCE_REGION, + RESOURCE_TYPE, + SERVICE + } + } + +A query is represented using JSON or YAML markup and must specify both 'queryId' +and 'queryText' keys. The above query uses YAML, specifies an identifier of +'LW_Custom_UnrestrictedIngressToTCP445', and identifies AWS EC2 security groups with +unrestricted access to TCP port 445. The queryText is expressed in Lacework Query +Language (LQL) syntax which is delimited by '{ }' and contains three sections: + + * Source data is specified in the 'source' clause. The source of data is the + 'LW_CFG_AWS_EC2_SECURITY_GROUPS' datasource. LQL queries generally refer to other + datasources, and customizable policies always target a suitable datasource. + + * Records of interest are specified by the 'filter' clause. In the example, the + records available in 'LW_CFG_AWS_EC2_SECURITY_GROUPS' are filtered for those whose IP + protocol is 'tcp', whose from and to port is '445', and CidrIP is '0.0.0.0/0'. + The syntax for this filtering expression strongly resembles SQL. + + * The fields this query exposes are listed in the 'return' clause. Because there + may be unwanted duplicates among result records when Lacework composes them from + just these four columns, the distinct modifier is added. This behaves like a SQL + 'SELECT DISTINCT'. Each returned column in this case is just a field that is present + in 'LW_CFG_AWS_EC2_SECURITY_GROUPS', but you can compose results by manipulating strings, + dates, JSON and numbers as well. + +The resulting dataset is shaped like a table. The table's columns are named with the +names of the columns selected. If desired, you could alias them to other names as well. + +For more information about LQL, visit: + + https://docs.lacework.com/lql-overview +`, + Args: cobra.NoArgs, + RunE: createQuery, + } +) + +func init() { + // add sub-commands to the lql command + queryCmd.AddCommand(queryCreateCmd) + + setQuerySourceFlags(queryCreateCmd) + + if cli.isLCLInstalled() { + queryCreateCmd.Flags().StringVarP( + &queryCmdState.CURVFromLibrary, + "library", "l", "", + "create query from Lacework Content Library", + ) + } +} + +func createQuery(cmd *cobra.Command, args []string) error { + msg := "unable to create query" + + // input query + queryString, err := inputQuery(cmd) + if err != nil { + return errors.Wrap(err, msg) + } + + // parse query + newQuery, err := api.ParseNewQuery(queryString) + if err != nil { + return errors.Wrap(queryErrorCrumbs(queryString), msg) + } + + // create query + cli.Log.Debugw("creating query", "query", queryString) + cli.StartProgress(" Creating query...") + create, err := cli.LwApi.V2.Query.Create(newQuery) + cli.StopProgress() + + // output + if err != nil { + return errors.Wrap(err, msg) + } + if cli.JSONOutput() { + return cli.OutputJSON(create.Data) + } + cli.OutputHuman("The query %s was created.\n", create.Data.QueryID) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_delete.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_delete.go new file mode 100644 index 000000000..2d66b3f78 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_delete.go @@ -0,0 +1,57 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // queryDeleteCmd represents the lql delete command + queryDeleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a query", + Long: `Delete a single LQL query by providing the query ID. + +Use the command 'lacework query list' to list the available queries in +your Lacework account.`, + Args: cobra.ExactArgs(1), + RunE: deleteQuery, + } +) + +func init() { + // add sub-commands to the lql command + queryCmd.AddCommand(queryDeleteCmd) +} + +func deleteQuery(_ *cobra.Command, args []string) error { + cli.Log.Debugw("deleting query", "id", args[0]) + + cli.StartProgress(" Deleting query...") + _, err := cli.LwApi.V2.Query.Delete(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to delete query") + } + + cli.OutputHuman("The query %s was deleted.\n", args[0]) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_library.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_library.go new file mode 100644 index 000000000..238f3b72d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_library.go @@ -0,0 +1,117 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "sort" + + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + queryListLibraryCmd = &cobra.Command{ + Use: "list-library", + Short: "List queries from library", + Long: `List all LQL queries in your Lacework Content Library.`, + Args: cobra.NoArgs, + RunE: listQueryLibrary, + } + queryShowLibraryCmd = &cobra.Command{ + Use: "show-library ", + Short: "Show a query from library", + Long: `Show a query in your Lacework Content Library.`, + Args: cobra.ExactArgs(1), + RunE: showQueryLibrary, + } +) + +func init() { + if cli.isLCLInstalled() { + queryCmd.AddCommand(queryListLibraryCmd) + queryCmd.AddCommand(queryShowLibraryCmd) + } +} + +func getListQueryLibraryTable(queries map[string]LCLQuery) (out [][]string) { + for id := range queries { + out = append(out, []string{id}) + } + // order by ID + sort.Slice(out, func(i, j int) bool { + return out[i][0] < out[j][0] + }) + return +} + +func listQueryLibrary(_ *cobra.Command, args []string) error { + cli.Log.Debugw("listing queries from library") + + cli.StartProgress(" Retrieving queries...") + lcl, err := cli.LoadLCL() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "unable to list queries") + } + if cli.JSONOutput() { + return cli.OutputJSON(lcl.Queries) + } + if len(lcl.Queries) == 0 { + cli.OutputHuman("There were no queries found.") + return nil + } + cli.OutputHuman( + renderSimpleTable( + []string{"Query ID"}, + getListQueryLibraryTable(lcl.Queries), + ), + ) + return nil +} + +func showQueryLibrary(_ *cobra.Command, args []string) error { + var ( + msg string = "unable to show query" + queryString string + newQuery api.NewQuery + err error + ) + cli.Log.Debugw("retrieving query", "id", args[0]) + + cli.StartProgress(" Retrieving query...") + // input query + if queryString, err = inputQueryFromLibrary(args[0]); err != nil { + cli.StopProgress() + return errors.Wrap(err, msg) + } + // parse query + newQuery, err = api.ParseNewQuery(queryString) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, msg) + } + if cli.JSONOutput() { + return cli.OutputJSON(newQuery) + } + cli.OutputHuman(newQuery.QueryText) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_list.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_list.go new file mode 100644 index 000000000..013d63674 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_list.go @@ -0,0 +1,88 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "sort" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + // queryListCmd represents the lql list command + queryListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List queries", + Long: `List all LQL queries in your Lacework account.`, + Args: cobra.NoArgs, + RunE: listQueries, + } +) + +func init() { + queryCmd.AddCommand(queryListCmd) +} + +func queryTable(queryData []api.Query) (out [][]string) { + for _, query := range queryData { + out = append(out, []string{ + query.QueryID, + query.Owner, + query.LastUpdateTime, + query.LastUpdateUser, + }) + } + + // order by ID + sort.Slice(out, func(i, j int) bool { + return out[i][0] < out[j][0] + }) + + return +} + +func listQueries(_ *cobra.Command, args []string) error { + cli.Log.Debugw("listing queries") + + cli.StartProgress(" Retrieving queries...") + queryResponse, err := cli.LwApi.V2.Query.List() + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to list queries") + } + + if cli.JSONOutput() { + return cli.OutputJSON(queryResponse.Data) + } + if len(queryResponse.Data) == 0 { + cli.OutputHuman("There were no queries found.") + return nil + } + cli.OutputHuman( + renderSimpleTable( + []string{"Query ID", "Owner", "Last Update Time", "Last Update User"}, + queryTable(queryResponse.Data), + ), + ) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_preview.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_preview.go new file mode 100644 index 000000000..1f7309904 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_preview.go @@ -0,0 +1,117 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/lwtime" +) + +var ( + queryPreviewSourceCmd = &cobra.Command{ + Use: "preview-source ", + Short: "Preview Lacework query datasource", + Long: `Preview Lacework query datasource.`, + Args: cobra.ExactArgs(1), + RunE: previewQuerySource, + } + queryPreviewSourceTemplate = `CLIAdhocPreview { source { %s } return distinct { %s } }` +) + +func init() { + queryCmd.AddCommand(queryPreviewSourceCmd) +} + +func previewQuerySource(_ *cobra.Command, args []string) error { + cli.Log.Debugw("retrieving datasource", "id", args[0]) + + cli.StartProgress(" Retrieving datasource...") + datasourceResponse, err := cli.LwApi.V2.Datasources.Get(args[0]) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "unable to retrieve datasource") + } + + // build returns list from datasource fields + var returns []string + for _, ret := range datasourceResponse.Data.ResultSchema { + returns = append(returns, ret.Name) + } + if len(returns) == 0 { + return errors.New("unable to parse datasource schema") + } + + // initialize limit + limit := 1 + + // initialize query + executeQuery := api.ExecuteQueryRequest{ + Query: api.ExecuteQuery{ + QueryText: fmt.Sprintf( + queryPreviewSourceTemplate, args[0], strings.Join(returns, ",")), + }, + Options: api.ExecuteQueryOptions{Limit: &limit}, + } + + // initialize time attempts + timeAttempts := []map[string]string{ + {"start": "-24h", "end": "now"}, + {"start": "-7d", "end": "-24h"}, + {"start": "-30d", "end": "-7d"}, + } + + for _, timeAttempt := range timeAttempts { + start, _ := lwtime.ParseRelative(timeAttempt["start"]) + end, _ := lwtime.ParseRelative(timeAttempt["end"]) + + executeQuery.Arguments = []api.ExecuteQueryArgument{ + { + Name: api.QueryStartTimeRange, + Value: start.UTC().Format(lwtime.RFC3339Milli), + }, + { + Name: api.QueryEndTimeRange, + Value: end.UTC().Format(lwtime.RFC3339Milli), + }, + } + + // execute query + cli.Log.Debugw("running query", "query", executeQuery.Query.QueryText) + cli.StartProgress(" Executing preview query...") + response, err := cli.LwApi.V2.Query.Execute(executeQuery) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to preview datasource") + } + + if len(response.Data) == 0 { + continue + } + return cli.OutputJSON(response.Data[0]) + } + cli.OutputHuman("No results found for datasource\n") + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_show.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_show.go new file mode 100644 index 000000000..7528002df --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_show.go @@ -0,0 +1,81 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + // queryShowCmd represents the lql show command + queryShowCmd = &cobra.Command{ + Use: "show ", + Short: "Show a query", + Long: `Show a query in your Lacework account.`, + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, _ []string) error { + b, err := cmd.Flags().GetBool("yaml") + if err != nil { + return errors.Wrap(err, "unable to parse --yaml flag") + } + if b { + cli.EnableYAMLOutput() + } + return nil + }, + RunE: showQuery, + } +) + +func init() { + queryCmd.AddCommand(queryShowCmd) + + queryShowCmd.Flags().Bool( + "yaml", false, "output query in YAML format", + ) +} + +func showQuery(_ *cobra.Command, args []string) error { + cli.Log.Debugw("retrieving query", "id", args[0]) + + cli.StartProgress("Retrieving query...") + queryResponse, err := cli.LwApi.V2.Query.Get(args[0]) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "unable to show query") + } + + if cli.JSONOutput() { + return cli.OutputJSON(queryResponse.Data) + } + + if cli.YAMLOutput() { + return cli.OutputYAML(&api.NewQuery{ + QueryID: queryResponse.Data.QueryID, + QueryText: queryResponse.Data.QueryText, + }) + } + + cli.OutputHuman(queryResponse.Data.QueryText) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_sources.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_sources.go new file mode 100644 index 000000000..5f7c2070b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_sources.go @@ -0,0 +1,195 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sort" + + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + queryListSourcesCmd = &cobra.Command{ + Aliases: []string{"sources"}, + Use: "list-sources", + Short: "List Lacework query datasources", + Long: `List Lacework query datasources.`, + Args: cobra.NoArgs, + RunE: listQuerySources, + } + + queryShowSourceCmd = &cobra.Command{ + Aliases: []string{"describe"}, + Use: "show-source ", + Short: "Show Lacework query datasource", + Long: `Show Lacework query datasource.`, + Args: cobra.ExactArgs(1), + RunE: showQuerySource, + } +) + +func init() { + queryCmd.AddCommand(queryListSourcesCmd) + queryCmd.AddCommand(queryShowSourceCmd) +} + +func querySourcesTable(datasources []api.Datasource) (out [][]string) { + var preOut [][]string + for _, source := range datasources { + preOut = append(preOut, []string{source.Name, source.Description}) + } + + // order by Name + sort.Slice(preOut, func(i, j int) bool { + return preOut[i][0] < preOut[j][0] + }) + + // condence output since datasources can be really long, + // how long? you ask, as of today, we have over 150 characters + for _, source := range preOut { + out = append(out, []string{ + fmt.Sprintf("%s\n%s", source[0], source[1]), + }) + } + return +} + +func listQuerySources(_ *cobra.Command, args []string) error { + cli.Log.Debugw("retrieving LQL datasources") + lqlSourcesUnableMsg := "unable to retrieve LQL datasources" + datasourcesResponse, err := cli.LwApi.V2.Datasources.List() + + if err != nil { + return errors.Wrap(err, lqlSourcesUnableMsg) + } + if cli.JSONOutput() { + return cli.OutputJSON(datasourcesResponse.Data) + } + if len(datasourcesResponse.Data) == 0 { + return yikes(lqlSourcesUnableMsg) + } + + cli.OutputHuman( + renderCustomTable([]string{"Datasource"}, + querySourcesTable(datasourcesResponse.Data), + tableFunc(func(t *tablewriter.Table) { + t.SetAlignment(tablewriter.ALIGN_LEFT) + t.SetColWidth(120) + t.SetAutoWrapText(true) + t.SetRowLine(true) + t.SetBorder(false) + t.SetReflowDuringAutoWrap(false) + }), + ), + ) + + cli.OutputHuman( + "\nUse 'lacework query show-source ' to show details about the datasource.\n", + ) + return nil +} + +func getShowQuerySourceTable(resultSchema []api.DatasourceSchema) (out [][]string) { + for _, schemaItem := range resultSchema { + out = append(out, []string{ + schemaItem.Name, + schemaItem.DataType, + schemaItem.Description, + }) + } + return +} + +func getShowQuerySourceRelationshipsTable(relationships []api.DatasourceRelationship) (out [][]string) { + for _, relationship := range relationships { + out = append(out, []string{ + relationship.Name, + relationship.From, + relationship.To, + relationship.ToCardinality, + relationship.Description, + }) + } + return +} + +func showQuerySource(_ *cobra.Command, args []string) error { + cli.Log.Infow("retrieving datasource", "id", args[0]) + + cli.StartProgress(" Retrieving datasource...") + datasourceResponse, err := cli.LwApi.V2.Datasources.Get(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to show datasource") + } + + if cli.JSONOutput() { + return cli.OutputJSON(datasourceResponse.Data) + } + cli.OutputHuman( + renderOneLineCustomTable("Datasource", + datasourceResponse.Data.Name, + tableFunc(func(t *tablewriter.Table) { + t.SetAlignment(tablewriter.ALIGN_LEFT) + t.SetColWidth(120) + t.SetBorder(false) + }), + ), + ) + cli.OutputHuman("\n") + cli.OutputHuman(renderOneLineCustomTable("DESCRIPTION", + datasourceResponse.Data.Description, + tableFunc(func(t *tablewriter.Table) { + t.SetAlignment(tablewriter.ALIGN_LEFT) + t.SetColWidth(120) + t.SetBorder(false) + t.SetAutoWrapText(true) + }), + )) + cli.OutputHuman("\n") + cli.OutputHuman( + renderSimpleTable( + []string{"Field Name", "Data Type", "Description"}, + getShowQuerySourceTable(datasourceResponse.Data.ResultSchema), + ), + ) + // if source relationships exist + if len(datasourceResponse.Data.SourceRelationships) > 0 { + cli.OutputHuman("\n") + cli.OutputHuman( + renderSimpleTable( + []string{"Relationship Name", "From", "To", "Cardinality", "Description"}, + getShowQuerySourceRelationshipsTable(datasourceResponse.Data.SourceRelationships), + ), + ) + } + // breadcrumb + cli.OutputHuman( + fmt.Sprintf( + "\nUse 'lacework query preview-source %s' to see an actual result from the datasource.\n", + datasourceResponse.Data.Name, + ), + ) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_update.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_update.go new file mode 100644 index 000000000..e593776ae --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_update.go @@ -0,0 +1,149 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + + "github.com/AlecAivazis/survey/v2" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" + + "github.com/lacework/go-sdk/api" +) + +var ( + // queryUpdateCmd represents the lql update command + queryUpdateCmd = &cobra.Command{ + Use: "update [query_id]", + Short: "Update a query", + Args: cobra.RangeArgs(0, 1), + Long: ` +There are multiple ways you can update a query: + + * Typing the query into your default editor (via $EDITOR) + * Passing a query ID to load it into your default editor + * From a local file on disk using the flag '--file' + * From a URL using the flag '--url' + +There are also multiple formats you can use to define a query: + + * Javascript Object Notation (JSON) + * YAML Ain't Markup Language (YAML) + +To launch your default editor and update a query. + + lacework query update +`, + RunE: updateQuery, + } +) + +func init() { + // add sub-commands to the lql command + queryCmd.AddCommand(queryUpdateCmd) + + setQuerySourceFlags(queryUpdateCmd) + + if cli.isLCLInstalled() { + queryUpdateCmd.Flags().StringVarP( + &queryCmdState.CURVFromLibrary, + "library", "l", "", + "update query from Lacework Content Library", + ) + } +} + +func updateQuery(cmd *cobra.Command, args []string) error { + msg := "unable to update query" + + var ( + queryString string + err error + ) + + if len(args) != 0 { + // query id via argument + cli.StartProgress("Retrieving query...") + queryRes, err := cli.LwApi.V2.Query.Get(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to load query from your account") + } + + queryYaml, err := yaml.Marshal(&api.NewQuery{ + QueryID: queryRes.Data.QueryID, + QueryText: queryRes.Data.QueryText, + }) + if err != nil { + return errors.Wrap(err, msg) + } + + prompt := &survey.Editor{ + Message: fmt.Sprintf("Update query %s", args[0]), + Default: string(queryYaml), + HideDefault: true, + AppendDefault: true, + FileName: "query*.yaml", + } + var queryStr string + err = survey.AskOne(prompt, &queryStr) + if err != nil { + return errors.Wrap(err, msg) + } + + queryString = queryStr + } else { + // input query + queryString, err = inputQuery(cmd) + if err != nil { + return errors.Wrap(err, msg) + } + } + + // parse query + newQuery, err := api.ParseNewQuery(queryString) + if err != nil { + return errors.Wrap(queryErrorCrumbs(queryString), msg) + } + + // avoid letting the user change the query id + if len(args) != 0 && newQuery.QueryID != args[0] { + return errors.New("changes to query ID not supported") + } + + // update query + cli.Log.Debugw("updating query", "query", queryString) + cli.StartProgress(" Updating query...") + update, err := cli.LwApi.V2.Query.Update(newQuery.QueryID, api.UpdateQuery{ + QueryText: newQuery.QueryText, + }) + cli.StopProgress() + + // output + if err != nil { + return errors.Wrap(err, msg) + } + if cli.JSONOutput() { + return cli.OutputJSON(update.Data) + } + cli.OutputHuman("The query %s was updated.\n", update.Data.QueryID) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_validate.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_validate.go new file mode 100644 index 000000000..1b5a8000e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_validate.go @@ -0,0 +1,108 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +const ( + lqlValidateUnableMsg string = "unable to validate query" +) + +var ( + // queryValidateCmd represents the lql validate command + queryValidateCmd = &cobra.Command{ + Use: "validate", + Short: "Validate a query", + Long: `Use this command to validate a single LQL query before creating it. + +There are multiple ways you can validate a query: + + * Typing the query into your default editor (via $EDITOR) + * From a local file on disk using the flag '--file' + * From a URL using the flag '--url' + +There are also multiple formats you can use to define a query: + + * Javascript Object Notation (JSON) + * YAML Ain't Markup Language (YAML) + +To launch your default editor and validate a query. + + lacework query validate +`, + Args: cobra.NoArgs, + RunE: validateQuery, + } +) + +func init() { + queryCmd.AddCommand(queryValidateCmd) + + setQuerySourceFlags(queryValidateCmd) + + if cli.isLCLInstalled() { + queryValidateCmd.Flags().StringVarP( + &queryCmdState.CURVFromLibrary, + "library", "l", "", + "validate query from Lacework Content Library", + ) + } +} + +func validateQuery(cmd *cobra.Command, args []string) error { + // input query + queryString, err := inputQuery(cmd) + if err != nil { + return errors.Wrap(err, lqlValidateUnableMsg) + } + // parse query + newQuery, err := api.ParseNewQuery(queryString) + if err != nil { + return errors.Wrap(queryErrorCrumbs(queryString), lqlValidateUnableMsg) + } + + cli.Log.Debugw("validating query", "query", queryString) + + return validateQueryAndOutput(newQuery) +} + +func validateQueryAndOutput(nq api.NewQuery) error { + vq := api.ValidateQuery{ + QueryText: nq.QueryText, + } + + cli.StartProgress(" Validating query...") + validate, err := cli.LwApi.V2.Query.Validate(vq) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, lqlValidateUnableMsg) + } + + if cli.JSONOutput() { + return cli.OutputJSON(validate.Data) + } + + cli.OutputHuman("Query validated successfully. Time for %s\n", randomEmoji()) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/migration.go b/vendor/github.com/lacework/go-sdk/cli/cmd/migration.go new file mode 100644 index 000000000..e158b3607 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/migration.go @@ -0,0 +1,179 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/spf13/viper" + + "github.com/lacework/go-sdk/internal/cache" + "github.com/lacework/go-sdk/internal/file" + "github.com/lacework/go-sdk/lwconfig" +) + +// The name of the directory we will store backups of configuration files before migrating them +const ConfigBackupDir = "cfg_backups" + +// Migrations executes automatic configuration migrations, +// if a configuration file does not exist, it will only update +// the CLI state to the appropriate parameters +func (c *cliState) Migrations() (err error) { + c.Log.Debugw("executing v2 migration") + c.Event.Feature = featMigrateConfigV2 + c.CfgVersion = 0 + + defer func() { + if err == nil { + c.SendHoneyvent() + } else { + err = errors.Wrap(err, "during v2 migration") + } + + // update global honeyvent with updated state + c.Event.Account = c.Account + c.Event.Subaccount = c.Subaccount + c.Event.CfgVersion = c.CfgVersion + }() + + err = c.NewClient() + if err != nil { + return + } + + err = c.VerifySettings() + if err != nil { + return err + } + + resp, err := c.LwApi.V2.OrganizationInfo.Get() + if err != nil { + return err + } + orgInfo := resp.Data[0] + + // set new v2 config version and notify our feature event + c.CfgVersion = 2 + c.Event.AddFeatureField("config_version", c.CfgVersion) + c.Event.AddFeatureField("org_account", orgInfo.OrgAccount) + // NOTE: @afiune this will be a constant pattern below where + // we will update settings and notify the feature event + + if orgInfo.OrgAccount { + // we only need to update the account/sub-account + // if the user has an organizational account + c.Log.Debugw("organizational account detected") + c.Event.AddFeatureField("org_account_url", orgInfo.OrgAccountURL) + + primaryAccount := strings.ToLower(orgInfo.AccountName()) + + // if the user is accessing a sub-account, that is, if the current + // account is different from the primary account name, set it as + // what it is, the sub-account + if primaryAccount != c.Account { + c.Log.Debugw("updating account settings for APIv2", + "old_account", c.Account, + "new_account", primaryAccount, + ) + + // ALLY-541: Frankfurt accounts will have the account as 'account.fra', + // we need to remove the .fra domain to use it as a subaccount + if strings.Contains(c.Account, ".") { + c.Log.Debugw("subaccount needs cleanup", "subaccount", c.Account) + accSplit := strings.Split(c.Account, ".") + c.Account = accSplit[0] + } + + c.Subaccount = c.Account + c.Account = primaryAccount + + c.Event.AddFeatureField("account", c.Account) + c.Event.AddFeatureField("subaccount", c.Subaccount) + + c.Log.Debugw("generating new API client") + err = c.NewClient() + if err != nil { + return err + } + } + } + + // if the configuration file does not exist, most likely the user + // is executing the CLI via env variables or flags, update feature + // field and exit migration + if !file.FileExists(viper.ConfigFileUsed()) { + c.Log.Debugw("config file not found, skipping profile migration") + c.Event.AddFeatureField("config_file", "not_found") + return nil + } + + c.Log.Debugw("config found, migrating profile", "profile", c.Profile) + migratedProfile := lwconfig.Profile{ + Account: c.Account, + Subaccount: c.Subaccount, + ApiKey: c.KeyID, + ApiSecret: c.Secret, + Version: c.CfgVersion, + } + + // create a backup before modifying the user's configuration + bkpPath, err := createConfigurationBackup() + if err != nil { + return err + } + c.Log.Debugw("configuration backup", "path", bkpPath) + c.Event.AddFeatureField("backup_file", path.Base(bkpPath)) + + // store the migrated profile + err = lwconfig.StoreProfileAt(viper.ConfigFileUsed(), c.Profile, migratedProfile) + if err != nil { + return errors.Wrap(err, "unable to store migrated profile") + } + + c.Log.Debugw("configuration migrated successfully") + return nil +} + +func createConfigurationBackup() (string, error) { + profiles, err := lwconfig.LoadProfilesFrom(viper.ConfigFileUsed()) + if err != nil { + return "", err + } + + cacheDir, err := cache.CacheDir() + if err != nil { + return "", err + } + + backupDir := path.Join(cacheDir, ConfigBackupDir) + if err := os.MkdirAll(backupDir, 0755); err != nil { + return "", err + } + + backupCfgPath := path.Join(backupDir, + fmt.Sprintf(".lacework.toml.%s.%s.bkp", + time.Now().Format("20060102150405"), newID()), + ) + return backupCfgPath, lwconfig.StoreAt(backupCfgPath, profiles) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/outputs.go b/vendor/github.com/lacework/go-sdk/cli/cmd/outputs.go new file mode 100644 index 000000000..73b435f89 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/outputs.go @@ -0,0 +1,150 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/csv" + "fmt" + "os" + "strings" + + "github.com/fatih/color" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +// OutputJSON will print out the JSON representation of the provided data +func (c *cliState) OutputJSON(v interface{}) error { + pretty, err := c.JsonF.Marshal(v) + if err != nil { + c.Log.Debugw("unable to pretty print JSON object", "raw", v) + return err + } + fmt.Fprintln(color.Output, string(pretty)) + return nil +} + +// OutputChecklist prints out a message with an icon formatted as a checklist, +// note that all strings should come already colorized +func (c *cliState) OutputChecklist(icon, message string, a ...interface{}) { + c.OutputHuman(fmt.Sprintf(" [%s] %s", icon, message), a...) +} + +// OutputHuman will print out the provided message if the cli state is +// configured to talk to humans, to switch to json format use --json +func (c *cliState) OutputHuman(format string, a ...interface{}) { + if c.HumanOutput() { + if len(a) == 0 { + fmt.Fprint(color.Output, format) + } else { + fmt.Fprintf(color.Output, format, a...) + } + } +} + +// OutputHumanErr will print the msg if human output is enabled to stderr +func (c *cliState) OutputHumanErr(format string, a ...interface{}) { + if c.HumanOutput() { + if len(a) == 0 { + fmt.Fprint(color.Error, format) + } else { + fmt.Fprintf(color.Error, format, a...) + } + } +} + +// OutputJSONString is just like OutputJSON but from a JSON string +func (c *cliState) OutputJSONString(s string) error { + pretty, err := c.FormatJSONString(s) + if err != nil { + return err + } + fmt.Fprintln(color.Output, string(pretty)) + return nil +} + +// FormatJSONString formats a JSON string into a pretty JSON format +func (c *cliState) FormatJSONString(s string) (string, error) { + pretty, err := c.JsonF.Format([]byte(strings.Trim(s, "'"))) + if err != nil { + c.Log.Debugw("unable to pretty print JSON string", "raw", s, "error", err.Error()) + return "", err + } + return string(pretty), nil +} + +// Used to clean CSV inputs prior to rendering +func csvCleanData(input []string) []string { + var data []string + for _, h := range input { + data = append(data, strings.Replace(h, "\n", "", -1)) + } + return data +} + +// Used to produce CSV output +func renderAsCSV(headers []string, data [][]string) (string, error) { + csvOut := &strings.Builder{} + csv := csv.NewWriter(csvOut) + + if len(headers) > 0 { + if err := csv.Write(csvCleanData(headers)); err != nil { + return "", errors.Wrap(err, "Failed to build csv output") + } + } + + for _, record := range data { + if err := csv.Write(csvCleanData(record)); err != nil { + return "", errors.Wrap(err, "Failed to build csv output") + } + } + + // Write any buffered data to the underlying writer (standard output). + csv.Flush() + return csvOut.String(), csv.Error() +} + +// OutputCSV will print out the provided headers/data in CSV format +func (c *cliState) OutputCSV(headers []string, data [][]string) error { + csv, err := renderAsCSV(headers, data) + if err != nil { + return err + } + + fmt.Fprint(os.Stdout, csv) + return nil +} + +func (c *cliState) OutputNonDefaultProfileFlag() string { + if c.Profile != "default" { + return fmt.Sprintf(" --profile %s", c.Profile) + } + return "" +} + +// OutputYAML will print out the YAML representation of the provided data +func (c *cliState) OutputYAML(v interface{}) error { + y, err := yaml.Marshal(v) + if err != nil { + c.Log.Debugw("unable to pretty print YAML object", "raw", v) + return err + } + fmt.Fprint(os.Stdout, string(y)) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/package_manifest.go b/vendor/github.com/lacework/go-sdk/cli/cmd/package_manifest.go new file mode 100644 index 000000000..5fe900f5e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/package_manifest.go @@ -0,0 +1,560 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "os/exec" + "regexp" + "runtime" + "strings" + "syscall" + "time" + + "github.com/pkg/errors" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/file" +) + +var SupportedPackageManagers = []string{"dpkg-query", "rpm"} // @afiune can we support yum and apk? + +type OS struct { + Name string + Version string +} + +var ( + procUAStatusFile = "/proc/1/root/var/lib/ubuntu-advantage/status.json" + osReleaseFile = "/etc/os-release" + sysReleaseFile = "/etc/system-release" + rexNameFromID = regexp.MustCompile(`^ID=(.*)$`) + rexVersionID = regexp.MustCompile(`^VERSION_ID=(.*)$`) +) + +func (c *cliState) GeneratePackageManifest() (*api.VulnerabilitiesPackageManifest, error) { + var ( + err error + start = time.Now() + ) + + defer func() { + c.Event.DurationMs = time.Since(start).Milliseconds() + if err == nil { + // if this function returns an error, most likely, + // the command will send a honeyvent with that error, + // therefore we should duplicate events and only send + // one here if there is NO error + c.SendHoneyvent() + } + }() + + c.Event.Feature = featGenPkgManifest + + manifest := new(api.VulnerabilitiesPackageManifest) + osInfo, err := c.GetOSInfo() + if err != nil { + return manifest, err + } + + if osInfo.Name == "ubuntu" { + // ESM support + if c.IsEsmEnabled() { + osInfo.Version += "esm" + } + } + + c.Event.AddFeatureField("os", osInfo.Name) + c.Event.AddFeatureField("os_ver", osInfo.Version) + + manager, err := c.DetectPackageManager() + if err != nil { + return manifest, err + } + c.Event.AddFeatureField("pkg_manager", manager) + + var managerQuery []byte + switch manager { + case "rpm": + managerQuery, err = exec.Command( + "rpm", "-qa", "--queryformat", "%{NAME},%|EPOCH?{%{EPOCH}}:{0}|:%{VERSION}-%{RELEASE}\n", + ).Output() + if err != nil { + return manifest, errors.Wrap(err, "unable to query packages from package manager") + } + case "dpkg-query": + managerQuery, err = exec.Command( + "dpkg-query", "--show", "--showformat", "${Package},${Version}\n", + ).Output() + if err != nil { + return manifest, errors.Wrap(err, "unable to query packages from package manager") + } + case "yum": + return manifest, errors.New("yum not yet supported") + case "apk": + apkInfo, err := exec.Command("apk", "info").Output() + if err != nil { + return manifest, errors.Wrap(err, "unable to query packages from package manager") + } + apkInfoT := strings.TrimSuffix(string(apkInfo), "\n") + apkInfoArray := strings.Split(apkInfoT, "\n") + + apkInfoWithVersion, err := exec.Command("apk", "info", "-v").Output() + if err != nil { + return manifest, errors.Wrap(err, "unable to query packages from package manager") + } + apkInfoWithVersionT := strings.TrimSuffix(string(apkInfoWithVersion), "\n") + apkInfoWithVersionArray := strings.Split(apkInfoWithVersionT, "\n") + + mq := []string{} + for i, pkg := range apkInfoWithVersionArray { + mq = append(mq, + fmt.Sprintf("%s,%s", + apkInfoArray[i], + strings.Trim( + strings.Replace(pkg, apkInfoArray[i], "", 1), + "-", + ), + ), + ) + } + managerQuery = []byte(strings.Join(mq, "\n")) + default: + return manifest, errors.New( + "this is most likely a mistake on us, please report it to support@lacework.com.", + ) + } + + c.Log.Debugw("package-manager query", "raw", string(managerQuery)) + + // @afiune this is an example of the output from the query we + // send to the local package-manager: + // + // {PkgName},{PkgVersion}\n + // ... + // {PkgName},{PkgVersion}\n + // + // first, trim the last carriage return + managerQueryOut := strings.TrimSuffix(string(managerQuery), "\n") + // then, split by carriage return + for _, pkg := range strings.Split(managerQueryOut, "\n") { + // finally, split by comma to get PackageName and PackageVersion + pkgDetail := strings.Split(pkg, ",") + + // the splitted package detail must be size of 2 elements + if len(pkgDetail) != 2 { + c.Log.Warnw("unable to parse package, expected length=2, skipping", + "raw_pkg_details", pkg, + "split_pkg_details", pkgDetail, + ) + continue + } + + manifest.OsPkgInfoList = append(manifest.OsPkgInfoList, + api.VulnerabilitiesOsPkgInfo{ + Os: osInfo.Name, + OsVer: osInfo.Version, + Pkg: pkgDetail[0], + PkgVer: pkgDetail[1], + }, + ) + } + + c.Event.AddFeatureField("total_manifest_pkgs", len(manifest.OsPkgInfoList)) + c.Log.Debugw("package-manifest", "raw", manifest) + return c.removeInactivePackagesFromManifest(manifest, manager), nil +} + +func (c *cliState) removeInactivePackagesFromManifest( + manifest *api.VulnerabilitiesPackageManifest, manager string, +) *api.VulnerabilitiesPackageManifest { + // Detect Active Kernel + // + // The default behavior of most linux distros is to keep the last N kernel packages + // installed for users that need to fallback in case the new kernel do not boot. + // However, the presence of the package does not mean that kernel is active. + // We must continue to allow the standard kernel package preservation behavior + // without providing false-positives of vulnerabilities that are not active. + // + // We will try to detect the active kernel and remove any other installed-inactive + // kernel from the generated package manifest + activeKernel, detected := c.detectActiveKernel() + c.Event.AddFeatureField("active_kernel", activeKernel) + if !detected { + return manifest + } + + newManifest := new(api.VulnerabilitiesPackageManifest) + for i, pkg := range manifest.OsPkgInfoList { + + switch manager { + case "rpm": + kernelPkgName := "kernel" + pkgVer := removeEpochFromPkgVersion(pkg.PkgVer) + if pkg.Pkg == kernelPkgName && !strings.Contains(activeKernel, pkgVer) { + // this package is NOT the active kernel + c.Log.Warnw("inactive kernel package detected, removing from generated pkg manifest", + "pkg_name", kernelPkgName, + "pkg_version", pkg.PkgVer, + "active_kernel", activeKernel, + ) + c.Event.AddFeatureField( + fmt.Sprintf("kernel_suppressed_%d", i), + fmt.Sprintf("%s-%s", pkg.Pkg, pkg.PkgVer)) + continue + } + case "dpkg-query": + kernelPkgName := "linux-image-" + if strings.Contains(pkg.Pkg, kernelPkgName) { + // this is a kernel package, trim the package name prefix to get the version + kernelVer := strings.TrimPrefix(pkg.Pkg, kernelPkgName) + + if !strings.Contains(activeKernel, kernelVer) { + // this package is NOT the active kernel + c.Log.Warnw("inactive kernel package detected, removing from generated pkg manifest", + "pkg_name", kernelPkgName, + "pkg_version", pkg.PkgVer, + "active_kernel", activeKernel, + ) + c.Event.AddFeatureField( + fmt.Sprintf("kernel_suppressed_%d", i), + fmt.Sprintf("%s-%s", pkg.Pkg, pkg.PkgVer)) + continue + } + } + } + + newManifest.OsPkgInfoList = append(newManifest.OsPkgInfoList, pkg) + } + + if len(manifest.OsPkgInfoList) != len(newManifest.OsPkgInfoList) { + c.Log.Debugw("package-manifest modified", "raw", newManifest) + } + return newManifest +} + +func (c *cliState) detectActiveKernel() (string, bool) { + kernel, err := exec.Command("uname", "-r").Output() + fmt.Println(kernel) + if err != nil { + fmt.Println("nooooooope") + fmt.Println(err) + c.Log.Warnw("unable to detect active kernel", + "cmd", "uname -r", + "error", err, + ) + return "", false + } + return strings.TrimSuffix(string(kernel), "\n"), true +} + +func (c *cliState) IsEsmEnabled() bool { + type uaStatusFile struct { + SchemaVersion string `json:"_schema_version,omitempty"` + Status string `json:"execution_status,omitempty"` + Services []struct { + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + } `json:"services,omitempty"` + } + + if file.FileExists(procUAStatusFile) { + c.Log.Debugw("detecting ubuntu ESM support", "file", procUAStatusFile) + uaStatusBytes, err := os.ReadFile(procUAStatusFile) + if err != nil { + c.Log.Warnw("unable to read UA status file", "error", err) + return false + } + + var uaStatus uaStatusFile + if err = json.Unmarshal(uaStatusBytes, &uaStatus); err != nil { + c.Log.Warnw("unable to unmarshal UA status file", "error", err) + return false + } + + for _, svc := range uaStatus.Services { + if strings.Contains(svc.Name, "esm") && svc.Status == "enabled" { + c.Log.Debug("ESM is enabled") + return true + } + } + + c.Log.Debug("no UA service enabled") + return false + } + + c.Log.Warnw("unable to detect ubuntu ESM support, file not found", "file", procUAStatusFile) + return false +} + +func (c *cliState) GetOSInfo() (*OS, error) { + osInfo := new(OS) + + c.Log.Debugw("detecting operating system information", + "os", runtime.GOOS, + "arch", runtime.GOARCH, + ) + + if file.FileExists(osReleaseFile) { + c.Log.Debugw("parsing os release file", "file", osReleaseFile) + return openOsReleaseFile(osReleaseFile) + } + + if file.FileExists(sysReleaseFile) { + c.Log.Debugw("parsing system release file", "file", sysReleaseFile) + return openSystemReleaseFile(sysReleaseFile) + } + + msg := `unsupported platform + +For more information about supported platforms, visit: + https://docs.lacework.com/host-vulnerability-assessment-overview` + return osInfo, errors.New(msg) +} + +func openSystemReleaseFile(filename string) (*OS, error) { + osInfo := new(OS) + + f, err := os.Open(filename) + + if err != nil { + return osInfo, err + } + + defer f.Close() + + s := bufio.NewScanner(f) + for s.Scan() { + m := strings.Split(s.Text(), " ") + if len(m) > 0 { + osInfo.Name = strings.ToLower(m[0]) + osInfo.Version = strings.ToLower(m[2]) + break + } + } + + return osInfo, err +} + +func openOsReleaseFile(filename string) (*OS, error) { + osInfo := new(OS) + + f, err := os.Open(filename) + if err != nil { + return osInfo, err + } + + defer f.Close() + + s := bufio.NewScanner(f) + for s.Scan() { + if m := rexNameFromID.FindStringSubmatch(s.Text()); m != nil { + osInfo.Name = strings.Trim(m[1], `"`) + } else if m := rexVersionID.FindStringSubmatch(s.Text()); m != nil { + osInfo.Version = strings.Trim(m[1], `"`) + } + } + + return osInfo, err +} + +func (c *cliState) DetectPackageManager() (string, error) { + c.Log.Debugw("detecting package-manager") + + for _, manager := range SupportedPackageManagers { + if c.checkPackageManager(manager) { + c.Log.Debugw("detected", "package-manager", manager) + return manager, nil + } + } + msg := "unable to find supported package managers." + msg = fmt.Sprintf("%s Supported package managers are %s.", + msg, strings.Join(SupportedPackageManagers, ", ")) + return "", errors.New(msg) +} + +func (c *cliState) checkPackageManager(manager string) bool { + var ( + cmd = exec.Command("which", manager) + _, err = cmd.CombinedOutput() + ) + if err != nil { + c.Log.Debugw("error trying to check package-manager", + "cmd", "which", + "package-manager", manager, + "error", err, + ) + if exitError, ok := err.(*exec.ExitError); ok { + waitStatus := exitError.Sys().(syscall.WaitStatus) + return waitStatus.ExitStatus() == 0 + } + c.Log.Warnw("something went wrong with 'which', trying native command") + return c.checkPackageManagerWithNativeCommand(manager) + } + waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus) + return waitStatus.ExitStatus() == 0 +} + +func (c *cliState) checkPackageManagerWithNativeCommand(manager string) bool { + var ( + cmd = exec.Command("command", "-v", manager) + _, err = cmd.CombinedOutput() + ) + if err != nil { + c.Log.Debugw("error trying to check package-manager", + "cmd", "command", + "package-manager", manager, + "error", err, + ) + if exitError, ok := err.(*exec.ExitError); ok { + waitStatus := exitError.Sys().(syscall.WaitStatus) + return waitStatus.ExitStatus() == 0 + } + return false + } + waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus) + return waitStatus.ExitStatus() == 0 +} + +func removeEpochFromPkgVersion(pkgVer string) string { + if strings.Contains(pkgVer, ":") { + pkgVerSplit := strings.Split(pkgVer, ":") + if len(pkgVerSplit) == 2 { + return pkgVerSplit[1] + } + } + + return pkgVer +} + +// split the provided package_manifest into chucks, if the manifest +// is smaller than the provided chunk size, it will return the manifest +// as an array without modifications +func splitPackageManifest( + manifest *api.VulnerabilitiesPackageManifest, chunks int, +) []*api.VulnerabilitiesPackageManifest { + if len(manifest.OsPkgInfoList) <= chunks { + return []*api.VulnerabilitiesPackageManifest{manifest} + } + + var batches []*api.VulnerabilitiesPackageManifest + for i := 0; i < len(manifest.OsPkgInfoList); i += chunks { + batch := manifest.OsPkgInfoList[i:min(i+chunks, len(manifest.OsPkgInfoList))] + cli.Log.Infow("manifest batch", "total_packages", len(batch)) + batches = append(batches, &api.VulnerabilitiesPackageManifest{OsPkgInfoList: batch}) + } + return batches +} + +func min(a, b int) int { + if a <= b { + return a + } + return b +} + +// fan-out a number of package manifests into multiple requests all at once +func fanOutHostScans(manifests ...*api.VulnerabilitiesPackageManifest) ( + api.VulnerabilitySoftwarePackagesResponse, error, +) { + var ( + resCh = make(chan api.VulnerabilitySoftwarePackagesResponse) + errCh = make(chan error) + workers = len(manifests) + fanInRes = api.VulnerabilitySoftwarePackagesResponse{} + ) + + // disallow more than 10 workers which are 10 calls all at once, + // the API has a rate-limit of 10 calls per hour, per access key + if workers > 10 { + return fanInRes, errors.New("limit of packages exceeded") + } + + var ( + err error + start = time.Now() + ) + defer func() { + cli.Event.DurationMs = time.Since(start).Milliseconds() + // avoid duplicating events + if err == nil { + cli.SendHoneyvent() + } + }() + + // ensure that the api client has a valid token + // before creating workers + if !cli.LwApi.ValidAuth() { + _, err = cli.LwApi.GenerateToken() + if err != nil { + return fanInRes, err + } + } + + // for every manifest, create a new worker, that is, spawn + // a new goroutine that will send the manifest to scan + for n, m := range manifests { + if m == nil { + workers-- + continue + } + cli.Log.Infow("spawn worker", "number", n+1) + go cli.triggerHostVulnScan(m, resCh, errCh) + } + + cli.Event.AddFeatureField("workers", workers) + + // lock the main process and read both, the error and response + // channels, if we receive at least one error, we will stop + // processing and bubble up the error to the caller + for processed := 0; processed < workers; processed++ { + select { + case err = <-errCh: + // end processing as soon as we receive the first error + return fanInRes, err + case res := <-resCh: + // processing scan + cli.Log.Infow("processing worker response", "n", processed+1) + cli.Event.AddFeatureField(fmt.Sprintf("worker%d_total_vulns", processed), len(res.Data)) + mergeHostVulnScanPkgManifestResponses(&fanInRes, &res) + } + } + + return fanInRes, nil +} + +func mergeHostVulnScanPkgManifestResponses(to, from *api.VulnerabilitySoftwarePackagesResponse) { + // append vulnerabilities from -> to + to.Data = append(to.Data, from.Data...) +} + +func (c *cliState) triggerHostVulnScan(manifest *api.VulnerabilitiesPackageManifest, + resCh chan<- api.VulnerabilitySoftwarePackagesResponse, + errCh chan<- error, +) { + response, err := c.LwApi.V2.Vulnerabilities.SoftwarePackages.Scan(*manifest) + if err != nil { + errCh <- err + return + } + resCh <- response +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy.go new file mode 100644 index 000000000..45714c80f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/policy.go @@ -0,0 +1,807 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "io" + "net/http" + "os" + "regexp" + "sort" + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/pointer" + "github.com/lacework/go-sdk/lwseverity" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +var ( + policyCmdState = struct { + AlertEnabled bool + Enabled bool + File string + Severity string + Tag string + State *bool + URL string + CUFromLibrary string + CascadeDelete bool + }{} + + policyTableHeaders = []string{ + "Policy ID", + "Severity", + "Title", + "State", + "Alert State", + "Frequency", + "Tags", + } + + // policyCmd represents the policy parent command + policyCmd = &cobra.Command{ + Use: "policy", + Aliases: []string{"policies"}, + Short: "Manage policies", + Long: `Manage policies in your Lacework account. + +Policies add annotated metadata to queries for improving the context of alerts, +reports, and information displayed in the Lacework Console. + +Policies also facilitate the scheduled execution of Lacework queries. + +Queries let you interactively request information from specified +curated datasources. Queries have a defined structure for authoring detections. + +Lacework ships a set of default LQL policies that are available in your account. + +Limitations: + * The maximum number of records that each policy will return is 1000 + * The maximum number of API calls is 120 per hour for on-demand LQL query executions + +To view all the policies in your Lacework account. + + lacework policy ls + +To view more details about a single policy. + + lacework policy show + +To view the LQL query associated with the policy, use the query ID. + + lacework query show + +**Note: LQL syntax may change.** +`, + } + + // policyListCmd represents the policy list command + policyListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all policies", + Long: `List all registered policies in your Lacework account.`, + Args: cobra.NoArgs, + PreRunE: func(_ *cobra.Command, _ []string) error { + if policyCmdState.Severity != "" && + !lwseverity.IsValid(policyCmdState.Severity) { + return errors.Errorf("the severity %s is not valid, use one of %s", + policyCmdState.Severity, lwseverity.ValidSeverities.String(), + ) + } + return nil + }, + RunE: listPolicies, + } + // policyListTagsCmd represents the policy list command + policyListTagsCmd = &cobra.Command{ + Use: "list-tags", + Aliases: []string{"ls"}, + Short: "List policy tags", + Long: `List all tags associated with policies in your Lacework account.`, + Args: cobra.NoArgs, + RunE: listPolicyTags, + } + // policyShowCmd represents the policy show command + policyShowCmd = &cobra.Command{ + Use: "show ", + Aliases: []string{"ls"}, + Short: "Show details about a policy", + Long: `Show details about the provided policy ID.`, + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, _ []string) error { + b, err := cmd.Flags().GetBool("yaml") + if err != nil { + return errors.Wrap(err, "unable to parse --yaml flag") + } + if b { + cli.EnableYAMLOutput() + } + return nil + }, + RunE: showPolicy, + } + policyIDIntRE = regexp.MustCompile(`^(.*-)(\d+)$`) +) + +func init() { + // add the policy command + rootCmd.AddCommand(policyCmd) + + // add sub-commands to the policy command + policyCmd.AddCommand(policyListCmd) + policyCmd.AddCommand(policyListTagsCmd) + policyCmd.AddCommand(policyShowCmd) + + // Lacework Content Library + if cli.isLCLInstalled() { + policyCreateCmd.Flags().StringVarP( + &policyCmdState.CUFromLibrary, + "library", "l", "", + "create policy from Lacework Content Library", + ) + policyUpdateCmd.Flags().StringVarP( + &policyCmdState.CUFromLibrary, + "library", "l", "", + "update policy from Lacework Content Library", + ) + } + + // policy list specific flags + policyListCmd.Flags().StringVar( + &policyCmdState.Severity, + "severity", "", + fmt.Sprintf("filter policies by severity threshold (%s)", + strings.Join(api.ValidPolicySeverities, ", ")), + ) + policyListCmd.Flags().BoolVar( + &policyCmdState.Enabled, + "enabled", false, "only show enabled policies", + ) + policyListCmd.Flags().BoolVar( + &policyCmdState.AlertEnabled, + "alert_enabled", false, "only show alert_enabled policies", + ) + policyListCmd.Flags().StringVar( + &policyCmdState.Tag, + "tag", "", "only show policies with the specified tag", + ) + // policy show specific flags + policyShowCmd.Flags().Bool( + "yaml", false, "output query in YAML format", + ) +} + +func setPolicySourceFlags(cmds ...*cobra.Command) { + for _, cmd := range cmds { + if cmd == nil { + return + } + action := strings.Split(cmd.Use, " ")[0] + + // file flag to specify a policy from disk + cmd.Flags().StringVarP( + &policyCmdState.File, + "file", "f", "", + fmt.Sprintf("path to a policy to %s", action), + ) + /* repo flag to specify a policy from repo + cmd.Flags().BoolVarP( + &policyCmdState.Repo, + "repo", "r", false, + fmt.Sprintf("id of a policy to %s via active repo", action), + )*/ + // url flag to specify a policy from url + cmd.Flags().StringVarP( + &policyCmdState.URL, + "url", "u", "", + fmt.Sprintf("url to a policy to %s", action), + ) + } +} + +// for commands that take a policy as input +func inputPolicy(cmd *cobra.Command, args ...string) (string, error) { + // if running via library (CU) + if policyCmdState.CUFromLibrary != "" { + return inputPolicyFromLibrary(policyCmdState.CUFromLibrary) + } + // if running via file + if policyCmdState.File != "" { + return inputPolicyFromFile(policyCmdState.File) + } + // if running via URL + if policyCmdState.URL != "" { + return inputPolicyFromURL(policyCmdState.URL) + } + stat, err := os.Stdin.Stat() + if err != nil { + cli.Log.Debugw("error retrieving stdin mode", "error", err.Error()) + } else if (stat.Mode() & os.ModeCharDevice) == 0 { + bytes, err := io.ReadAll(os.Stdin) + return string(bytes), err + } + // if running via editor + action := strings.Split(cmd.Use, " ")[0] + + if action == "create" { + return inputPolicyFromEditor(action, "") + } + + policyYaml, err := fetchExistingPolicy(args) + if err != nil { + return "", err + } + return inputPolicyFromEditor(action, policyYaml) + +} + +func fetchExistingPolicy(args []string) (string, error) { + var policyID string + + if len(args) > 0 && len(args[0]) > 0 { + policyID = args[0] + } else { + if err := promptSetPolicyID(&policyID); err != nil { + return "", err + } + } + + cli.StartProgress(fmt.Sprintf("Retrieving policy '%s'...", policyID)) + policy, err := cli.LwApi.V2.Policy.Get(policyID) + cli.StopProgress() + if err != nil { + return "", errors.Wrap(err, fmt.Sprintf("unable to retrieve %s", policyID)) + } + + policyYaml, err := yaml.Marshal(policy.Data) + if err != nil { + return "", errors.Wrap(err, fmt.Sprintf("unable to yaml marshall %s", policyID)) + } + return string(policyYaml), nil +} + +func inputPolicyFromLibrary(id string) (string, error) { + var ( + lcl *LaceworkContentLibrary + err error + ) + + if lcl, err = cli.LoadLCL(); err != nil { + return "", err + } + return lcl.GetPolicy(id) +} + +func inputPolicyFromFile(filePath string) (string, error) { + fileData, err := os.ReadFile(filePath) + + if err != nil { + return "", errors.Wrap(err, "unable to read file") + } + + return string(fileData), nil +} + +func inputPolicyFromURL(url string) (policy string, err error) { + msg := "unable to access URL" + + response, err := http.Get(url) + if err != nil { + err = errors.Wrap(err, msg) + return + } + defer response.Body.Close() + + if response.StatusCode != 200 { + err = errors.Wrap(errors.New(response.Status), msg) + return + } + + body, err := io.ReadAll(response.Body) + if err != nil { + err = errors.Wrap(err, msg) + return + } + policy = string(body) + return +} + +func inputPolicyFromEditor(action string, policyYaml string) (policy string, err error) { + prompt := &survey.Editor{ + Message: fmt.Sprintf("Use the editor to %s your policy", action), + FileName: "policy*.yaml", + HideDefault: true, + AppendDefault: true, + Default: policyYaml, + } + + err = survey.AskOne(prompt, &policy) + return +} + +func promptSetPolicyID(policyID *string) error { + cli.StartProgress("Retrieving policies...") + policiesResponse, err := cli.LwApi.V2.Policy.List() + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to retrieve policies to select") + } + + var policyIds []string + for _, policy := range policiesResponse.Data { + policyIds = append(policyIds, policy.PolicyID) + } + + return survey.AskOne(&survey.Select{ + Message: "Select policy to update:", + Options: policyIds, + }, policyID) +} + +func promptSetPolicyIDs() ([]string, error) { + cli.StartProgress("Retrieving policies...") + policiesResponse, err := cli.LwApi.V2.Policy.List() + cli.StopProgress() + if err != nil { + return nil, errors.Wrap(err, "unable to retrieve policies to select") + } + + var policyIDs []string + for _, policy := range policiesResponse.Data { + // exclude manual PolicyTypes and exclude policies whose state matches the new state + // eg. do not show already enabled policyIDs when running 'lacework policy enable' + if policy.PolicyType != api.PolicyTypeManual.String() { + if pointer.CompareBoolPtr(policyCmdState.State, policy.Enabled) { + continue + } + policyIDs = append(policyIDs, policy.PolicyID) + } + } + + var response []string + err = survey.AskOne(&survey.MultiSelect{ + Message: "Select which policy ids to update:", + Options: policyIDs, + }, &response) + + if err != nil { + return nil, err + } + + return response, nil +} + +func sortPolicyTable(out [][]string, policyIDIndex int) { + // order by ID (special handling for policy ID numbers) + sort.Slice(out, func(i, j int) bool { + iMatch := policyIDIntRE.FindStringSubmatch(out[i][policyIDIndex]) + jMatch := policyIDIntRE.FindStringSubmatch(out[j][policyIDIndex]) + // both regexes must match + // both regexes must have proper lengths since we'll be using... + // ...direct access from here on out + if iMatch == nil || jMatch == nil || len(iMatch) != 3 || len(jMatch) != 3 { + return out[i][policyIDIndex] < out[j][policyIDIndex] + } + // if string portions aren't the same + if iMatch[1] != jMatch[1] { + return out[i][policyIDIndex] < out[j][policyIDIndex] + } + // if string portions are the same; compare based on ints + // no error checking needed for Atoi since use regexp \d+ + iNum, _ := strconv.Atoi(iMatch[2]) + jNum, _ := strconv.Atoi(jMatch[2]) + return iNum < jNum + }) +} + +func policyTable(policies []api.Policy) (out [][]string) { + for _, policy := range policies { + state := "Disabled" + if policy.Enabled { + state = "Enabled" + } + alertState := "Disabled" + if policy.AlertEnabled { + alertState = "Enabled" + } + out = append(out, []string{ + policy.PolicyID, + policy.Severity, + policy.Title, + state, + alertState, + policy.EvalFrequency, + strings.Join(policy.Tags, ", "), + }) + } + sortPolicyTable(out, 0) + + return +} + +func filterPolicies(policies []api.Policy) []api.Policy { + newPolicies := []api.Policy{} + + for _, policy := range policies { + // filter severity if desired + if lwseverity.ShouldFilter(policy.Severity, policyCmdState.Severity) { + continue + } + // filter enabled=false if requesting "enabled-only" + if policyCmdState.Enabled && !policy.Enabled { + continue + } + // filter alert_enabled=false if requesting "alert_enabled-only" + if policyCmdState.AlertEnabled && !policy.AlertEnabled { + continue + } + // filter tag + if policyCmdState.Tag != "" && !policy.HasTag(policyCmdState.Tag) { + continue + } + newPolicies = append(newPolicies, policy) + } + return newPolicies +} + +func listPolicies(_ *cobra.Command, _ []string) error { + cli.Log.Info("listing policies") + cli.StartProgress("Retrieving policies...") + policiesResponse, err := cli.LwApi.V2.Policy.List() + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to list policies") + } + + cli.Log.Infow("total policies", "count", len(policiesResponse.Data)) + policies := filterPolicies(policiesResponse.Data) + if cli.JSONOutput() { + return cli.OutputJSON(policies) + } + + if len(policies) == 0 { + cli.OutputHuman("There were no policies found.") + } else { + cli.OutputHuman(renderCustomTable(policyTableHeaders, policyTable(policies), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetRowLine(true) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(true) + }), + )) + + if policyCmdState.Tag == "" { + cli.OutputHuman( + "\nTry using '--tag ' to only show policies with the specified tag.\n", + ) + } else if policyCmdState.Severity == "" { + cli.OutputHuman( + "\nTry using '--severity ' to filter policies by severity threshold.\n", + ) + } + } + return nil +} + +func showPolicy(_ *cobra.Command, args []string) error { + var ( + msg string = "unable to show policy" + policyResponse api.PolicyResponse + err error + ) + + cli.Log.Infow("retrieving policy", "id", args[0]) + cli.StartProgress("Retrieving policy...") + policyResponse, err = cli.LwApi.V2.Policy.Get(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, msg) + } + + if cli.JSONOutput() { + return cli.OutputJSON(policyResponse.Data) + } + + if cli.YAMLOutput() { + return cli.OutputYAML(&api.Policy{ + PolicyID: policyResponse.Data.PolicyID, + PolicyType: policyResponse.Data.PolicyType, + QueryID: policyResponse.Data.QueryID, + Title: policyResponse.Data.Title, + Enabled: policyResponse.Data.Enabled, + Description: policyResponse.Data.Description, + Remediation: policyResponse.Data.Remediation, + Severity: policyResponse.Data.Severity, + Limit: policyResponse.Data.Limit, + EvalFrequency: policyResponse.Data.EvalFrequency, + AlertEnabled: policyResponse.Data.AlertEnabled, + AlertProfile: policyResponse.Data.AlertProfile, + Tags: policyResponse.Data.Tags, + }) + } + + cli.OutputHuman(renderSimpleTable( + policyTableHeaders, policyTable([]api.Policy{policyResponse.Data}), + )) + cli.OutputHuman("\n") + cli.OutputHuman(buildPolicyDetailsTable(policyResponse.Data)) + cli.OutputHuman("\n") + cli.OutputHuman(renderOneLineCustomTable("DESCRIPTION", + policyResponse.Data.Description, + tableFunc(func(t *tablewriter.Table) { + t.SetAlignment(tablewriter.ALIGN_LEFT) + t.SetColWidth(120) + t.SetBorder(false) + t.SetAutoWrapText(true) + }), + )) + cli.OutputHuman("\n") + cli.OutputHuman(renderOneLineCustomTable("REMEDIATION", + policyResponse.Data.Remediation, + tableFunc(func(t *tablewriter.Table) { + t.SetAlignment(tablewriter.ALIGN_LEFT) + t.SetColWidth(120) + t.SetBorder(false) + t.SetAutoWrapText(true) + t.SetReflowDuringAutoWrap(false) + }), + )) + cli.OutputHuman("\n") + + if policyResponse.Data.QueryID != "" { + cli.StartProgress("Retrieving query...") + queryResponse, err := cli.LwApi.V2.Query.Get(policyResponse.Data.QueryID) + cli.StopProgress() + if err != nil { + // something went wrong trying to fetch the LQL query, since this is not + // the main purpose of this command, we don't error out but instead, log + // the error and show breadcrumbs to manually fetch the query + cli.Log.Warnw("unable to get query", "error", err) + cli.OutputHuman( + fmt.Sprintf( + "\nUse 'lacework query show %s' to see the query used by this policy.\n", + policyResponse.Data.QueryID, + ), + ) + } + // we know we are in human-readable format + cli.OutputHuman(renderOneLineCustomTable("QUERY TEXT", + queryResponse.Data.QueryText, + tableFunc(func(t *tablewriter.Table) { + t.SetAlignment(tablewriter.ALIGN_LEFT) + t.SetColWidth(120) + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + )) + cli.OutputHuman("\n") + } + return nil +} + +func buildPolicyDetailsTable(policy api.Policy) string { + details := [][]string{ + {"QUERY ID", policy.QueryID}, + {"POLICY TYPE", policy.PolicyType}, + {"LIMIT", fmt.Sprintf("%d", policy.Limit)}, + {"ALERT PROFILE", policy.AlertProfile}, + {"TAGS", strings.Join(policy.Tags, "\n")}, + {"OWNER", policy.Owner}, + {"UPDATED AT", policy.LastUpdateTime}, + {"UPDATED BY", policy.LastUpdateUser}, + {"EVALUATION FREQUENCY", policy.EvalFrequency}, + } + // Append VALID EXCEPTION CONSTRAINTS to the table + // Add "None" when ExceptionConfiguration is empty + exceptionConstraints := strings.Join( + getPolicyExceptionConstraintsSlice(policy.ExceptionConfiguration), ", ") + if exceptionConstraints == "" { + exceptionConstraints = "None" + } + entry := []string{"VALID EXCEPTION CONSTRAINTS", exceptionConstraints} + details = append(details, entry) + + return renderOneLineCustomTable("POLICY DETAILS", + renderCustomTable([]string{}, details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ) +} + +func getPolicyExceptionConstraintsSlice(exceptionConfiguration map[string][]api. + PolicyExceptionConfigurationConstraints) []string { + var exceptionConstraints []string + constraintFields := exceptionConfiguration["constraintFields"] + for _, constraint := range constraintFields { + exceptionConstraints = append(exceptionConstraints, constraint.FieldKey) + } + return exceptionConstraints +} + +func policyTagsTable(pt []string) (out [][]string) { + for _, tag := range pt { + out = append(out, []string{tag}) + } + + // order by Tag + sort.Slice(out, func(i, j int) bool { + return out[i][0] < out[j][0] + }) + + return +} + +func listPolicyTags(_ *cobra.Command, args []string) error { + cli.Log.Debugw("listing policy tags") + + cli.StartProgress("Retrieving policy tags...") + policyTagsResponse, err := cli.LwApi.V2.Policy.ListTags() + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to list policy tags") + } + + if cli.JSONOutput() { + return cli.OutputJSON(policyTagsResponse.Data) + } + if len(policyTagsResponse.Data) == 0 { + cli.OutputHuman("There were no policy tags found.\n") + return nil + } + cli.OutputHuman(renderSimpleTable([]string{"Tag"}, policyTagsTable(policyTagsResponse.Data))) + return nil +} + +func setPoliciesState(_ *cobra.Command, args []string) error { + var ( + state = policyCmdState.State + msg = "enable" + ) + + if !*state { + msg = "disable" + } + + // if tag is provided enable/disable all policies matching the tag + if policyCmdState.Tag != "" { + return setPolicyStateByTag(policyCmdState.Tag, *state) + } + + var ( + bulkPolicies api.BulkUpdatePolicies + err error + ) + + if len(args) > 0 { + for _, policyID := range args { + bulkPolicies = append(bulkPolicies, api.BulkUpdatePolicy{PolicyID: policyID, Enabled: state}) + } + } + + // if no arguments are provided enter prompt + if len(args) == 0 { + bulkPolicies, err = promptSetPoliciesState() + if err != nil { + return err + } + } + + if len(bulkPolicies) == 0 { + cli.OutputHuman("unable to find policies to update\n") + return nil + } + + resp, err := cli.LwApi.V2.Policy.UpdateMany(bulkPolicies) + if err != nil { + return err + } + cli.Log.Debugw("bulk policy updated response:", resp) + cli.OutputHuman("%d policies have been %sd \n", len(bulkPolicies), msg) + + return nil +} + +func promptSetPoliciesState() (api.BulkUpdatePolicies, error) { + var ( + policyIDs []string + err error + ) + + if policyIDs, err = promptSetPolicyIDs(); err != nil { + return nil, err + } + + var bulkPolicies api.BulkUpdatePolicies + for _, policyID := range policyIDs { + state := true + bulkPolicies = append(bulkPolicies, api.BulkUpdatePolicy{PolicyID: policyID, Enabled: &state}) + } + + return bulkPolicies, nil +} + +func setPolicyStateByTag(tag string, policyState bool) error { + msg := "disable" + if policyState { + msg = "enable" + } + + cli.StartProgress("Retrieving policies...") + policyTagsResponse, err := cli.LwApi.V2.Policy.List() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "unable to list policies") + } + + var ( + policiesUpdated []string + matchingPolicies []api.Policy + ) + + for _, p := range policyTagsResponse.Data { + if p.HasTag(tag) && p.Enabled != policyState { + matchingPolicies = append(matchingPolicies, p) + } + } + + if len(matchingPolicies) == 0 { + cli.OutputHuman("No policies found with tag '%s'\n", tag) + return nil + } + + // perform bulk update + var bulkPolicies api.BulkUpdatePolicies + for _, p := range matchingPolicies { + bulkPolicies = append(bulkPolicies, api.BulkUpdatePolicy{ + PolicyID: p.PolicyID, + Enabled: &policyState, + }) + } + + cli.StartProgress(fmt.Sprintf("%sing %d policies...", strings.TrimSuffix(msg, "e"), len(bulkPolicies))) + resp, err := cli.LwApi.V2.Policy.UpdateMany(bulkPolicies) + cli.StopProgress() + + if err != nil { + return errors.Wrapf(err, "failed to complete bulk %s.", msg) + } + + cli.Log.Debugw("bulk policy updated response:", resp) + cli.OutputHuman("%d policies tagged with %q have been %sd\n", len(policiesUpdated), tag, msg) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_create.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_create.go new file mode 100644 index 000000000..172253ce3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_create.go @@ -0,0 +1,147 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + // policyCreateCmd represents the policy create command + policyCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a policy", + Long: `Create a policy. + +A policy is represented in either JSON or YAML format. + +The following attributes are minimally required: + + --- + title: My Policy + enabled: false + policyType: Violation + alertEnabled: false + alertProfile: Alert_Profile_ID.Alert_Template_Name + evalFrequency: Daily + queryId: MyQuery + severity: high + description: My Policy Description + remediation: My Policy Remediation +`, + Args: cobra.NoArgs, + RunE: createPolicy, + } +) + +func init() { + // add sub-commands to the policy command + policyCmd.AddCommand(policyCreateCmd) + + // policy source specific flags + setPolicySourceFlags(policyCreateCmd) +} + +func createQueryFromLibrary(id string) error { + var ( + queryString string + err error + newQuery api.NewQuery + ) + + // input query + queryString, err = inputQueryFromLibrary(id) + if err != nil { + return err + } + + cli.Log.Debugw("creating query", "query", queryString) + + // parse query + newQuery, err = api.ParseNewQuery(queryString) + if err != nil { + return queryErrorCrumbs(queryString) + } + + // create query + _, err = cli.LwApi.V2.Query.Create(newQuery) + return err +} + +func createPolicy(cmd *cobra.Command, _ []string) error { + var ( + msg string = "unable to create policy" + err error + queryExists bool + ) + + // input policy + policyString, err := inputPolicy(cmd) + if err != nil { + return errors.Wrap(err, msg) + } + cli.Log.Debugw("creating policy", "policy", policyString) + + // parse policy + newPolicy, err := api.ParseNewPolicy(policyString) + if err != nil { + return errors.Wrap(err, msg) + } + + // if creating policy from library also create query + if policyCmdState.CUFromLibrary != "" { + cli.StartProgress(" Creating query (then policy)...") + err = createQueryFromLibrary(newPolicy.QueryID) + cli.StopProgress() + + if err != nil { + if queryExists = strings.Contains(err.Error(), "already exists"); !queryExists { + return errors.Wrap(err, "unable to create query") + } + } + } + + // create policy + cli.StartProgress(" Creating policy...") + createResponse, err := cli.LwApi.V2.Policy.Create(newPolicy) + cli.StopProgress() + + // output policy + if err != nil { + return errors.Wrap(err, msg) + } + if cli.JSONOutput() { + return cli.OutputJSON(createResponse.Data) + } + // if human output mode, creating from library, and query exists + if queryExists { + cli.OutputHuman(fmt.Sprintf("The query %s already exists.\n", newPolicy.QueryID)) + } + if policyCmdState.CUFromLibrary != "" { + cli.OutputHuman(fmt.Sprintf("The query %s was created.\n", newPolicy.QueryID)) + } + cli.OutputHuman(fmt.Sprintf("The policy %s was created.\n", createResponse.Data.PolicyID)) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_delete.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_delete.go new file mode 100644 index 000000000..124fc6a24 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_delete.go @@ -0,0 +1,94 @@ +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package cmd + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + // policyDeleteCmd represents the policy delete command + policyDeleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a policy", + Long: `Delete a policy by providing the policy ID. + +Use the command 'lacework policy list' to list the registered policies in +your Lacework account.`, + Args: cobra.ExactArgs(1), + RunE: deletePolicy, + } +) + +func init() { + // add sub-commands to the policy command + policyCmd.AddCommand(policyDeleteCmd) + + policyDeleteCmd.Flags().BoolVar( + &policyCmdState.CascadeDelete, + "cascade", false, "delete policy and its associated query", + ) +} + +func deletePolicy(_ *cobra.Command, args []string) error { + var ( + getResponse api.PolicyResponse + err error + queryID string + ) + + if policyCmdState.CascadeDelete { + cli.Log.Debugw("retrieving policy", "policyID", args[0]) + cli.StartProgress("Retrieving policy...") + getResponse, err = cli.LwApi.V2.Policy.Get(args[0]) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "unable to retrieve policy") + } + queryID = getResponse.Data.QueryID + } + + cli.Log.Debugw("deleting policy", "policyID", args[0]) + cli.StartProgress(" Deleting policy...") + _, err = cli.LwApi.V2.Policy.Delete(args[0]) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "unable to delete policy") + } + cli.OutputHuman( + fmt.Sprintf("The policy %s was deleted.\n", args[0]), + ) + // delete query + if policyCmdState.CascadeDelete { + cli.Log.Debugw("deleting query", "id", queryID) + cli.StartProgress(" Deleting query...") + _, err := cli.LwApi.V2.Query.Delete(queryID) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "unable to delete query") + } + cli.OutputHuman("The query %s was deleted.\n", queryID) + } + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_disable.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_disable.go new file mode 100644 index 000000000..6a1e8c4ec --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_disable.go @@ -0,0 +1,71 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/aws/smithy-go/ptr" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // This is an experimental command. + // policyDisableTagCmd represents the policy disable command + policyDisableTagCmd = &cobra.Command{ + Use: "disable [policy_id...]", + Short: "Disable policies", + Long: `Disable policies by ID or all policies matching a tag. + +To disable a single policy by its ID: + + lacework policy disable lacework-policy-id + +To disable many policies by ID provide a list of policy ids: + + lacework policy disable lacework-policy-id-one lacework-policy-id-two + +To disable all policies for AWS CIS 1.4.0: + + lacework policy disable --tag framework:cis-aws-1-4-0 + +To disable all policies for GCP CIS 1.3.0: + + lacework policy disable --tag framework:cis-gcp-1-3-0 +`, + PreRunE: func(_ *cobra.Command, args []string) error { + if len(args) > 0 && policyCmdState.Tag != "" { + return errors.New("'--tag' flag may not be use in conjunction with 'policy_id' arg") + } + policyCmdState.State = ptr.Bool(false) + return nil + }, + RunE: setPoliciesState, + } +) + +func init() { + // add sub-commands to the policy command + policyCmd.AddCommand(policyDisableTagCmd) + + // policy disable specific flags + policyDisableTagCmd.Flags().StringVar( + &policyCmdState.Tag, + "tag", "", "disable all policies with the specified tag", + ) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_enable.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_enable.go new file mode 100644 index 000000000..cae77b604 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_enable.go @@ -0,0 +1,75 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/aws/smithy-go/ptr" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // policyEnableTagCmd represents the policy enable command + policyEnableTagCmd = &cobra.Command{ + Use: "enable [policy_id...]", + Short: "Enable policies", + Long: `Enable policies by ID or all policies matching a tag. + +To enter the policy enable prompt: + + lacework policy enable + +To enable a single policy by its ID: + + lacework policy enable lacework-policy-id + +To enable many policies by ID provide a list of policy ids: + + lacework policy enable lacework-policy-id-one lacework-policy-id-two + +To enable all policies for AWS CIS 1.4.0: + + lacework policy enable --tag framework:cis-aws-1-4-0 + +To enable all policies for GCP CIS 1.3.0: + + lacework policy enable --tag framework:cis-gcp-1-3-0 + +`, + PreRunE: func(_ *cobra.Command, args []string) error { + if len(args) > 0 && policyCmdState.Tag != "" { + return errors.New("'--tag' flag may not be use in conjunction with 'policy_id' arg") + } + policyCmdState.State = ptr.Bool(true) + return nil + }, + RunE: setPoliciesState, + } +) + +func init() { + // add sub-commands to the policy command + policyCmd.AddCommand(policyEnableTagCmd) + + // policy enable specific flags + policyEnableTagCmd.Flags().StringVar( + &policyCmdState.Tag, + "tag", "", "enable all policies with the specified tag", + ) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_exceptions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_exceptions.go new file mode 100644 index 000000000..0f31255ec --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_exceptions.go @@ -0,0 +1,640 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" +) + +var ( + // policyExceptionCmd represents the policy parent command + policyExceptionCmd = &cobra.Command{ + Use: "policy-exception", + Aliases: []string{"policy-exceptions", "pe", "px"}, + Short: "Manage policy exceptions", + Long: `Manage policy exceptions in your Lacework account. + +To view all the policies in your Lacework account. + + lacework policy list +`, + } + + // policyExceptionListCmd represents the policy exception list command + policyExceptionListCmd = &cobra.Command{ + Use: "list ", + Aliases: []string{"ls"}, + Short: "List all exceptions from a single policy", + Long: `List all of the policy exceptions from the provided policy ID.`, + Args: cobra.ExactArgs(1), + RunE: listPolicyExceptions, + } + + // policyExceptionShowCmd represents the policy exception show command + policyExceptionShowCmd = &cobra.Command{ + Use: "show ", + Aliases: []string{"get"}, + Short: "Show details about a policy exception", + Long: `Show the details of a policy exception.`, + Args: cobra.ExactArgs(2), + RunE: showPolicyException, + } + + // policyExceptionDeleteCmd represents the policy exception delete command + policyExceptionDeleteCmd = &cobra.Command{ + Use: "delete ", + Aliases: []string{"rm"}, + Short: "Delete a policy exception", + Long: `Delete a policy exception. + +To remove a policy exception, run the delete command with policy ID and exception ID arguments: + + lacework policy-exception delete `, + Args: cobra.ExactArgs(2), + RunE: deletePolicyException, + } + + // policyExceptionCreateCmd represents the policy exception create command + policyExceptionCreateCmd = &cobra.Command{ + Use: "create [policy_id]", + Aliases: []string{"rm"}, + Short: "Create a policy exception", + Long: `Create a new policy exception. + +To create a new policy exception, run the command: + + lacework policy-exception create [policy_id] + +If you run the command without providing the policy_id, a +list of policies is displayed in an interactive prompt. +`, + Args: cobra.MaximumNArgs(1), + RunE: createPolicyException, + } +) + +func init() { + // add the policy exception command + rootCmd.AddCommand(policyExceptionCmd) + + // add sub-commands to the policy exception command + policyExceptionCmd.AddCommand(policyExceptionListCmd) + policyExceptionCmd.AddCommand(policyExceptionShowCmd) + policyExceptionCmd.AddCommand(policyExceptionDeleteCmd) + policyExceptionCmd.AddCommand(policyExceptionCreateCmd) +} + +func listPolicyExceptions(_ *cobra.Command, args []string) error { + if len(args) > 0 { + cli.StartProgress(fmt.Sprintf( + "Retrieving policy exceptions from policy ID '%s'...", args[0], + )) + policyExceptionResponse, err := cli.LwApi.V2.Policy.Exceptions.List(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrapf(err, "unable to list policy exceptions for ID %s", args[0]) + } + + if cli.JSONOutput() { + return cli.OutputJSON(policyExceptionResponse.Data) + } + + if len(policyExceptionResponse.Data) == 0 { + cli.OutputHuman("There were no policy exceptions found.\n") + return nil + } + + cli.OutputHuman(renderSimpleTable(policyExceptionTableHeaders, + policyExceptionTable(policyExceptionResponse.Data, args[0])), + ) + } + + return nil +} + +func showPolicyException(_ *cobra.Command, args []string) error { + var policyException api.PolicyExceptionResponse + cli.StartProgress(fmt.Sprintf( + "Fetching policy exception '%s' from policy '%s'...", args[0], args[1], + )) + err := cli.LwApi.V2.Policy.Exceptions.Get(args[0], args[1], &policyException) + cli.StopProgress() + if err != nil { + return errors.Wrapf(err, "unable to fetch policy exception for ID %s", args[0]) + } + + if cli.JSONOutput() { + return cli.OutputJSON(policyException) + } + + cli.OutputHuman(policyExceptionDetailsTable(policyException.Data, args[0])) + return nil +} + +func deletePolicyException(_ *cobra.Command, args []string) error { + cli.StartProgress(fmt.Sprintf( + "Deleting policy exception '%s' from policy '%s'...", args[0], args[1], + )) + err := cli.LwApi.V2.Policy.Exceptions.Delete(args[0], args[1]) + cli.StopProgress() + if err != nil { + return errors.Wrapf(err, "unable to remove policy exception for ID %s", args[0]) + } + + cli.OutputHuman("Policy exception '%s' deleted from policy '%s'\n", args[0], args[1]) + return nil +} + +func createPolicyException(_ *cobra.Command, args []string) error { + res, policyID, err := promptCreatePolicyException(args) + + if err != nil { + return errors.Wrap(err, "unable to create policy exception") + } + + cli.OutputHuman( + "The policy exception '%s' was created for policy '%s' \n", + res.Data.ExceptionID, policyID, + ) + return nil +} + +func promptCreatePolicyException(args []string) (api.PolicyExceptionResponse, string, error) { + var ( + policy api.PolicyResponse + policyList []string + policyID string + err error + ) + + if len(args) > 0 { + policy, err = cli.LwApi.V2.Policy.Get(args[0]) + if err != nil { + return api.PolicyExceptionResponse{}, "", errors.Wrapf(err, "invalid policy ID %s", args[0]) + } + policyID = policy.Data.PolicyID + } else { + cli.StartProgress("Retrieving list of policies...") + policies, err := cli.LwApi.V2.Policy.List() + cli.StopProgress() + if err != nil { + return api.PolicyExceptionResponse{}, "", errors.Wrap(err, "unable to fetch policies") + } + for _, p := range policies.Data { + if p.PolicyType != "Violation" { + policyList = append(policyList, p.PolicyID) + } + } + if err = survey.AskOne(&survey.Select{ + Message: "Policy ID:", + Options: policyList, + }, &policyID); err != nil { + return api.PolicyExceptionResponse{}, "", err + } + policy, err = cli.LwApi.V2.Policy.Get(policyID) + if err != nil { + return api.PolicyExceptionResponse{}, "", errors.Wrapf(err, "invalid policy ID %s", policyID) + } + } + + questions := []*survey.Question{ + { + Name: "description", + Prompt: &survey.Input{Message: "Exception Description: "}, + Validate: survey.Required, + }, + } + + answers := struct { + Description string `json:"description"` + }{} + + err = survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return api.PolicyExceptionResponse{}, "", err + } + + constraints, err := promptAddExceptionConstraints(policy.Data) + if err != nil { + return api.PolicyExceptionResponse{}, "", err + } + + exception := api.PolicyException{Description: answers.Description, Constraints: constraints} + cli.StartProgress("Creating policy exception ...") + response, err := cli.LwApi.V2.Policy.Exceptions.Create(policyID, exception) + + cli.StopProgress() + return response, policyID, err +} + +var policyExceptionTableHeaders = []string{"POLICY ID", "EXCEPTION ID", "DESCRIPTION", "UPDATED AT", "UPDATED BY"} + +func policyExceptionTable(policyException []api.PolicyException, policyID string) (out [][]string) { + for _, exception := range policyException { + out = append(out, []string{ + policyID, + exception.ExceptionID, + exception.Description, + exception.LastUpdateTime, + exception.LastUpdateUser, + }) + } + return +} + +func policyExceptionDetailsTable(policyException api.PolicyException, policyID string) string { + var ( + table strings.Builder + out [][]string + details [][]string + ) + + out = append(out, []string{ + policyID, + policyException.ExceptionID, + policyException.Description, + policyException.LastUpdateTime, + policyException.LastUpdateUser, + }) + + table.WriteString(renderSimpleTable(policyExceptionTableHeaders, out)) + table.WriteString("\n") + + for _, constraint := range policyException.Constraints { + jsonFieldValues, _ := json.Marshal(constraint.FieldValues) + details = append(details, []string{constraint.FieldKey, string(jsonFieldValues)}) + } + + table.WriteString(renderOneLineCustomTable("CONSTRAINTS", + renderSimpleTable([]string{"FIELD KEY", "FIELD VALUES"}, details), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }))) + + return table.String() +} + +func promptAddExceptionConstraints(policy api.Policy) ([]api.PolicyExceptionConstraint, error) { + if len(policy.ExceptionConfiguration) == 0 { + return []api.PolicyExceptionConstraint{}, nil + } + var responses []api.PolicyExceptionConstraint + questions := buildPromptAddExceptionConstraintListQuestions(policy.ExceptionConfiguration["constraintFields"]) + + for _, q := range questions { + addConstraint, err := promptAddConstraint(q.constraint.FieldKey) + if err != nil { + return []api.PolicyExceptionConstraint{}, nil + } + + if !addConstraint { + continue + } + + answer, err := promptAskConstraintsQuestion(q) + + if err != nil { + return []api.PolicyExceptionConstraint{}, err + } + + if answer != nil { + responses = append(responses, *answer) + } + } + + if len(responses) == 0 { + return []api.PolicyExceptionConstraint{}, errors.New("policy exceptions must have at least 1 constraint") + } + + return responses, nil +} + +func promptAskConstraintsQuestion(constraintQuestion PolicyExceptionSurveyQuestion) ( + *api.PolicyExceptionConstraint, error, +) { + var ( + answer *api.PolicyExceptionConstraint + err error + ) + + switch constraintQuestion.constraint.DataType { + case "String": + if constraintQuestion.constraint.MultiValue { + // prompt string list question + answer, err = promptAddExceptionConstraintList( + constraintQuestion.constraint.FieldKey, constraintQuestion.questions, + ) + } else { + // prompt string question + answer, err = promptAddExceptionConstraintString( + constraintQuestion.constraint.FieldKey, constraintQuestion.questions, + ) + } + case "KVTagPair": + if constraintQuestion.constraint.MultiValue { + // workaround for the unique shape of resourceTags data, see GROW-2831 + if constraintQuestion.constraint.FieldKey == "resourceTags" { + answer, err = promptAddExceptionConstraintResourceTags(constraintQuestion.constraint.FieldKey) + } else { + //prompt kv tag list + answer, err = promptAddExceptionConstraintMapList( + constraintQuestion.constraint.FieldKey, constraintQuestion.questions, + ) + } + } else { + //prompt kv tag + mapAnswers, err := promptAddExceptionConstraintMap(constraintQuestion.questions) + if err != nil { + return nil, err + } + return &api.PolicyExceptionConstraint{ + FieldKey: constraintQuestion.constraint.FieldKey, + FieldValues: []any{mapAnswers}, + }, nil + } + } + + if err != nil { + return nil, err + } + + return answer, nil +} + +func buildPromptAddExceptionConstraintListQuestions( + constraints []api.PolicyExceptionConfigurationConstraints, +) []PolicyExceptionSurveyQuestion { + questions := []PolicyExceptionSurveyQuestion{} + + for _, constraint := range constraints { + switch constraint.DataType { + case "String": + if constraint.MultiValue { + questions = append(questions, PolicyExceptionSurveyQuestion{[]*survey.Question{{ + Name: constraint.FieldKey, + Prompt: &survey.Multiline{Message: fmt.Sprintf("%s values:", constraint.FieldKey)}, + Validate: survey.Required, + }}, + constraint, + }) + } else { + questions = append(questions, PolicyExceptionSurveyQuestion{[]*survey.Question{{ + Name: constraint.FieldKey, + Prompt: &survey.Input{Message: fmt.Sprintf("%s value:", constraint.FieldKey)}, + Validate: survey.Required, + }}, + constraint, + }) + } + case "KVTagPair": + if constraint.MultiValue { + questions = append(questions, PolicyExceptionSurveyQuestion{[]*survey.Question{{ + Name: "key", + Prompt: &survey.Input{Message: "key:"}, + Validate: survey.Required, + }, { + Name: "value", + Prompt: &survey.Input{Message: "value:"}, + Validate: survey.Required, + }, + }, + constraint, + }) + } else { + questions = append(questions, PolicyExceptionSurveyQuestion{[]*survey.Question{{ + Name: "key", + Prompt: &survey.Input{Message: "key:"}, + Validate: survey.Required, + }, { + Name: "value", + Prompt: &survey.Input{Message: "value:"}, + Validate: survey.Required, + }, + }, + constraint, + }) + } + } + } + + return questions +} + +type PolicyExceptionSurveyQuestion struct { + questions []*survey.Question + constraint api.PolicyExceptionConfigurationConstraints +} + +func promptAddExceptionConstraintList( + key string, questions []*survey.Question, +) (*api.PolicyExceptionConstraint, error) { + if key == "accountIds" { + return promptAddExceptionConstraintAwsAccountsList() + } + + var ( + values []any + fieldValues string + ) + + err := survey.Ask(questions, &fieldValues) + if err != nil { + return nil, err + } + + for _, v := range strings.Split(fieldValues, "\n") { + values = append(values, v) + } + + return &api.PolicyExceptionConstraint{ + FieldKey: key, + FieldValues: values, + }, nil +} + +func promptAddExceptionConstraintString( + key string, questions []*survey.Question, +) (*api.PolicyExceptionConstraint, error) { + var fieldValue string + + err := survey.Ask(questions, &fieldValue) + if err != nil { + return nil, err + } + + return &api.PolicyExceptionConstraint{ + FieldKey: key, + FieldValues: []any{fieldValue}, + }, nil +} + +func promptAddExceptionConstraintAwsAccountsList() (*api.PolicyExceptionConstraint, error) { + var ( + values []any + fieldValues []string + accountIds []string + ) + + cli.StartProgress("Retrieving AWS accounts...") + accounts, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AwsCfgCloudAccount) + cli.StopProgress() + + if err != nil { + return nil, err + } + + if len(accounts.Data) == 0 { + return nil, errors.New("no aws cloud accounts found") + } + + for _, ca := range accounts.Data { + if caMap, ok := ca.GetData().(map[string]interface{}); ok { + accountIds = append(accountIds, caMap["awsAccountId"].(string)) + } + } + + question := survey.Question{Name: "awsAccounts", + Prompt: &survey.MultiSelect{Message: "Select AWS Accounts:", + Options: array.Unique(accountIds)}, + Validate: survey.MinItems(1)} + + err = survey.Ask([]*survey.Question{&question}, &fieldValues) + if err != nil { + return nil, err + } + + for _, v := range fieldValues { + values = append(values, v) + } + return &api.PolicyExceptionConstraint{ + FieldKey: "accountIds", + FieldValues: values, + }, nil +} + +func promptAddExceptionConstraintMap(mapQuestions []*survey.Question) (constraintMapAnswer, error) { + var mapAnswer constraintMapAnswer + err := survey.Ask(mapQuestions, &mapAnswer, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return constraintMapAnswer{}, err + } + + return mapAnswer, nil +} + +// resourceTags requires special handling, see GROW-2831 +type resourceTagsConstraintAnswer struct { + Key string `json:"key"` + Value string `json:"value"` +} + +// resourceTags requires special handling, see GROW-2831 +func promptAddExceptionConstraintResourceTags(key string) (*api.PolicyExceptionConstraint, error) { + questions := []*survey.Question{{ + Name: "key", + Prompt: &survey.Input{Message: "tag name:"}, + Validate: survey.Required, + }, { + Name: "value", + Prompt: &survey.Input{Message: "values:", Help: "Supply a comma seperated list for multiple"}, + Validate: survey.Required, + }} + + var answer resourceTagsConstraintAnswer + if err := survey.Ask(questions, &answer); err != nil { + return nil, err + } + + finalAnswer := strings.Split(answer.Value, ",") + return &api.PolicyExceptionConstraint{ + FieldKey: key, + FieldValues: []any{ + map[string]interface{}{"key": answer.Key, "value": finalAnswer}, + }, + }, nil + +} + +type constraintMapAnswer struct { + Key string `json:"key"` + Value []string `json:"value"` +} + +func promptAddExceptionConstraintMapList( + key string, mapQuestions []*survey.Question, +) (*api.PolicyExceptionConstraint, error) { + var mapAnswers []any + + res, err := promptAddExceptionConstraintMap(mapQuestions) + if err != nil { + return nil, err + } + mapAnswers = append(mapAnswers, res) + + addTag := false + for { + if err := survey.AskOne(&survey.Confirm{ + Message: fmt.Sprintf("Add another %s constraint?", key), + }, &addTag); err != nil { + return nil, err + } + + if addTag { + res, err := promptAddExceptionConstraintMap(mapQuestions) + if err != nil { + return nil, err + } + mapAnswers = append(mapAnswers, res) + } else { + break + } + } + + return &api.PolicyExceptionConstraint{ + FieldKey: key, + FieldValues: mapAnswers, + }, nil +} + +func promptAddConstraint(key string) (bool, error) { + addConstraint := false + + if err := survey.AskOne(&survey.Confirm{ + Message: fmt.Sprintf("Add constraint %q?", key), + }, &addConstraint); err != nil { + return false, err + } + + return addConstraint, nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_library.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_library.go new file mode 100644 index 000000000..31bfea5c8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_library.go @@ -0,0 +1,552 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +var ( + policyLibraryTableHeaders []string = []string{"Policy ID", "Title", "Query ID", "TAGS"} + + policyListLibraryCmd = &cobra.Command{ + Use: "list-library", + Short: "List policies from library", + Long: `List all LQL policies in your Lacework Content Library.`, + Args: cobra.NoArgs, + RunE: listPoliciesLibrary, + } + policyShowLibraryCmd = &cobra.Command{ + Use: "show-library ", + Short: "Show a policy from library", + Long: `Show a policy in your Lacework Content Library.`, + Args: cobra.ExactArgs(1), + RunE: showPolicyLibrary, + } + policySyncLibraryCmd = &cobra.Command{ + Use: "sync-library", + Short: "Synchronize library policies", + Long: `Synchronize library policies with your Lacework account. + +Requirements: +Specify the --tag flag to select policies and queries. + +Behavior: +1. Policies and queries that exist in the library but not in your account will be created. +2. Policies and queries that exist in both the library and your account will be updated. +3. Nothing will be deleted. + +To view all policies in the library and their associated tags. + + lacework policy list-library`, + Args: cobra.NoArgs, + RunE: syncPolicyLibrary, + } +) + +func init() { + if !cli.isLCLInstalled() { + return + } + + policyCmd.AddCommand(policyListLibraryCmd) + policyListLibraryCmd.Flags().StringVar( + &policyCmdState.Tag, + "tag", "", "only show policies with the specified tag", + ) + + policyCmd.AddCommand(policyShowLibraryCmd) + + policyCmd.AddCommand(policySyncLibraryCmd) + policySyncLibraryCmd.Flags().StringVar( + &policyCmdState.Tag, + "tag", "", "sync policies and queries with the specified tag", + ) +} + +func policyLibraryTable(policies map[string]LCLPolicy) (out [][]string) { + for _, policy := range policies { + out = append(out, []string{ + policy.PolicyID, + policy.Title, + policy.QueryID, + strings.Join(policy.Tags, "\n"), + }) + } + sortPolicyTable(out, 0) + return +} + +func buildPolicyLibraryDetailsTable(policy api.Policy) string { + details := [][]string{ + {"DESCRIPTION", policy.Description}, + {"REMEDIATION", policy.Remediation}, + {"POLICY TYPE", policy.PolicyType}, + {"LIMIT", fmt.Sprintf("%d", policy.Limit)}, + {"ALERT PROFILE", policy.AlertProfile}, + {"EVALUATION FREQUENCY", policy.EvalFrequency}, + } + + return renderOneLineCustomTable("POLICY DETAILS", + renderCustomTable([]string{}, details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ) +} + +func listPoliciesLibrary(_ *cobra.Command, args []string) error { + cli.Log.Debugw("listing policies from library") + + cli.StartProgress("Retrieving policies...") + lcl, err := cli.LoadLCL() + cli.StopProgress() + + var policies map[string]LCLPolicy + if policyCmdState.Tag == "" { + policies = lcl.Policies + } else { + policies = lcl.GetPoliciesByTag(policyCmdState.Tag) + } + + if err != nil { + return errors.Wrap(err, "unable to list policies") + } + if cli.JSONOutput() { + return cli.OutputJSON(policies) + } + if len(policies) == 0 { + cli.OutputHuman("There were no policies found.") + return nil + } + cli.OutputHuman( + renderCustomTable( + policyLibraryTableHeaders, + policyLibraryTable(policies), + tableFunc(func(t *tablewriter.Table) { + t.SetAutoWrapText(false) + t.SetBorder(false) + }), + ), + ) + return nil +} + +func showPolicyLibrary(_ *cobra.Command, args []string) error { + var ( + msg string = "unable to show policy" + policyString string + newPolicy api.NewPolicy + policyResponse api.PolicyResponse + err error + ) + cli.Log.Debugw("retrieving policy", "id", args[0]) + + cli.StartProgress("Retrieving policy...") + // input policy + if policyString, err = inputPolicyFromLibrary(args[0]); err != nil { + cli.StopProgress() + return errors.Wrap(err, msg) + } + // parse policy + newPolicy, err = api.ParseNewPolicy(policyString) + policyResponse.Data = api.Policy{ + PolicyID: newPolicy.PolicyID, + PolicyType: newPolicy.PolicyType, + QueryID: newPolicy.QueryID, + Title: newPolicy.Title, + Enabled: newPolicy.Enabled, + Description: newPolicy.Description, + Remediation: newPolicy.Remediation, + Severity: newPolicy.Severity, + Limit: newPolicy.Limit, + EvalFrequency: newPolicy.EvalFrequency, + AlertEnabled: newPolicy.AlertEnabled, + AlertProfile: newPolicy.AlertProfile, + } + cli.StopProgress() + + // output policy + if err != nil { + return errors.Wrap(err, msg) + } + if cli.JSONOutput() { + return cli.OutputJSON(newPolicy) + } + cli.OutputHuman( + renderSimpleTable(policyTableHeaders, policyTable([]api.Policy{policyResponse.Data}))) + cli.OutputHuman("\n") + cli.OutputHuman(buildPolicyLibraryDetailsTable(policyResponse.Data)) + return nil +} + +type PolicySyncOperation struct { + ID string + ContentType string + Operation string +} + +func getPolicySyncOperations(policies map[string]LCLPolicy) ([]PolicySyncOperation, error) { + policyOps := []PolicySyncOperation{} + + cli.StartProgress("Retrieving platform policies...") + policiesResponse, err := cli.LwApi.V2.Policy.List() + cli.StopProgress() + + if err != nil { + return policyOps, err + } + var platformPolicyIDs = make([]string, len(policiesResponse.Data)) + for i, policy := range policiesResponse.Data { + platformPolicyIDs[i] = policy.PolicyID + } + + cli.StartProgress("Retrieving platform queries...") + queryResponse, err := cli.LwApi.V2.Query.List() + cli.StopProgress() + + if err != nil { + return policyOps, err + } + var platformQueryIDs = make([]string, len(queryResponse.Data)) + for i, query := range queryResponse.Data { + platformQueryIDs[i] = query.QueryID + } + + for _, lclPolicy := range policies { + qso := PolicySyncOperation{ + ID: lclPolicy.QueryID, + ContentType: "query", + Operation: "create", + } + if array.ContainsStr(platformQueryIDs, lclPolicy.QueryID) { + qso.Operation = "update" + } + policyOps = append(policyOps, qso) + + pso := PolicySyncOperation{ + ID: lclPolicy.PolicyID, + ContentType: "policy", + Operation: "create", + } + suf := fmt.Sprintf("-%s", lclPolicy.PolicyID) + for _, platformPolicyID := range platformPolicyIDs { + // TODO: proper handling for $account based ids + if platformPolicyID == lclPolicy.PolicyID || strings.HasSuffix(platformPolicyID, suf) { + pso.Operation = "update" + break + } + } + policyOps = append(policyOps, pso) + } + + return policyOps, nil +} + +func policySyncOpsDetails(psos []PolicySyncOperation) string { + var ( + ops = []string{"Operation details:"} + detailTemplate = " %s %s will be %sd." + validOperations = []string{"policy-create", "policy-update", "query-create", "query-update"} + caser = cases.Title(language.Und) + ) + + for _, pso := range psos { + key := fmt.Sprintf("%s-%s", pso.ContentType, pso.Operation) + + if !array.ContainsStr(validOperations, key) { + continue + } + + ops = append(ops, fmt.Sprintf( + detailTemplate, + caser.String(pso.ContentType), + pso.ID, + pso.Operation, + )) + } + + return strings.Join(ops, "\n") + "\n\n" +} + +func policySyncOpsSummary(psos []PolicySyncOperation) string { + var ( + msg = "Policy sync-library will create %d policies, update %d policies, create %d queries, update %d queries." + obs = map[string]int{ + "policy-create": 0, + "policy-update": 0, + "query-create": 0, + "query-update": 0, + } + ) + + for _, pso := range psos { + key := fmt.Sprintf("%s-%s", pso.ContentType, pso.Operation) + if v, ok := obs[key]; ok { + obs[key] = v + 1 + } + } + + return fmt.Sprintf( + msg, + obs["policy-create"], + obs["policy-update"], + obs["query-create"], + obs["query-update"], + ) +} + +// Simple helper to prompt for next steps after TF plan +func policySyncPrompt(psos []PolicySyncOperation, previewShown *bool) (int, error) { + options := []string{ + "Apply sync", + } + + // Omit option to show details if we already have + if !*previewShown { + options = append(options, "Show details") + } + options = append(options, "Quit") + + var answer int + err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: policySyncOpsSummary(psos), + Options: options, + }, + Response: &answer, + }) + + return answer, err +} + +func policySyncDisplayChanges(psos []PolicySyncOperation) (bool, error) { + // Prompt for next steps + prompt := true + previewShown := false + var answer int + + // Displaying resources + for prompt { + id, err := policySyncPrompt(psos, &previewShown) + if err != nil { + return false, err + } + + switch { + case id == 1 && !previewShown: + cli.OutputHuman(policySyncOpsDetails(psos)) + default: + answer = id + prompt = false + } + + if id == 1 && !previewShown { + previewShown = true + } + } + + // Run apply + if answer == 0 { + return true, nil + } + + // Quit + return false, nil +} + +func policySyncExecuteChanges(lcl *LaceworkContentLibrary, psos []PolicySyncOperation) error { + for _, pso := range psos { + msg := fmt.Sprintf("unable to %s %s", pso.Operation, pso.ContentType) + + if pso.ContentType == "query" { + // input query + queryString, err := lcl.GetQuery(pso.ID) + if err != nil { + return errors.Wrap(err, msg) + } + + if pso.Operation == "create" { + cli.Log.Debugw("creating query", "query", queryString) + + // parse query + newQuery, err := api.ParseNewQuery(queryString) + if err != nil { + return errors.Wrap(queryErrorCrumbs(queryString), msg) + } + cli.StartProgress(" Creating query...") + create, err := cli.LwApi.V2.Query.Create(newQuery) + cli.StopProgress() + + // output + if err != nil { + return errors.Wrap(err, msg) + } + cli.OutputHuman("The query %s was created.\n", create.Data.QueryID) + } + + if pso.Operation == "update" { + cli.Log.Debugw("updating query", "query", queryString) + + // parse query + newQuery, err := api.ParseNewQuery(queryString) + if err != nil { + return errors.Wrap(queryErrorCrumbs(queryString), msg) + } + updateQuery := api.UpdateQuery{ + QueryText: newQuery.QueryText, + } + + // update query + cli.StartProgress(" Updating query...") + update, err := cli.LwApi.V2.Query.Update(newQuery.QueryID, updateQuery) + cli.StopProgress() + + // output + if err != nil { + return errors.Wrap(err, msg) + } + cli.OutputHuman("The query %s was updated.\n", update.Data.QueryID) + } + } + + if pso.ContentType == "policy" { + // input policy + policyString, err := lcl.GetPolicy(pso.ID) + if err != nil { + return errors.Wrap(err, msg) + } + + if pso.Operation == "create" { + cli.Log.Debugw("creating policy", "policy", policyString) + + // parse policy + newPolicy, err := api.ParseNewPolicy(policyString) + if err != nil { + return errors.Wrap(err, msg) + } + + // create policy + cli.StartProgress(" Creating policy...") + createResponse, err := cli.LwApi.V2.Policy.Create(newPolicy) + cli.StopProgress() + + // output policy + if err != nil { + return errors.Wrap(err, msg) + } + cli.OutputHuman(fmt.Sprintf("The policy %s was created.\n", createResponse.Data.PolicyID)) + } + + if pso.Operation == "update" { + cli.Log.Debugw("updating policy", "policy", policyString) + + // parse policy + updatePolicy, err := api.ParseUpdatePolicy(policyString) + if err != nil { + return errors.Wrap(err, msg) + } + // remove state from policies we're updating + updatePolicy.Enabled = nil + updatePolicy.AlertEnabled = nil + + cli.StartProgress(" Updating policy...") + updateResponse, err := cli.LwApi.V2.Policy.Update(updatePolicy) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, msg) + } + cli.OutputHuman("The policy %s was updated.\n", updateResponse.Data.PolicyID) + } + } + } + return nil +} + +func syncPolicyLibrary(_ *cobra.Command, args []string) error { + msg := "unable to sync policies" + + // check tag + if policyCmdState.Tag == "" { + return errors.New("must specify the --tag flag when performing library sync") + } + // check json output + if cli.JSONOutput() { + return errors.New("json output format not supported for sync-library") + } + + // load content library + cli.StartProgress("Retrieving library policies...") + lcl, err := cli.LoadLCL() + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, msg) + } + + // get policies for tag + policies := lcl.GetPoliciesByTag(policyCmdState.Tag) + if len(policies) == 0 { + cli.OutputHuman("There were no policies found.") + return nil + } + + // build smart list of changes + psos, err := getPolicySyncOperations(policies) + if err != nil { + return errors.Wrap(err, msg) + } + + // prompt for changes + proceed, err := policySyncDisplayChanges(psos) + if err != nil { + return errors.Wrap(err, msg) + } + if !proceed { + return nil + } + + // execute changes + err = policySyncExecuteChanges(lcl, psos) + if err != nil { + return errors.Wrap(err, msg) + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_update.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_update.go new file mode 100644 index 000000000..4342579ac --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_update.go @@ -0,0 +1,225 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strings" + + "github.com/lacework/go-sdk/lwseverity" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + // policyUpdateCmd represents the policy update command + policyUpdateCmd = &cobra.Command{ + Use: "update [policy_id...]", + Short: "Update a policy", + Long: `Update a policy. + +A policy identifier is required to update a policy. + +A policy identifier can be specified via: + +1. A policy update command argument + + lacework policy update my-policy-1 + +2. The policy update payload + + { + "policy_id": "my-policy-1", + "severity": "critical" + } + +A policy identifier specified via command argument always takes precedence over +a policy identifer specified via payload. + +The severity of many policies can be updated at once by passing a list of policy identifiers: + + lacework policy update my-policy-1 my-policy-2 --severity critical + +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + // return error if multiple policy-ids are supplied without severity flag + if len(args) > 1 && policyCmdState.Severity == "" { + return errors.Errorf(`policy bulk update is only supported with the '--severity' flag + +For example: + + lacework policy update %s --severity critical + `, strings.Join(args, " ")) + } + + if policyCmdState.Severity != "" && !lwseverity.IsValid(policyCmdState.Severity) { + return errors.Errorf("invalid severity %q valid severities are: %s", + policyCmdState.Severity, lwseverity.ValidSeverities.String()) + } + + return nil + }, + RunE: updatePolicy, + } +) + +func init() { + // add sub-commands to the policy command + policyCmd.AddCommand(policyUpdateCmd) + + // policy source specific flags + setPolicySourceFlags(policyUpdateCmd) + + // add severity flag + policyUpdateCmd.Flags().StringVar(&policyCmdState.Severity, "severity", "", + "update the policy severity", + ) +} + +func updateQueryFromLibrary(id string) error { + var ( + queryString string + err error + newQuery api.NewQuery + ) + + // input query + queryString, err = inputQueryFromLibrary(id) + if err != nil { + return err + } + + cli.Log.Debugw("creating query", "query", queryString) + + // parse query + newQuery, err = api.ParseNewQuery(queryString) + if err != nil { + return queryErrorCrumbs(queryString) + } + updateQuery := api.UpdateQuery{ + QueryText: newQuery.QueryText, + } + + // update query + _, err = cli.LwApi.V2.Query.Update(newQuery.QueryID, updateQuery) + return err +} + +func updatePolicy(cmd *cobra.Command, args []string) error { + var ( + msg = "unable to update policy" + err error + queryUpdated bool + policyID string + ) + + // if severity flag is provided, attempt bulk update + if policyCmdState.Severity != "" { + err = policyBulkUpdate(args) + if err != nil { + return err + } + return nil + } + + if len(args) != 0 && len(args[0]) != 0 { + policyID = args[0] + } + // input policy + policyString, err := inputPolicy(cmd, policyID) + if err != nil { + return errors.Wrap(err, msg) + } + cli.Log.Debugw("updating policy", "policy", policyString) + + // parse policy + updatePolicy, err := api.ParseUpdatePolicy(policyString) + if err != nil { + return errors.Wrap(err, msg) + } + // set policy id if not already set + if policyID != "" && updatePolicy.PolicyID == "" { + updatePolicy.PolicyID = policyID + } + + cli.StartProgress("Updating policy...") + updateResponse, err := cli.LwApi.V2.Policy.Update(updatePolicy) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, msg) + } + // if updating policy from library also update query + if policyCmdState.CUFromLibrary != "" { + cli.StartProgress("Updating query...") + err = updateQueryFromLibrary(updatePolicy.QueryID) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, msg) + } + queryUpdated = true + } + + // output policy + if cli.JSONOutput() { + return cli.OutputJSON(updateResponse.Data) + } + if queryUpdated { + cli.OutputHuman(fmt.Sprintf("The query %s was updated.\n", updatePolicy.QueryID)) + } + cli.OutputHuman("The policy %s was updated.\n", updateResponse.Data.PolicyID) + return nil +} + +func policyBulkUpdate(args []string) error { + var ( + policyIds []string + err error + ) + // if no policy ids are provided; prompt a list of policy ids + if len(args) == 0 { + policyIds, err = promptSetPolicyIDs() + if err != nil { + return err + } + } else { + policyIds = args + } + + var bulkPolicies api.BulkUpdatePolicies + for _, p := range policyIds { + bulkPolicies = append(bulkPolicies, api.BulkUpdatePolicy{ + PolicyID: p, + Severity: policyCmdState.Severity, + }) + } + + response, err := cli.LwApi.V2.Policy.UpdateMany(bulkPolicies) + if err != nil { + return errors.Wrap(err, "unable to update policies") + } + + cli.Log.Debugw("bulk policy updated", "response", response) + cli.OutputHuman("%d policies updated with new severity %q\n", len(policyIds), policyCmdState.Severity) + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/prompt.go b/vendor/github.com/lacework/go-sdk/cli/cmd/prompt.go new file mode 100644 index 000000000..8db56153e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/prompt.go @@ -0,0 +1,32 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/pkg/errors" +) + +func promptRequiredStringLen(size int, err string) func(interface{}) error { + return func(input interface{}) error { + if str, ok := input.(string); !ok || len(str) < size { + return errors.New(err) + } + return nil + } +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions.go new file mode 100644 index 000000000..f69e113d0 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions.go @@ -0,0 +1,375 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strconv" + "strings" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + CreateReportDefinitionQuestion = "Create from an existing report definition template?" + CreateReportDefinitionReportNameQuestion = "Report Name: " + CreateReportDefinitionDisplayNameQuestion = "Display Name: " + CreateReportDefinitionReportSubTypeQuestion = "Report SubType: " + CreateReportDefinitionAddSectionQuestion = "Add another policy section?" + CreateReportDefinitionSectionTitleQuestion = "Section Title: " + CreateReportDefinitionPoliciesQuestion = "Select Policies in this Section: " + SelectReportDefinitionQuestion = "Select an existing report definition as a template?" + + UpdateReportDefinitionQuestion = "Update report definition in editor?" + UpdateReportDefinitionReportNameQuestion = "Report Name: " + UpdateReportDefinitionDisplayNameQuestion = "Display Name: " + UpdateReportDefinitionEditSectionQuestion = "Update an existing policy section?" + UpdateReportDefinitionEditAnotherSectionQuestion = "Update another existing policy section?" + UpdateReportDefinitionAddSectionQuestion = "Add a new policy section?" + UpdateReportDefinitionSelectSectionQuestion = "Select a section to edit" + + reportDefinitionsCmdState = struct { + // filter report definitions by subtype. 'AWS', 'GCP' or 'Azure' + SubType string + // create report definitions from a file input + File string + // retrieve report definition by version + Version string + }{} + + // report-definitions command is used to manage lacework report definitions + reportDefinitionsCommand = &cobra.Command{ + Use: "report-definition", + Hidden: true, + Aliases: []string{"report-definitions", "rd"}, + Short: "Manage report definitions", + Long: `Manage report definitions to configure the data retrieval and layout information for a report. +`, + } + + // list command is used to list all lacework report definitions + reportDefinitionsListCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all report definitions", + Long: "List all report definitions configured in your Lacework account.", + Args: cobra.NoArgs, + PreRunE: func(_ *cobra.Command, _ []string) error { + if reportDefinitionsCmdState.SubType != "" && + !array.ContainsStr(api.ReportDefinitionSubtypes, reportDefinitionsCmdState.SubType) { + return errors.Errorf("'%s' is not valid. Report definitions subtype can be %s", + reportDefinitionsCmdState.SubType, api.ReportDefinitionSubtypes, + ) + } + return nil + }, + RunE: func(_ *cobra.Command, _ []string) error { + cli.StartProgress(" Fetching report definitions...") + reportDefinitions, err := cli.LwApi.V2.ReportDefinitions.List() + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get report definitions") + } + + if len(reportDefinitions.Data) == 0 { + cli.OutputHuman("There are no report definitions configured in your account.\n") + return nil + } + + // filter definitions by subtype + if reportDefinitionsCmdState.SubType != "" { + filterReportDefinitions(&reportDefinitions) + } + + if cli.JSONOutput() { + return cli.OutputJSON(reportDefinitions) + } + + var rows [][]string + for _, definition := range reportDefinitions.Data { + rows = append(rows, []string{definition.ReportDefinitionGuid, definition.ReportName, + definition.ReportType, definition.SubReportType}) + } + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "TYPE", "SUB-TYPE"}, rows)) + return nil + }, + } + // show command is used to retrieve a lacework report definition by guid + reportDefinitionsShowCommand = &cobra.Command{ + Use: "show ", + Short: "Show a report definition by ID", + Long: `Show a single report definition by it's ID. +To show specific report definition version: + + lacework report-definition show --version + +To show all versions of a report definition: + + lacework report-definition show --version all + +`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if reportDefinitionsCmdState.Version != "" { + return fetchReportDefinitionVersion(args[0]) + } + + cli.StartProgress(" Fetching report definition...") + response, err := cli.LwApi.V2.ReportDefinitions.Get(args[0]) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "unable to get report definition") + } + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + outputReportDefinitionTable(response.Data) + + return nil + }, + } + + // delete command is used to remove a lacework report definition by id + reportDefinitionsDeleteCommand = &cobra.Command{ + Use: "delete ", + Short: "Delete a report definition", + Long: "Delete a single report definition by it's ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress("Deleting report definition...") + err := cli.LwApi.V2.ReportDefinitions.Delete(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to delete report definition") + } + cli.OutputHuman("The report definition with GUID %s was deleted\n", args[0]) + return nil + }, + } +) + +func outputReportDefinitionTable(reportDefinition api.ReportDefinition) { + headers := [][]string{ + {reportDefinition.ReportDefinitionGuid, reportDefinition.ReportName, reportDefinition.ReportType, + reportDefinition.SubReportType}, + } + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "TYPE", "SUB-TYPE"}, headers)) + cli.OutputHuman("\n") + cli.OutputHuman(buildReportDefinitionDetailsTable(reportDefinition)) +} + +func outputReportVersionsList(guid string, versions []string) { + details := [][]string{{"VERSIONS", strings.Join(versions, ", ")}} + + detailsTable := &strings.Builder{} + detailsTable.WriteString(renderOneLineCustomTable(guid, + renderCustomTable([]string{}, details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ), + ) + + cli.OutputHuman(detailsTable.String()) +} + +func fetchReportDefinitionVersion(id string) error { + var ( + err error + version int + reportDefinition api.ReportDefinition + versions []string + ) + + // if no version is supplied return all previous versions + if reportDefinitionsCmdState.Version == "all" { + cli.StartProgress("Fetching all report definition versions...") + response, err := cli.LwApi.V2.ReportDefinitions.GetVersions(id) + cli.StopProgress() + + if err != nil { + return err + } + + if cli.JSONOutput() { + err := cli.OutputJSON(response) + if err != nil { + return err + } + } + + for _, reportVersion := range response.Data { + versions = append(versions, strconv.Itoa(reportVersion.Version)) + } + + outputReportVersionsList(response.Data[0].ReportDefinitionGuid, versions) + return nil + } + + if version, err = strconv.Atoi(reportDefinitionsCmdState.Version); err != nil { + return errors.Wrap(err, "unable to parse version") + } + + cli.StartProgress(fmt.Sprintf("Fetching report definition version %d...", version)) + response, err := cli.LwApi.V2.ReportDefinitions.GetVersions(id) + cli.StopProgress() + + if err != nil { + return err + } + + for _, reportVersion := range response.Data { + versions = append(versions, strconv.Itoa(reportVersion.Version)) + if reportVersion.Version == version { + reportDefinition = reportVersion + } + } + + if cli.JSONOutput() { + return cli.OutputJSON(reportDefinition) + } + + if reportDefinition.ReportDefinitionGuid == "" { + cli.OutputHuman("version %d not found\nvalid versions are: %s\n", version, strings.Join(versions, ", ")) + return nil + } + + outputReportDefinitionTable(reportDefinition) + + return nil +} + +func filterReportDefinitions(reportDefinitions *api.ReportDefinitionsResponse) { + var filteredDefinitions []api.ReportDefinition + for _, rd := range reportDefinitions.Data { + if rd.SubReportType == reportDefinitionsCmdState.SubType { + filteredDefinitions = append(filteredDefinitions, rd) + } + } + reportDefinitions.Data = filteredDefinitions +} + +func init() { + // add the report-definition command + rootCmd.AddCommand(reportDefinitionsCommand) + + // add sub-commands to the report-definition command + reportDefinitionsCommand.AddCommand(reportDefinitionsCreateCommand) + reportDefinitionsCommand.AddCommand(reportDefinitionsListCommand) + reportDefinitionsCommand.AddCommand(reportDefinitionsShowCommand) + reportDefinitionsCommand.AddCommand(reportDefinitionsDeleteCommand) + reportDefinitionsCommand.AddCommand(reportDefinitionsUpdateCommand) + reportDefinitionsCommand.AddCommand(reportDefinitionsRevertCommand) + reportDefinitionsCommand.AddCommand(reportDefinitionsDiffCommand) + + // add flags to report-definition commands + reportDefinitionsShowCommand.Flags().StringVar(&reportDefinitionsCmdState.Version, + "version", "", "show a version of a report definition", + ) + reportDefinitionsListCommand.Flags().StringVar(&reportDefinitionsCmdState.SubType, + "subtype", "", "filter report definitions by subtype. 'AWS', 'GCP' or 'Azure'", + ) + reportDefinitionsCreateCommand.Flags().StringVar(&reportDefinitionsCmdState.File, + "file", "", "create a report definition from an existing definition file", + ) + reportDefinitionsUpdateCommand.Flags().StringVar(&reportDefinitionsCmdState.File, + "file", "", "update a report definition from an existing definition file", + ) +} + +func buildReportDefinitionDetailsTable(definition api.ReportDefinition) string { + var ( + details [][]string + engine = "" + releaseLabel = "" + ) + + details = append(details, []string{"FREQUENCY", definition.Frequency}) + if definition.Props != nil { + engine = definition.Props.Engine + releaseLabel = definition.Props.ReleaseLabel + } + + details = append(details, []string{"ENGINE", engine}) + details = append(details, []string{"RELEASE LABEL", releaseLabel}) + details = append(details, []string{"UPDATED BY", definition.CreatedBy}) + details = append(details, []string{"LAST UPDATED", definition.CreatedTime.String()}) + details = append(details, []string{"VERSION", strconv.Itoa(definition.Version)}) + + detailsTable := &strings.Builder{} + detailsTable.WriteString(renderOneLineCustomTable("REPORT DEFINITION DETAILS", + renderCustomTable([]string{}, details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ), + ) + + policiesTable := &strings.Builder{} + if len(definition.ReportDefinitionDetails.Sections) > 0 { + + for _, s := range definition.ReportDefinitionDetails.Sections { + var policies [][]string + policies = append(policies, []string{s.Title, strings.Join(s.Policies, ", ")}) + + policiesTable.WriteString(renderCustomTable([]string{"title", "policy"}, policies, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + ) + } + policiesTable.WriteString("\n") + + detailsTable.WriteString(renderOneLineCustomTable( + "POLICIES", policiesTable.String(), tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + }), + )) + } + + return detailsTable.String() +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_create.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_create.go new file mode 100644 index 000000000..f07f21c61 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_create.go @@ -0,0 +1,343 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/exp/slices" + "gopkg.in/yaml.v3" +) + +// create command is used to create a new lacework report definition +var reportDefinitionsCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a report definition", + Long: `Create a new report definition to view the evaluation of a set of policies in a report. + +To create a new report definition: + + lacework report-definition create + +To create a new report definition from an existing file: + + lacework report-definition create --file custom-report.json +`, + Args: cobra.NoArgs, + RunE: createReportDefinition, +} + +func createReportDefinition(_ *cobra.Command, args []string) error { + var ( + reportDefinition api.ReportDefinition + err error + ) + + if reportDefinitionsCmdState.File != "" { + fileInput, err := inputReportDefinitionFromFile(reportDefinitionsCmdState.File) + if err != nil { + return err + } + + cfg, err := parseNewReportDefinition(fileInput) + if err != nil { + return err + } + reportDefinition = api.NewReportDefinition(cfg) + } else { + reportDefinition, err = promptCreateReportDefinition() + if err != nil { + return err + } + } + + cli.StartProgress("Creating report definition...") + resp, err := cli.LwApi.V2.ReportDefinitions.Create(reportDefinition) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to create report definition") + } + + cli.OutputHuman("New report definition created. To view the report run:\n\n"+ + "lacework report-definition show %s \n", resp.Data.ReportDefinitionGuid) + return nil +} + +func inputReportDefinitionFromFile(filePath string) (string, error) { + fileData, err := os.ReadFile(filePath) + + if err != nil { + return "", errors.Wrap(err, "unable to read file") + } + + return string(fileData), nil +} + +func promptCreateReportDefinition() (api.ReportDefinition, error) { + var useExisting bool + + if err := survey.AskOne(&survey.Confirm{Message: CreateReportDefinitionQuestion}, &useExisting); err != nil { + return api.ReportDefinition{}, err + } + + if useExisting { + return promptCreateReportDefinitionFromExisting() + } + + return promptCreateReportDefinitionFromNew() +} + +func promptCreateReportDefinitionFromNew() (reportDefinition api.ReportDefinition, err error) { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: CreateReportDefinitionReportNameQuestion}, + Validate: survey.Required, + }, + { + Name: "display", + Prompt: &survey.Input{Message: CreateReportDefinitionDisplayNameQuestion}, + Validate: survey.Required, + }, + { + Name: "subType", + Prompt: &survey.Select{ + Message: CreateReportDefinitionReportSubTypeQuestion, + Options: api.ReportDefinitionSubTypes(), + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string `survey:"name"` + DisplayName string `survey:"display"` + SubType string `survey:"subType"` + }{} + + if err = survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { + return + } + + reportDefinition = api.ReportDefinition{ + ReportName: answers.Name, + DisplayName: answers.DisplayName, + SubReportType: answers.SubType, + ReportType: api.ReportDefinitionTypeCompliance.String(), + } + + var sections []api.ReportDefinitionSection + + cli.StartProgress("Fetching list of policy ids...") + resp, err := cli.LwApi.V2.Policy.List() + cli.StopProgress() + + //filter the policies not in current report's domain + var policies []api.Policy + for _, p := range resp.Data { + domain := strings.ToUpper(answers.SubType) + if slices.Contains(p.Tags, fmt.Sprintf("domain:%s", domain)) { + policies = append(policies, p) + } + } + + if err != nil { + return api.ReportDefinition{}, err + } + + if len(policies) == 0 { + return api.ReportDefinition{}, errors.New("unable to find policies") + } + + // add sections + if err = promptAddReportDefinitionSection(§ions, policies); err != nil { + return + } + + addSection := false + for { + if err = survey.AskOne(&survey.Confirm{ + Message: CreateReportDefinitionAddSectionQuestion, + }, &addSection); err != nil { + return + } + + if addSection { + if err = promptAddReportDefinitionSection(§ions, policies); err != nil { + return + } + } else { + break + } + } + + reportDefinition.ReportDefinitionDetails = api.ReportDefinitionDetails{Sections: sections} + + return +} + +func promptAddReportDefinitionSection(sections *[]api.ReportDefinitionSection, policies []api.Policy) error { + var policyIDs []string + + for _, policy := range policies { + policyIDs = append(policyIDs, policy.PolicyID) + } + + questions := []*survey.Question{ + { + Name: "title", + Prompt: &survey.Input{Message: CreateReportDefinitionSectionTitleQuestion}, + Validate: survey.Required, + }, + { + Name: "policies", + Prompt: &survey.MultiSelect{Message: CreateReportDefinitionPoliciesQuestion, Options: policyIDs}, + Validate: survey.MinItems(1), + }, + } + + answers := struct { + Title string `survey:"title"` + Policies []string `survey:"policies"` + }{} + + if err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { + return err + } + + section := api.ReportDefinitionSection{ + Title: answers.Title, + Policies: answers.Policies, + } + + *sections = append(*sections, section) + return nil +} + +func promptCreateReportDefinitionFromExisting() (reportDefinition api.ReportDefinition, err error) { + var ( + reports = make(map[string]api.ReportDefinition) + reportNames []string + selectedReport string + reportDefinitionConfig api.ReportDefinitionConfig + ) + + cli.StartProgress("Fetching existing report definitions...") + resp, err := cli.LwApi.V2.ReportDefinitions.List() + cli.StopProgress() + + if err != nil { + return + } + + for _, report := range resp.Data { + reports[report.ReportName] = report + reportNames = append(reportNames, report.ReportName) + } + + // Add option for blank template + reports["BLANK TEMPLATE"] = api.ReportDefinition{ReportName: "TEMPLATE", + ReportType: api.ReportDefinitionTypeCompliance.String(), + ReportDefinitionDetails: api.ReportDefinitionDetails{ + Sections: []api.ReportDefinitionSection{ + {Title: "CUSTOM SECTION TITLE", Policies: []string{"example-policy-1"}}}}} + reportNames = append([]string{"BLANK TEMPLATE"}, reportNames...) + + if err = survey.AskOne(&survey.Select{ + Message: SelectReportDefinitionQuestion, + Options: reportNames, + }, &selectedReport); err != nil { + return + } + + reportTemplateYaml, err := yaml.Marshal(reports[selectedReport].Config()) + if err != nil { + return + } + + // open editor with report yaml + report, err := inputReportDefinitionFromEditor("create", string(reportTemplateYaml)) + if err != nil { + return + } + err = yaml.Unmarshal([]byte(report), &reportDefinitionConfig) + + reportDefinition = api.NewReportDefinition(reportDefinitionConfig) + return +} + +func inputReportDefinitionFromEditor(action string, reportYaml string) (report string, err error) { + prompt := &survey.Editor{ + Message: fmt.Sprintf("Use the editor to %s your report definition", action), + FileName: "report-definition*.yaml", + HideDefault: true, + AppendDefault: true, + Default: reportYaml, + } + + err = survey.AskOne(prompt, &report) + return +} + +func parseNewReportDefinition(s string) (report api.ReportDefinitionConfig, err error) { + var res api.ReportDefinitionResponse + if err = json.Unmarshal([]byte(s), &res); err == nil && res.Data.ReportName != "" { + report = api.ReportDefinitionConfig{ + ReportName: res.Data.ReportName, + ReportType: res.Data.ReportType, + SubReportType: res.Data.SubReportType, + DisplayName: res.Data.DisplayName, + Sections: res.Data.ReportDefinitionDetails.Sections, + } + return report, nil + } + + var cfg api.ReportDefinition + if err = json.Unmarshal([]byte(s), &cfg); err == nil && cfg.ReportName != "" { + report = api.ReportDefinitionConfig{ + ReportName: cfg.ReportName, + ReportType: cfg.ReportType, + SubReportType: cfg.SubReportType, + DisplayName: cfg.DisplayName, + Sections: cfg.ReportDefinitionDetails.Sections, + } + return report, nil + } + + var yamlCfg api.ReportDefinition + if err = yaml.Unmarshal([]byte(s), &yamlCfg); err == nil && yamlCfg.ReportName != "" { + report = api.ReportDefinitionConfig{ + ReportName: yamlCfg.ReportName, + ReportType: yamlCfg.ReportType, + SubReportType: yamlCfg.SubReportType, + DisplayName: yamlCfg.DisplayName, + Sections: yamlCfg.ReportDefinitionDetails.Sections, + } + return report, nil + } + + return report, errors.New("unable to parse report definition file") +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_diff.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_diff.go new file mode 100644 index 000000000..f08c348e8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_diff.go @@ -0,0 +1,166 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strconv" + "strings" + + "github.com/fatih/color" + "github.com/pkg/errors" + "github.com/pmezard/go-difflib/difflib" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +// reportDefinitionsDiffCommand command is used to compare 2 lacework report definition versions +var reportDefinitionsDiffCommand = &cobra.Command{ + Use: "diff ", + Short: "Compare two versions of a report definition", + Long: `Compare two versions of a report definition. + +To see a diff of two report definition versions: + + lacework report-definition diff +`, + Args: cobra.ExactArgs(3), + RunE: diffReportDefinition, +} + +func diffReportDefinition(_ *cobra.Command, args []string) error { + var ( + err error + versionOne int + versionTwo int + reportOne *diffCfg + reportTwo *diffCfg + ) + + if versionOne, err = strconv.Atoi(args[1]); err != nil { + return errors.Wrap(err, "unable to parse version") + } + + if versionTwo, err = strconv.Atoi(args[2]); err != nil { + return errors.Wrap(err, "unable to parse version") + } + + cli.StartProgress("Fetching all report definition versions...") + response, err := cli.LwApi.V2.ReportDefinitions.GetVersions(args[0]) + cli.StopProgress() + + if err != nil { + return err + } + + for _, r := range response.Data { + if r.Version == versionOne { + reportOne = &diffCfg{ + name: fmt.Sprintf("Version-%d", r.Version), + object: r, + } + } + if r.Version == versionTwo { + reportTwo = &diffCfg{ + name: fmt.Sprintf("Version-%d", r.Version), + object: r, + } + } + } + + if reportOne == nil || reportTwo == nil { + return errors.New("unable to find report definition versions") + } + + diff, err := diffAsYamlString(*reportOne, *reportTwo) + if err != nil { + return err + } + + cli.OutputHuman(diff) + return nil +} + +type diffCfg struct { + name string + object any +} + +func diffAsYamlString(objectOne, objectTwo diffCfg) (string, error) { + yamlBytesOne, err := yaml.Marshal(objectOne.object) + if err != nil { + return "", err + } + + yamlBytesTwo, err := yaml.Marshal(objectTwo.object) + if err != nil { + return "", err + } + + diff := difflib.UnifiedDiff{ + A: difflib.SplitLines(string(yamlBytesOne)), + B: difflib.SplitLines(string(yamlBytesTwo)), + FromFile: objectOne.name, + ToFile: objectTwo.name, + Context: 3, + } + + diffText, err := difflib.GetUnifiedDiffString(diff) + if err != nil { + return "", err + } + + output := prettyPrintDiff(diffText) + return output, nil +} + +func prettyPrintDiff(diff string) string { + if diff == "" { + return "" + } + + var sb = &strings.Builder{} + lines := strings.Split(diff, "\n") + + //colourize lines in diff + for _, s := range lines { + if strings.HasPrefix(s, "+") { + addition := color.HiGreenString(fmt.Sprintf("%s\n", s)) + sb.WriteString(addition) + continue + } + + if strings.HasPrefix(s, "-") { + subtraction := color.HiRedString(fmt.Sprintf("%s\n", s)) + sb.WriteString(subtraction) + continue + } + + if strings.HasPrefix(s, "@@") { + lineDiff := color.HiBlueString(fmt.Sprintf("%s\n", s)) + sb.WriteString(lineDiff) + continue + } + + sb.WriteString(fmt.Sprintf("%s\n", s)) + + } + + return sb.String() +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_revert.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_revert.go new file mode 100644 index 000000000..7206416a0 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_revert.go @@ -0,0 +1,67 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strconv" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// revert command is used to rollback lacework report definition to a previous version +var reportDefinitionsRevertCommand = &cobra.Command{ + Use: "revert ", + Aliases: []string{"restore"}, + Short: "Update a report definition", + Long: `Update an existing custom report definition. + +To revert a report definition: + + lacework report-definition revert + +To compare two report definition versions before a revert: + + lacework report-definition diff +`, + Args: cobra.ExactArgs(2), + RunE: revertReportDefinition, +} + +func revertReportDefinition(_ *cobra.Command, args []string) error { + var ( + err error + version int + ) + + if version, err = strconv.Atoi(args[1]); err != nil { + return errors.Wrap(err, "unable to parse version") + } + + cli.StartProgress("Reverting report definition...") + resp, err := cli.LwApi.V2.ReportDefinitions.Revert(args[0], version) + cli.StopProgress() + + if err != nil { + return err + } + + cli.OutputHuman("The report definition %s was reverted to version %d \n", resp.Data.ReportDefinitionGuid, version) + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_update.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_update.go new file mode 100644 index 000000000..8bcd687fa --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_update.go @@ -0,0 +1,345 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/exp/slices" + "gopkg.in/yaml.v3" +) + +// update command is used to update a new lacework report definition +var reportDefinitionsUpdateCommand = &cobra.Command{ + Use: "update ", + Short: "Update a report definition", + Long: `Update an existing custom report definition. + +To update an existing report definition: + + lacework report-definition update + +To update a new report definition from an existing file: + + lacework report-definition update --file custom-report.json +`, + Args: cobra.ExactArgs(1), + RunE: updateReportDefinition, +} + +func updateReportDefinition(_ *cobra.Command, args []string) error { + var ( + reportDefinition api.ReportDefinitionUpdate + err error + ) + + cli.StartProgress("Fetching report definition...") + existingReport, err := cli.LwApi.V2.ReportDefinitions.Get(args[0]) + cli.StopProgress() + + if err != nil { + return err + } + + if existingReport.Data.CreatedBy == "SYSTEM" { + return errors.New("only user created report definitions can be modified") + } + + if reportDefinitionsCmdState.File != "" { + fileInput, err := inputReportDefinitionFromFile(reportDefinitionsCmdState.File) + if err != nil { + return err + } + + cfg, err := parseNewReportDefinition(fileInput) + if err != nil { + return err + } + reportDefinition = api.NewReportDefinitionUpdate(cfg) + } else { + reportDefinition, err = promptUpdateReportDefinition(existingReport.Data) + if err != nil { + return err + } + } + + cli.StartProgress("Updating report definition...") + resp, err := cli.LwApi.V2.ReportDefinitions.Update(args[0], reportDefinition) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to update report definition") + } + + cli.OutputHuman("The report definition %s was updated. \n", resp.Data.ReportDefinitionGuid) + return nil +} + +func promptUpdateReportDefinition(existingReport api.ReportDefinition) (api.ReportDefinitionUpdate, error) { + var useEditor bool + + if err := survey.AskOne(&survey.Confirm{Message: UpdateReportDefinitionQuestion}, &useEditor); err != nil { + return api.ReportDefinitionUpdate{}, err + } + + if useEditor { + return promptUpdateReportDefinitionFromEditor(existingReport) + } + + return promptUpdateReportDefinitionQuestions(existingReport) +} + +func promptUpdateReportDefinitionQuestions(existingReport api.ReportDefinition) ( + reportDefinition api.ReportDefinitionUpdate, err error, +) { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: UpdateReportDefinitionReportNameQuestion, Default: existingReport.ReportName}, + Validate: survey.Required, + }, + { + Name: "display", + Prompt: &survey.Input{Message: UpdateReportDefinitionDisplayNameQuestion, Default: existingReport.DisplayName}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string `survey:"name"` + DisplayName string `survey:"display"` + }{} + + if err = survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { + return + } + + reportDefinition = api.ReportDefinitionUpdate{ + ReportName: answers.Name, + DisplayName: answers.DisplayName, + } + + sections, err := promptUpdateReportDefinitionSections(existingReport) + if err != nil { + return + } + + reportDefinition.ReportDefinitionDetails = &api.ReportDefinitionDetails{Sections: sections} + + return +} + +func promptUpdateReportDefinitionSections(report api.ReportDefinition) ([]api.ReportDefinitionSection, error) { + var ( + sections = report.ReportDefinitionDetails.Sections + newSections []api.ReportDefinitionSection + err error + ) + + cli.StartProgress("Fetching list of policy ids...") + resp, err := cli.LwApi.V2.Policy.List() + cli.StopProgress() + + if err != nil { + return nil, err + } + + //filter the policies not in current report's domain + var policies []api.Policy + for _, p := range resp.Data { + domain := strings.ToUpper(report.SubReportType) + if slices.Contains(p.Tags, fmt.Sprintf("domain:%s", domain)) { + policies = append(policies, p) + } + } + + if len(policies) == 0 { + return nil, errors.New("unable to find policies") + } + + // edit existing sections + editSection := false + if err := survey.AskOne(&survey.Confirm{ + Message: UpdateReportDefinitionEditSectionQuestion, + }, &editSection); err != nil { + return nil, err + } + + if editSection { + if sections, err = promptUpdateReportDefinitionSection(§ions, policies); err != nil { + return nil, err + } + + editAnotherSection := false + for { + if err := survey.AskOne(&survey.Confirm{ + Message: UpdateReportDefinitionEditAnotherSectionQuestion, + }, &editAnotherSection); err != nil { + return nil, err + } + + if editAnotherSection { + if sections, err = promptUpdateReportDefinitionSection(&newSections, policies); err != nil { + return nil, err + } + } else { + break + } + } + } + + // add new sections + addSection := false + if err := survey.AskOne(&survey.Confirm{ + Message: UpdateReportDefinitionAddSectionQuestion, + }, &addSection); err != nil { + return nil, err + } + + if addSection { + if err := promptAddReportDefinitionSection(&newSections, policies); err != nil { + return nil, err + } + + addAnotherSection := false + for { + if err := survey.AskOne(&survey.Confirm{ + Message: CreateReportDefinitionAddSectionQuestion, + }, &addAnotherSection); err != nil { + return nil, err + } + + if addAnotherSection { + if err := promptAddReportDefinitionSection(&newSections, policies); err != nil { + return nil, err + } + } else { + break + } + } + } + + sections = append(sections, newSections...) + + return sections, nil + +} + +func promptUpdateReportDefinitionFromEditor(existingReport api.ReportDefinition) ( + reportDefinition api.ReportDefinitionUpdate, err error, +) { + var reportDefinitionConfig api.ReportDefinitionConfig + + if err != nil { + return + } + + updateCfg := api.NewReportDefinitionUpdate(existingReport.Config()) + reportTemplateYaml, err := yaml.Marshal(updateCfg) + if err != nil { + return + } + + // open editor with report yaml + report, err := inputReportDefinitionFromEditor("update", string(reportTemplateYaml)) + if err != nil { + return + } + err = yaml.Unmarshal([]byte(report), &reportDefinitionConfig) + + reportDefinition = api.NewReportDefinitionUpdate(reportDefinitionConfig) + + return +} + +func promptUpdateReportDefinitionSection(currentSections *[]api.ReportDefinitionSection, policies []api.Policy) ( + []api.ReportDefinitionSection, error, +) { + type sectionMapping struct { + section api.ReportDefinitionSection + position int + } + + var ( + policyIDs []string + selectedSections = make(map[string]sectionMapping) + sectionTitles []string + sections = *currentSections + ) + + for _, policy := range policies { + policyIDs = append(policyIDs, policy.PolicyID) + } + + for i, section := range sections { + sectionTitles = append(sectionTitles, section.Title) + selectedSections[section.Title] = sectionMapping{section: section, position: i} + } + + var selectedTitle string + if err := survey.AskOne(&survey.Select{ + Message: UpdateReportDefinitionSelectSectionQuestion, + Options: sectionTitles, + }, &selectedTitle); err != nil { + return nil, err + } + + selectedSection := selectedSections[selectedTitle] + + questions := []*survey.Question{ + { + Name: "title", + Prompt: &survey.Input{ + Message: CreateReportDefinitionSectionTitleQuestion, + Default: selectedSection.section.Title, + }, + Validate: survey.Required, + }, + { + Name: "policies", + Prompt: &survey.MultiSelect{ + Message: CreateReportDefinitionPoliciesQuestion, + Options: policyIDs, + Default: selectedSection.section.Policies, + }, + Validate: survey.MinItems(1), + }, + } + + answers := struct { + Title string `survey:"title"` + Policies []string `survey:"policies"` + }{} + + if err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { + return nil, err + } + + updatedSection := api.ReportDefinitionSection{ + Title: answers.Title, + Policies: answers.Policies, + } + + sections[selectedSection.position] = updatedSection + return sections, nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions.go new file mode 100644 index 000000000..b97f2519b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions.go @@ -0,0 +1,226 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/structs" + "github.com/lacework/go-sdk/api" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + CreateReportDistributionReportNameQuestion = "Report Distribution Name: " + CreateReportDistributionFrequencyQuestion = "Select Frequency: " + CreateReportDistributionDefinitionQuestion = "Select Report Definition: " + CreateReportDistributionAlertChannelsQuestion = "Select Alert Channels: " + CreateReportDistributionResourceGroupsQuestion = "Select Resource Groups: " + CreateReportDistributionIntegrationAwsQuestion = "Select Aws Accounts: " + CreateReportDistributionAddSeveritiesQuestion = "Add Severities? " + CreateReportDistributionSeveritiesQuestion = "Select Severities: " + CreateReportDistributionAddViolationsQuestion = "Add Violations? " + CreateReportDistributionScopeQuestion = "Select Distribution Scope:" + CreateReportDistributionViolationsQuestion = "Select Violations: " + UpdateReportDistributionReportNameQuestion = "Update Report Distribution Name? " + UpdateReportDistributionFrequencyQuestion = "Update Frequency?" + UpdateReportDistributionAlertChannelsQuestion = "Update Alert Channels? " + UpdateReportDistributionAddSeveritiesQuestion = "Update Severities? " + UpdateReportDistributionAddViolationsQuestion = "Update Violations? " + + // report-distributions command is used to manage lacework report distributions + reportDistributionsCommand = &cobra.Command{ + Use: "report-distribution", + Hidden: true, + Aliases: []string{"report-distributions"}, + Short: "Manage report distributions", + Long: `Manage report distributions to configure the data retrieval and layout information for a report. +`, + } + + // list command is used to list all lacework report distributions + reportDistributionsListCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all report distributions", + Long: "List all report distributions configured in your Lacework account.", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + cli.StartProgress(" Fetching report distributions...") + reportDistributions, err := cli.LwApi.V2.ReportDistributions.List() + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get report distributions") + } + + if len(reportDistributions.Data) == 0 { + cli.OutputHuman("There are no report distributions configured in your account.\n") + return nil + } + + if cli.JSONOutput() { + return cli.OutputJSON(reportDistributions) + } + + var rows [][]string + for _, distribution := range reportDistributions.Data { + rows = append(rows, []string{distribution.ReportDistributionGuid, distribution.DistributionName, + distribution.Frequency, distribution.ReportDefinitionGuid}) + } + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "FREQUENCY", "DEFINITION GUID"}, rows)) + return nil + }, + } + // show command is used to retrieve a lacework report distribution by guid + reportDistributionsShowCommand = &cobra.Command{ + Use: "show ", + Short: "Show a report distribution by ID", + Long: `Show a single report distribution by it's ID. +To show specific report distribution details: + + lacework report-distribution show + +`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cli.StartProgress(" Fetching report distribution...") + response, err := cli.LwApi.V2.ReportDistributions.Get(args[0]) + cli.StopProgress() + + if err != nil { + return errors.Wrap(err, "unable to get report distribution") + } + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + outputReportDistributionTable(response.Data) + + return nil + }, + } + + // delete command is used to remove a lacework report distribution by id + reportDistributionsDeleteCommand = &cobra.Command{ + Use: "delete ", + Short: "Delete a report distribution", + Long: "Delete a single report distribution by it's ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress("Deleting report distribution...") + err := cli.LwApi.V2.ReportDistributions.Delete(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to delete report distribution") + } + cli.OutputHuman("The report distribution with GUID %s was deleted\n", args[0]) + return nil + }, + } +) + +func outputReportDistributionTable(reportDistribution api.ReportDistribution) { + headers := [][]string{ + {reportDistribution.ReportDistributionGuid, reportDistribution.DistributionName, reportDistribution.Frequency, + reportDistribution.ReportDefinitionGuid}, + } + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "FREQUENCY", "DEFINITION GUID"}, headers)) + cli.OutputHuman("\n") + cli.OutputHuman(buildReportDistributionDetailsTable(reportDistribution)) +} + +func init() { + // add the report-distribution command + rootCmd.AddCommand(reportDistributionsCommand) + + // add sub-commands to the report-distribution command + reportDistributionsCommand.AddCommand(reportDistributionsListCommand) + reportDistributionsCommand.AddCommand(reportDistributionsShowCommand) + reportDistributionsCommand.AddCommand(reportDistributionsDeleteCommand) + reportDistributionsCommand.AddCommand(reportDistributionsCreateCommand) + reportDistributionsCommand.AddCommand(reportDistributionsUpdateCommand) +} + +func buildReportDistributionDetailsTable(distribution api.ReportDistribution) string { + var ( + details [][]string + integrations []string + ) + + if distribution.Data.Integrations != nil { + for _, integration := range distribution.Data.Integrations { + var integrationKV strings.Builder + integrationMap := structs.Map(integration) + for k, v := range integrationMap { + if v == "" { + continue + } + integrationKV.WriteString(fmt.Sprintf("%s: %s ", k, v)) + } + integrations = append(integrations, integrationKV.String()) + } + } + + details = append(details, []string{"SEVERITY", strings.Join(distribution.Data.Severities, ", ")}) + details = append(details, []string{"VIOLATIONS", strings.Join(distribution.Data.Violations, ", ")}) + + detailsTable := &strings.Builder{} + dataTable := &strings.Builder{} + + dataTable.WriteString(renderCustomTable([]string{}, details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + )) + dataTable.WriteString("\n") + + var data [][]string + channels := strings.Join(distribution.AlertChannels, "\n") + groups := strings.Join(distribution.Data.ResourceGroups, "\n") + integrationList := strings.Join(integrations, "\n") + + data = append(data, []string{channels, groups, integrationList}) + + dataTable.WriteString(renderCustomTable([]string{"ALERT CHANNELS", "RESOURCE GROUPS", "INTEGRATIONS"}, data, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + ) + + detailsTable.WriteString(renderOneLineCustomTable("REPORT DISTRIBUTION DETAILS", + dataTable.String(), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ), + ) + + return detailsTable.String() +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_create.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_create.go new file mode 100644 index 000000000..5b8af9b22 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_create.go @@ -0,0 +1,441 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/lwseverity" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// create command is used to create a new lacework report distribution +var reportDistributionsCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a report distribution", + Long: `Create associates a report definition with a distribution channel for the report. +A report distribution can refine the scope of the report by filtering its content by incident severity, +resource groups, and integrations. + +To create a new report distribution: + + lacework report-distribution create +`, + Args: cobra.NoArgs, + RunE: createReportDistribution, +} + +func createReportDistribution(_ *cobra.Command, args []string) error { + var ( + reportDistribution api.ReportDistribution + err error + ) + reportDistribution, err = promptCreateReportDistributionFromNew() + if err != nil { + return err + } + + cli.StartProgress("Creating report distribution...") + resp, err := cli.LwApi.V2.ReportDistributions.Create(reportDistribution) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to create report distribution") + } + + cli.OutputHuman("New report distribution created. To view the report run:\n\n"+ + "lacework report-distribution show %s \n", resp.Data.ReportDistributionGuid) + return nil +} + +func promptCreateReportDistributionFromNew() (reportDistribution api.ReportDistribution, err error) { + cli.StartProgress("Fetching list of report definitions...") + reportDefinitions, err := cli.LwApi.V2.ReportDefinitions.List() + cli.StopProgress() + if err != nil { + return api.ReportDistribution{}, err + } + definitionMap := make(map[string]api.ReportDefinition, len(reportDefinitions.Data)) + var definitionDisplayOptions []string + + for _, definition := range reportDefinitions.Data { + definitionMap[definition.DisplayName] = definition + definitionDisplayOptions = append(definitionDisplayOptions, definition.DisplayName) + } + + cli.StartProgress("Fetching list of alert channels...") + alertChannels, err := cli.LwApi.V2.AlertChannels.List() + cli.StopProgress() + if err != nil { + return api.ReportDistribution{}, err + } + channelMap := make(map[string]string, len(alertChannels.Data)) + var channelOptions []string + + for _, channel := range alertChannels.Data { + if channel.Type == api.EmailUserAlertChannelType.String() { + channelMap[channel.Name] = channel.IntgGuid + channelOptions = append(channelOptions, channel.Name) + } + } + + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: CreateReportDistributionReportNameQuestion}, + Validate: survey.Required, + }, + { + Name: "frequency", + Prompt: &survey.Select{ + Message: CreateReportDistributionFrequencyQuestion, + Options: api.ReportDistributionFrequencies(), + }, + Validate: survey.Required, + }, + { + Name: "definition", + Prompt: &survey.Select{Message: CreateReportDistributionDefinitionQuestion, Options: definitionDisplayOptions}, + Validate: survey.Required, + }, + { + Name: "channels", + Prompt: &survey.MultiSelect{Message: CreateReportDistributionAlertChannelsQuestion, Options: channelOptions}, + Validate: survey.Required, + }, + } + + answers := struct { + Name string `survey:"name"` + Frequency string `survey:"frequency"` + Definition string `survey:"definition"` + Channels []string `survey:"channels"` + ResourceGroups []string `survey:"groups"` + Integrations []string `survey:"integrations"` + }{} + + if err = survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { + return + } + + var channelAnswers []string + for _, c := range answers.Channels { + channelAnswers = append(channelAnswers, channelMap[c]) + } + + reportDistribution = api.ReportDistribution{ + ReportDefinitionGuid: definitionMap[answers.Definition].ReportDefinitionGuid, + DistributionName: answers.Name, + AlertChannels: channelAnswers, + Frequency: answers.Frequency, + } + + // scope can only be either resource group or integration + if err = promptReportDistributionScope(&reportDistribution, + definitionMap[answers.Definition].SubReportType); err != nil { + return api.ReportDistribution{}, err + } + + // prompt optional fields + violations, err := promptAddReportDistributionViolations() + if err != nil { + return api.ReportDistribution{}, err + } + reportDistribution.Data.Violations = violations + + severities, err := promptAddReportDistributionSeverities() + + if err != nil { + return api.ReportDistribution{}, err + } + reportDistribution.Data.Severities = severities + + return +} + +func promptReportDistributionScope(distribution *api.ReportDistribution, subReportType string) error { + distributionScope := "" + if err := survey.AskOne(&survey.Select{ + Message: CreateReportDistributionScopeQuestion, + Options: api.ReportDistributionScopes(), + }, &distributionScope); err != nil { + return err + } + + if distributionScope == api.ReportDistributionScopeCloudIntegration.String() { + if err := promptReportDistributionIntegration(distribution, subReportType); err != nil { + return err + } + return nil + } else { + if err := promptReportDistributionResourceGroup(distribution); err != nil { + return err + } + return nil + } +} + +func promptReportDistributionResourceGroup(distribution *api.ReportDistribution) error { + cli.StartProgress("Fetching list of resource groups...") + resourceGroups, err := cli.LwApi.V2.ResourceGroups.List() + cli.StopProgress() + if err != nil { + return err + } + groupMap := make(map[string]string, len(resourceGroups.Data)) + + var ( + groupOptions []string + groupAnswers []string + ) + + for _, definition := range resourceGroups.Data { + groupMap[definition.Name] = definition.ID() + groupOptions = append(groupOptions, definition.Name) + } + + var selectedGroups []string + if err = survey.AskOne(&survey.MultiSelect{ + Message: CreateReportDistributionResourceGroupsQuestion, + Options: groupOptions, + }, &selectedGroups); err != nil { + return err + } + + for _, c := range selectedGroups { + groupAnswers = append(groupAnswers, groupMap[c]) + } + + distribution.Data.ResourceGroups = groupAnswers + + return nil +} + +func promptReportDistributionIntegration(distribution *api.ReportDistribution, subReportType string) error { + var integrations []api.ReportDistributionIntegration + + switch subReportType { + case api.ReportDefinitionSubTypeAws.String(): + if err := promptReportDistributionIntegrationsAws(&integrations); err != nil { + return err + } + case api.ReportDefinitionSubTypeGcp.String(): + if err := promptReportDistributionIntegrationsGcp(&integrations); err != nil { + return err + } + case api.ReportDefinitionSubTypeAzure.String(): + if err := promptReportDistributionIntegrationsAzure(&integrations); err != nil { + return err + } + default: + return errors.Errorf("unsupported report definition type '%s'", + subReportType) + } + + distribution.Data.Integrations = integrations + return nil +} + +func promptReportDistributionIntegrationsAws(integrations *[]api.ReportDistributionIntegration) error { + cli.StartProgress("Fetching Aws Account IDs...") + accounts, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AwsCfgCloudAccount) + cli.StopProgress() + + if err != nil { + return err + } + + var integrationOptions []string + + for _, ca := range accounts.Data { + if caMap, ok := ca.GetData().(map[string]interface{}); ok { + integrationOptions = append(integrationOptions, caMap["awsAccountId"].(string)) + } + } + + var integrationAnswers []string + if err = survey.AskOne(&survey.MultiSelect{ + Renderer: survey.Renderer{}, + Message: CreateReportDistributionIntegrationAwsQuestion, + Options: integrationOptions, + }, &integrationAnswers); err != nil { + return err + } + + for _, integration := range integrationAnswers { + *integrations = append(*integrations, api.ReportDistributionIntegration{AccountID: integration}) + } + + return nil +} + +func promptReportDistributionIntegrationsGcp(integrations *[]api.ReportDistributionIntegration) error { + err := promptReportDistributionIntegrationsGcpData(integrations) + if err != nil { + return err + } + + addIntegration := false + for { + if err := survey.AskOne(&survey.Confirm{ + Message: "Add another Gcp Integration?", + }, &addIntegration); err != nil { + return err + } + + if addIntegration { + err = promptReportDistributionIntegrationsGcpData(integrations) + if err != nil { + return err + } + } else { + break + } + } + + return nil +} + +func promptReportDistributionIntegrationsGcpData(integrations *[]api.ReportDistributionIntegration) error { + questions := []*survey.Question{ + { + Name: "org", + Prompt: &survey.Input{Message: "Organization ID:"}, + Validate: survey.Required, + }, + { + Name: "project", + Prompt: &survey.Input{Message: "Project ID:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Org string `survey:"org"` + Project string `survey:"project"` + }{} + + if err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { + return err + } + + *integrations = append(*integrations, api.ReportDistributionIntegration{OrganizationID: answers.Org, + ProjectID: answers.Project}) + + return nil +} + +func promptReportDistributionIntegrationsAzure(integrations *[]api.ReportDistributionIntegration) error { + err := promptReportDistributionIntegrationsAzureData(integrations) + if err != nil { + return err + } + + addIntegration := false + for { + if err := survey.AskOne(&survey.Confirm{ + Message: "Add another Azure Integration?", + }, &addIntegration); err != nil { + return err + } + + if addIntegration { + err = promptReportDistributionIntegrationsAzureData(integrations) + if err != nil { + return err + } + } else { + break + } + } + + return nil +} + +func promptReportDistributionIntegrationsAzureData(integrations *[]api.ReportDistributionIntegration) error { + questions := []*survey.Question{ + { + Name: "tenant", + Prompt: &survey.Input{Message: "Tenant ID:"}, + Validate: survey.Required, + }, + { + Name: "subscription", + Prompt: &survey.Input{Message: "Subscription ID:"}, + Validate: survey.Required, + }, + } + + answers := struct { + Tenant string `survey:"tenant"` + Subscription string `survey:"subscription"` + }{} + + if err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { + return err + } + + *integrations = append(*integrations, api.ReportDistributionIntegration{TenantID: answers.Tenant, + SubscriptionID: answers.Subscription}) + + return nil +} + +func promptAddReportDistributionViolations() (violations []string, err error) { + addViolations := false + if err = survey.AskOne(&survey.Confirm{ + Message: CreateReportDistributionAddViolationsQuestion, + }, &addViolations); err != nil { + return + } + + if addViolations { + if err = survey.AskOne(&survey.MultiSelect{ + Renderer: survey.Renderer{}, + Message: CreateReportDistributionViolationsQuestion, + Options: api.ReportDistributionViolations(), + }, &violations); err != nil { + return + } + } + + return +} + +func promptAddReportDistributionSeverities() (sevs []string, err error) { + addSevs := false + if err = survey.AskOne(&survey.Confirm{ + Message: CreateReportDistributionAddSeveritiesQuestion, + }, &addSevs); err != nil { + return + } + + if addSevs { + if err = survey.AskOne(&survey.MultiSelect{ + Message: CreateReportDistributionSeveritiesQuestion, + Options: strings.Split(lwseverity.ValidSeverities.String(), ","), + }, &sevs); err != nil { + return + } + } + + return +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_update.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_update.go new file mode 100644 index 000000000..cc2020e5c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_update.go @@ -0,0 +1,360 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2023, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/lwseverity" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// update command is used to update an existing lacework report distribution +var reportDistributionsUpdateCommand = &cobra.Command{ + Use: "update ", + Short: "Update an existing report distribution", + Long: `Update an existing report distribution. + +To update a report distribution: + + lacework report-distribution update +`, + Args: cobra.ExactArgs(1), + RunE: updateReportDistribution, +} + +func updateReportDistribution(_ *cobra.Command, args []string) error { + var ( + reportDistribution api.ReportDistributionUpdate + err error + ) + + existing, err := cli.LwApi.V2.ReportDistributions.Get(args[0]) + if err != nil { + return err + } + + reportDistribution, err = promptUpdateReportDistribution(existing.Data) + if err != nil { + return err + } + + cli.StartProgress("Updated report distribution...") + resp, err := cli.LwApi.V2.ReportDistributions.Update(args[0], reportDistribution) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to update report distribution") + } + + cli.OutputHuman("Report distribution updated. To view the report run:\n\n"+ + "lacework report-distribution show %s \n", resp.Data.ReportDistributionGuid) + return nil +} + +func promptUpdateReportDistribution(existing api.ReportDistribution) ( + reportDistribution api.ReportDistributionUpdate, err error, +) { + cli.StartProgress("Fetching list of alert channels...") + definition, err := cli.LwApi.V2.ReportDefinitions.Get(existing.ReportDefinitionGuid) + cli.StopProgress() + if err != nil { + return api.ReportDistributionUpdate{}, err + } + + cli.StartProgress("Fetching list of alert channels...") + alertChannels, err := cli.LwApi.V2.AlertChannels.List() + cli.StopProgress() + if err != nil { + return api.ReportDistributionUpdate{}, err + } + + channelMap := make(map[string]string, len(alertChannels.Data)) + channelIDMap := make(map[string]string, len(alertChannels.Data)) + var channelOptions []string + var channelDefaults []string + + for _, channel := range alertChannels.Data { + if channel.Type == api.EmailUserAlertChannelType.String() { + channelMap[channel.Name] = channel.IntgGuid + channelIDMap[channel.IntgGuid] = channel.Name + channelOptions = append(channelOptions, channel.Name) + } + } + + for _, def := range existing.AlertChannels { + channelDefaults = append(channelDefaults, channelIDMap[def]) + } + + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: UpdateReportDistributionReportNameQuestion, Default: existing.DistributionName}, + Validate: survey.Required, + }, + { + Name: "frequency", + Prompt: &survey.Select{ + Message: UpdateReportDistributionFrequencyQuestion, + Options: api.ReportDistributionFrequencies(), + Default: existing.Frequency, + }, + Validate: survey.Required, + }, + { + Name: "channels", + Prompt: &survey.MultiSelect{ + Message: UpdateReportDistributionAlertChannelsQuestion, + Options: channelOptions, + Default: channelDefaults, + }, + Validate: survey.Required, + }, + } + + answers := struct { + Name string `survey:"name"` + Frequency string `survey:"frequency"` + Definition string `survey:"definition"` + Channels []string `survey:"channels"` + Integrations []string `survey:"integrations"` + }{} + + if err = survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { + return + } + + var channelAnswers []string + for _, c := range answers.Channels { + channelAnswers = append(channelAnswers, channelMap[c]) + } + + reportDistribution = api.ReportDistributionUpdate{ + DistributionName: answers.Name, + AlertChannels: channelAnswers, + Frequency: answers.Frequency, + } + + // scope can only be either resource group or integration + if err = promptUpdateReportDistributionScope(&reportDistribution, + definition.Data.SubReportType, existing); err != nil { + return api.ReportDistributionUpdate{}, err + } + + // prompt optional fields + violations, err := promptUpdateReportDistributionViolations(existing) + if err != nil { + return api.ReportDistributionUpdate{}, err + } + + reportDistribution.Data.Violations = violations + + severities, err := promptUpdateReportDistributionSeverities(existing) + if err != nil { + return api.ReportDistributionUpdate{}, err + } + reportDistribution.Data.Severities = severities + + return +} + +func promptUpdateReportDistributionScope( + distribution *api.ReportDistributionUpdate, subReportType string, existing api.ReportDistribution, +) error { + distributionScope := "" + if err := survey.AskOne(&survey.Select{ + Message: CreateReportDistributionScopeQuestion, + Options: api.ReportDistributionScopes(), + }, &distributionScope); err != nil { + return err + } + + if distributionScope == api.ReportDistributionScopeCloudIntegration.String() { + if err := promptUpdateReportDistributionIntegration(distribution, subReportType, existing); err != nil { + return err + } + // clear resource group to avoid conflict + distribution.Data.ResourceGroups = []string{} + return nil + } else { + if err := promptUpdateReportDistributionResourceGroup(distribution, existing); err != nil { + return err + } + // clear integrations to avoid conflict + distribution.Data.Integrations = []api.ReportDistributionIntegration{} + return nil + } +} + +func promptUpdateReportDistributionResourceGroup( + distribution *api.ReportDistributionUpdate, existing api.ReportDistribution, +) error { + cli.StartProgress("Fetching list of resource groups...") + resourceGroups, err := cli.LwApi.V2.ResourceGroups.List() + cli.StopProgress() + if err != nil { + return err + } + + groupMap := make(map[string]string, len(resourceGroups.Data)) + groupIDMap := make(map[string]string, len(resourceGroups.Data)) + var ( + groupOptions []string + groupDefaults []string + groupAnswers []string + ) + + for _, group := range resourceGroups.Data { + groupMap[group.Name] = group.ID() + groupIDMap[group.ID()] = group.Name + groupOptions = append(groupOptions, group.Name) + } + + for _, group := range existing.Data.ResourceGroups { + groupDefaults = append(groupDefaults, groupIDMap[group]) + } + + var selectedGroups []string + if err = survey.AskOne(&survey.MultiSelect{ + Message: CreateReportDistributionResourceGroupsQuestion, + Options: groupOptions, + Default: groupDefaults, + }, &selectedGroups); err != nil { + return err + } + + for _, c := range selectedGroups { + groupAnswers = append(groupAnswers, groupMap[c]) + } + + distribution.Data.ResourceGroups = groupAnswers + return nil +} + +func promptUpdateReportDistributionIntegration( + distribution *api.ReportDistributionUpdate, reportType string, existing api.ReportDistribution, +) error { + var integrations []api.ReportDistributionIntegration + + switch reportType { + case api.ReportDefinitionSubTypeAws.String(): + if err := promptUpdateReportDistributionIntegrationsAws(&integrations, existing); err != nil { + return err + } + case api.ReportDefinitionSubTypeGcp.String(): + if err := promptReportDistributionIntegrationsGcp(&integrations); err != nil { + return err + } + case api.ReportDefinitionSubTypeAzure.String(): + if err := promptReportDistributionIntegrationsAzure(&integrations); err != nil { + return err + } + default: + return errors.Errorf("unsupported report definition type '%s'", + reportType) + } + + distribution.Data.Integrations = integrations + return nil +} + +func promptUpdateReportDistributionViolations(existing api.ReportDistribution) (violations []string, err error) { + addViolations := false + if err := survey.AskOne(&survey.Confirm{ + Message: UpdateReportDistributionAddViolationsQuestion, + }, &addViolations); err != nil { + return nil, err + } + + if addViolations { + if err := survey.AskOne(&survey.MultiSelect{ + Message: CreateReportDistributionViolationsQuestion, + Options: api.ReportDistributionViolations(), + Default: existing.Data.Violations, + }, &violations); err != nil { + return nil, err + } + } + + return violations, nil +} + +func promptUpdateReportDistributionSeverities(existing api.ReportDistribution) (sevs []string, err error) { + addSevs := false + if err = survey.AskOne(&survey.Confirm{ + Message: UpdateReportDistributionAddSeveritiesQuestion, + }, &addSevs); err != nil { + return nil, err + } + + if addSevs { + if err := survey.AskOne(&survey.MultiSelect{ + Message: CreateReportDistributionSeveritiesQuestion, + Options: strings.Split(lwseverity.ValidSeverities.String(), ","), + Default: existing.Data.Severities, + }, &sevs); err != nil { + return nil, err + } + } + + return sevs, nil +} + +func promptUpdateReportDistributionIntegrationsAws( + integrations *[]api.ReportDistributionIntegration, existing api.ReportDistribution, +) error { + cli.StartProgress("Fetching Aws Account IDs...") + accounts, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AwsCfgCloudAccount) + cli.StopProgress() + + if err != nil { + return err + } + + var integrationOptions []string + + for _, ca := range accounts.Data { + if caMap, ok := ca.GetData().(map[string]interface{}); ok { + integrationOptions = append(integrationOptions, caMap["awsAccountId"].(string)) + } + } + + var existingAccounts []string + for _, integration := range existing.Data.Integrations { + existingAccounts = append(existingAccounts, integration.AccountID) + } + + var integrationAnswers []string + if err = survey.AskOne(&survey.MultiSelect{ + Renderer: survey.Renderer{}, + Message: CreateReportDistributionIntegrationAwsQuestion, + Options: integrationOptions, + Default: existingAccounts, + }, &integrationAnswers); err != nil { + return err + } + + for _, integration := range integrationAnswers { + *integrations = append(*integrations, api.ReportDistributionIntegration{AccountID: integration}) + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_rules.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_rules.go new file mode 100644 index 000000000..77a8fa1fa --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/report_rules.go @@ -0,0 +1,412 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/fatih/structs" + "github.com/lacework/go-sdk/api" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +var ( + // report-rules command is used to manage lacework report rules + reportRulesCommand = &cobra.Command{ + Use: "report-rule", + Aliases: []string{"report-rules", "rr"}, + Short: "Manage report rules", + Long: `Manage report rules to route reports to one or more email alert channels. + +A report rule has four parts: + + 1. Email alert channel(s) that should receive the report + 2. One or more severities to include + 3. Resource group(s) containing the subset of your environment to consider + 4. Notification types containing which report information to send +`, + } + + // list command is used to list all lacework report rules + reportRulesListCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all report rules", + Long: "List all report rules configured in your Lacework account.", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + cli.StartProgress(" Fetching report rules...") + reportRules, err := cli.LwApi.V2.ReportRules.List() + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get report rules") + } + if len(reportRules.Data) == 0 { + msg := `There are no report rules configured in your account. + +Get started by configuring your report rules using the command: + + lacework report-rule create + +If you prefer to configure report rules via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Settings > Report Rules. +` + cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) + return nil + } + if cli.JSONOutput() { + return cli.OutputJSON(reportRules) + } + + var rows [][]string + for _, rule := range reportRules.Data { + rows = append(rows, []string{rule.Guid, rule.Filter.Name, rule.Filter.Status()}) + } + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "ENABLED"}, rows)) + return nil + }, + } + // show command is used to retrieve a lacework report rule by guid + reportRulesShowCommand = &cobra.Command{ + Use: "show ", + Short: "Show a report rule by ID", + Long: "Show a single report rule by it's ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var response api.ReportRuleResponse + cli.StartProgress(" Fetching report rule...") + + err := cli.LwApi.V2.ReportRules.Get(args[0], &response) + if err != nil { + cli.StopProgress() + return errors.Wrap(err, "unable to get report rule") + } + cli.StopProgress() + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + + reportRule := response.Data + headers := [][]string{ + []string{reportRule.Guid, reportRule.Filter.Name, reportRule.Filter.Status()}, + } + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "ENABLED"}, headers)) + cli.OutputHuman("\n") + cli.OutputHuman(buildReportRuleDetailsTable(reportRule)) + + return nil + }, + } + + // delete command is used to remove a lacework report rule by id + reportRulesDeleteCommand = &cobra.Command{ + Use: "delete ", + Short: "Delete a report rule", + Long: "Delete a single report rule by it's ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress(" Deleting report rule...") + err := cli.LwApi.V2.ReportRules.Delete(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to delete report rule") + } + cli.OutputHuman("The report rule with GUID %s was deleted\n", args[0]) + return nil + }, + } + + // create command is used to create a new lacework report rule + reportRulesCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new report rule", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, args []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + + response, err := promptCreateReportRule() + if err != nil { + return errors.Wrap(err, "unable to create report rule") + } + + cli.OutputHuman("The report rule was created with GUID %s\n", response.Data.Guid) + return nil + }, + } +) + +func init() { + // add the report-rule command + rootCmd.AddCommand(reportRulesCommand) + + // add sub-commands to the report-rule command + reportRulesCommand.AddCommand(reportRulesListCommand) + reportRulesCommand.AddCommand(reportRulesShowCommand) + reportRulesCommand.AddCommand(reportRulesCreateCommand) + reportRulesCommand.AddCommand(reportRulesDeleteCommand) +} + +func buildReportRuleDetailsTable(rule api.ReportRule) string { + var ( + details [][]string + notifications [][]string + updatedTime string + ) + severities := api.NewReportRuleSeveritiesFromIntSlice(rule.Filter.Severity).ToStringSlice() + + if nano, err := strconv.ParseInt(rule.Filter.CreatedOrUpdatedTime, 10, 64); err == nil { + updatedTime = time.Unix(nano/1000, 0).Format(time.RFC3339) + } + details = append(details, []string{"SEVERITIES", strings.Join(severities, ", ")}) + details = append(details, []string{"DESCRIPTION", rule.Filter.Description}) + details = append(details, []string{"UPDATED BY", rule.Filter.CreatedOrUpdatedBy}) + details = append(details, []string{"LAST UPDATED", updatedTime}) + + detailsTable := &strings.Builder{} + detailsTable.WriteString(renderOneLineCustomTable("ALERT RULE DETAILS", + renderCustomTable([]string{}, details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ), + ) + + notifcationsMap := rule.ReportNotificationTypes.ToMap() + // sort keys + keys := make([]string, 0, len(notifcationsMap)) + for k := range notifcationsMap { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, key := range keys { + notifications = append(notifications, + []string{key, cases.Title(language.English).String(strconv.FormatBool(notifcationsMap[key]))}, + ) + } + + detailsTable.WriteString(renderCustomTable([]string{"NOTIFICATION TYPES", "ENABLED"}, notifications, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + }), + ), + ) + detailsTable.WriteString("\n") + + if len(rule.EmailAlertChannels) > 0 { + channels := [][]string{{strings.Join(rule.EmailAlertChannels, "\n")}} + detailsTable.WriteString(renderCustomTable([]string{"EMAIL ALERT CHANNELS"}, channels, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + ) + detailsTable.WriteString("\n") + } + + if len(rule.Filter.ResourceGroups) > 0 { + resourceGroups := [][]string{{strings.Join(rule.Filter.ResourceGroups, "\n")}} + detailsTable.WriteString(renderCustomTable([]string{"RESOURCE GROUPS"}, resourceGroups, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + ) + } + + return detailsTable.String() +} + +func promptCreateReportRule() (api.ReportRuleResponse, error) { + channelList, channelMap := getEmailAlertChannels() + notificationFields := structs.Names(api.ReportRuleNotificationTypes{}) + notificationsMap := make(map[string]bool) + + if len(channelList) < 1 { + return api.ReportRuleResponse{}, errors.New("no email alert channels found.") + } + + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "description", + Prompt: &survey.Input{Message: "Description: "}, + Validate: survey.Required, + }, + { + Name: "channels", + Prompt: &survey.MultiSelect{ + Message: "Select email alert channels:", + Options: channelList, + }, + Validate: survey.Required, + }, + { + Name: "severities", + Prompt: &survey.MultiSelect{ + Message: "Select severities:", + Options: []string{"Critical", "High", "Medium", "Low", "Info"}, + }, + }, + { + Name: "notifications", + Prompt: &survey.MultiSelect{ + Message: "Select report notification types:", + Options: notificationFields, + }, + }, + } + + answers := struct { + Name string + Description string `survey:"description"` + Channels []string `survey:"channels"` + Severities []string `survey:"severities"` + ResourceGroups []string `survey:"resourceGroups"` + Notifications []string `survey:"notifications"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return api.ReportRuleResponse{}, err + } + + var channels []string + for _, channel := range answers.Channels { + channels = append(channels, channelMap[channel]) + } + + resourceGroups, resourceGroupMap := promptAddResourceGroupsToReportRule() + var groups []string + for _, group := range resourceGroups { + groups = append(groups, resourceGroupMap[group]) + } + + for _, n := range answers.Notifications { + notificationsMap[n] = true + } + + notifications := api.ReportRuleNotificationTypes{} + err = api.TransformReportRuleNotification(notificationsMap, ¬ifications) + if err != nil { + return api.ReportRuleResponse{}, err + } + + reportRule, err := api.NewReportRule( + answers.Name, + api.ReportRuleConfig{ + Description: answers.Description, + Severities: api.NewReportRuleSeverities(answers.Severities), + ResourceGroups: groups, + EmailAlertChannels: channels, + NotificationTypes: api.ReportRuleNotifications{notifications}, + }) + + if err != nil { + return api.ReportRuleResponse{}, err + } + + cli.StartProgress(" Creating report rule...") + defer cli.StopProgress() + + return cli.LwApi.V2.ReportRules.Create(reportRule) +} + +func getEmailAlertChannels() ([]string, map[string]string) { + cli.StartProgress("") + defer cli.StopProgress() + response, err := cli.LwApi.V2.AlertChannels.List() + + if err != nil { + return nil, nil + } + var items = make(map[string]string) + var channels = make([]string, 0) + for _, i := range response.Data { + if i.AlertChannelType() == api.EmailUserAlertChannelType { + displayName := fmt.Sprintf("%s - %s", i.ID(), i.Name) + channels = append(channels, displayName) + items[displayName] = i.ID() + } + } + + return channels, items +} + +func promptAddResourceGroupsToReportRule() ([]string, map[string]string) { + addResourceGroups := false + err := survey.AskOne(&survey.Confirm{ + Message: "Add Resource Groups to Report Rule?", + }, &addResourceGroups) + + if err != nil { + return nil, nil + } + + if addResourceGroups { + var groups []string + groupList, groupMap := getResourceGroups() + + err = survey.AskOne(&survey.MultiSelect{ + Message: "Select Resource Groups:", + Options: groupList, + }, &groups) + + if err != nil { + return nil, nil + } + return groups, groupMap + } + return nil, nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/resource_group_v2.go b/vendor/github.com/lacework/go-sdk/cli/cmd/resource_group_v2.go new file mode 100644 index 000000000..8002c82c7 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/resource_group_v2.go @@ -0,0 +1,94 @@ +// +// Author:: Zeki Sherif() +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" +) + +func createResourceGroup(resourceType string) error { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "description", + Prompt: &survey.Input{Message: "Description: "}, + Validate: survey.Required, + }, + { + Name: "query", + Prompt: inputRGQueryFromEditor(resourceType), + Validate: survey.Required, + }, + } + + answers := struct { + Name string `survey:"name"` + Description string `survey:"description"` + Query string `survey:"query"` + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + + if err != nil { + return err + } + + var rgQuery api.RGQuery + err = json.Unmarshal([]byte(answers.Query), &rgQuery) + if err != nil { + return err + } + + groupType, isValid := api.FindResourceGroupType(resourceType) + if !isValid { + // This should never reach this. The type is controlled by us in cmd/resource_groups + return errors.New("internal error") + } + resourceGroup := api.NewResourceGroup(answers.Name, groupType, answers.Description, &rgQuery) + cli.StartProgress(" Creating resource group...") + _, err = cli.LwApi.V2.ResourceGroups.Create(resourceGroup) + cli.StopProgress() + return err +} + +func inputRGQueryFromEditor(resourceType string) *survey.Editor { + iType, _ := api.FindResourceGroupType(resourceType) + + prompt := &survey.Editor{ + Message: fmt.Sprintf("Type a query for the new %s Resource Group", resourceType), + FileName: "resourceGroupQuery*.json", + Help: "Refer to https://lwdocs-rg2.netlify.app/api/api-resource-group/ for examples of a query", + } + prompt.Default = iType.QueryTemplate() + prompt.HideDefault = true + prompt.AppendDefault = true + + return prompt +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/resource_groups.go b/vendor/github.com/lacework/go-sdk/cli/cmd/resource_groups.go new file mode 100644 index 000000000..e3667c7fd --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/resource_groups.go @@ -0,0 +1,254 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strconv" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // resource-groups command is used to manage lacework resource groups + resourceGroupsCommand = &cobra.Command{ + Use: "resource-group", + Aliases: []string{"resource-groups", "rg"}, + Short: "Manage resource groups", + Long: "Manage Lacework-identifiable assets via the use of resource groups.", + } + + // list command is used to list all lacework resource groups + resourceGroupsListCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all resource groups", + Long: "List all resource groups configured in your Lacework account.", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + resourceGroups, err := cli.LwApi.V2.ResourceGroups.List() + if err != nil { + return errors.Wrap(err, "unable to get resource groups") + } + if len(resourceGroups.Data) == 0 { + msg := `There are no resource groups configured in your account. + +Get started by integrating your resource groups to manage alerting using the command: + + lacework resource-group create + +If you prefer to configure resource groups via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Settings > Resource Groups. +` + cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) + return nil + } + + groups := make([]resourceGroup, 0) + for _, g := range resourceGroups.Data { + + groups = append(groups, resourceGroup{ + Id: g.ResourceGroupGuid, + ResType: g.Type, + Name: g.Name, + Enabled: g.Enabled, + IsDefaultBoolean: g.IsDefaultBoolean, + Query: g.Query, + }) + } + + if cli.JSONOutput() { + jsonOut := struct { + Groups []resourceGroup `json:"resource_groups"` + }{Groups: groups} + return cli.OutputJSON(jsonOut) + } + + rows := [][]string{} + for _, g := range groups { + rows = append(rows, []string{g.Id, g.ResType, g.Name, strconv.Itoa(g.Enabled), + strconv.FormatBool(*g.IsDefaultBoolean)}) + } + + cli.OutputHuman(renderSimpleTable([]string{"RESOURCE GROUP ID", "TYPE", "NAME", "STATUS", "DEFAULT"}, rows)) + return nil + }, + } + // show command is used to retrieve a lacework resource group by resource id + resourceGroupsShowCommand = &cobra.Command{ + Use: "show ", + Short: "Get resource group by ID", + Long: "Get a single resource group by it's resource group ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var response api.ResourceGroupResponse + err := cli.LwApi.V2.ResourceGroups.Get(args[0], &response) + + if err != nil { + return errors.Wrap(err, "unable to get resource group") + } + + group := resourceGroup{ + Id: response.Data.ResourceGroupGuid, + ResType: response.Data.Type, + Name: response.Data.Name, + Enabled: response.Data.Enabled, + IsDefaultBoolean: response.Data.IsDefaultBoolean, + Query: response.Data.Query, + Description: response.Data.Description, + UpdatedBy: response.Data.UpdatedBy, + UpdatedTime: response.Data.UpdatedTime, + CreatedTime: response.Data.CreatedTime, + CreatedBy: response.Data.CreatedBy, + } + + if cli.JSONOutput() { + jsonOut := struct { + Group resourceGroup `json:"resource_group"` + }{Group: group} + return cli.OutputJSON(jsonOut) + } + + var groupCommon [][]string + + groupCommon = append(groupCommon, + []string{group.Id, group.ResType, group.Name, group.Description, strconv.Itoa(group.Enabled), + strconv.FormatBool(*group.IsDefaultBoolean), group.CreatedBy, group.CreatedTime.UTC().String(), + group.UpdatedBy, group.UpdatedTime.UTC().String()}, + ) + cli.OutputHuman(renderSimpleTable([]string{"RESOURCE GROUP ID", "TYPE", "NAME", "DESCRIPTION", "STATE", + "DEFAULT", "CREATED BY", "CREATED TIME", "UPDATED BY", "UPDATED TIME"}, groupCommon)) + + return nil + }, + } + + // delete command is used to remove a lacework resource group by resource id + resourceGroupsDeleteCommand = &cobra.Command{ + Use: "delete ", + Short: "Delete a resource group", + Long: "Delete a single resource group by it's resource group ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + err := cli.LwApi.V2.ResourceGroups.Delete(args[0]) + if err != nil { + return errors.Wrap(err, "unable to delete resource group") + } + + cli.OutputHuman("The resource group was deleted.\n") + return nil + }, + } + + // create command is used to create a new lacework resource group + resourceGroupsCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new resource group", + Long: "Creates a new single resource group.", + RunE: func(_ *cobra.Command, args []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + + err := promptCreateResourceGroup() + if err != nil { + return errors.Wrap(err, "unable to create resource group") + } + + cli.OutputHuman("The resource group was created.\n") + return nil + }, + } +) + +func promptCreateResourceGroup() error { + + resourceGroupOptions := []string{ + "AWS", + "AZURE", + "CONTAINER", + "GCP", + "MACHINE", + "OCI", + "KUBERNETES", + } + + var ( + group = "" + prompt = &survey.Select{ + Message: "Choose a resource group type to create: ", + Options: resourceGroupOptions, + } + err = survey.AskOne(prompt, &group) + ) + if err != nil { + return err + } + + switch group { + case "AWS": + return createResourceGroup("AWS") + case "AZURE": + return createResourceGroup("AZURE") + case "GCP": + return createResourceGroup("GCP") + case "CONTAINER": + return createResourceGroup("CONTAINER") + case "MACHINE": + return createResourceGroup("MACHINE") + case "OCI": + return createResourceGroup("OCI") + case "KUBERNETES": + return createResourceGroup("KUBERNETES") + default: + return errors.New("unknown resource group type") + } +} + +func init() { + // add the resource-group command + rootCmd.AddCommand(resourceGroupsCommand) + + // add sub-commands to the resource-group command + resourceGroupsCommand.AddCommand(resourceGroupsListCommand) + resourceGroupsCommand.AddCommand(resourceGroupsShowCommand) + resourceGroupsCommand.AddCommand(resourceGroupsCreateCommand) + resourceGroupsCommand.AddCommand(resourceGroupsDeleteCommand) +} + +type resourceGroup struct { + Id string `json:"resourceGroupGuid"` + ResType string `json:"type"` + Name string `json:"name"` + Enabled int `json:"enabled"` + IsDefaultBoolean *bool `json:"isDefaultBoolean"` + Query *api.RGQuery `json:"query"` + Description string `json:"description,omitempty"` + UpdatedTime *time.Time `json:"updatedTime,omitempty"` + UpdatedBy string `json:"updatedBy,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + CreatedTime *time.Time `json:"createdTime,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/root.go b/vendor/github.com/lacework/go-sdk/cli/cmd/root.go new file mode 100644 index 000000000..79b50f71f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/root.go @@ -0,0 +1,431 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/fatih/color" + homedir "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" + "github.com/spf13/cast" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/lacework/go-sdk/lwcomponent" + "github.com/lacework/go-sdk/lwlogger" +) + +var ( + // the global cli state with defaults + cli = NewDefaultState() + + // rootCmd represents the base command when called without any subcommands + rootCmd = &cobra.Command{ + Use: "lacework", + Short: "A tool to manage the Lacework cloud security platform.", + DisableAutoGenTag: true, + SilenceErrors: true, + Long: `The Lacework Command Line Interface is a tool that helps you manage the +Lacework cloud security platform. Use it to manage compliance reports, +external integrations, vulnerability scans, and other operations. + +Start by configuring the Lacework CLI with the command: + + lacework configure + +This will prompt you for your Lacework account and a set of API access keys.`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if isComponent(cmd.Annotations) { + return componentPersistentPreRun(cmd, args) + } + return cliPersistentPreRun(cmd, args) + }, + PersistentPostRunE: func(cmd *cobra.Command, _ []string) error { + // skip daily version check if the user is running the version command + if cmd.Use == "version" { + return nil + } + + // run the daily version check but do not fail if we couldn't check + // this is not a critical part of the CLI and we do not want to impact + // cusomters workflows or CI systems + if err := dailyVersionCheck(); err != nil { + cli.Log.Debugw("unable to run daily version check", "error", err) + } + + return nil + }, + } +) + +func cliPersistentPreRun(cmd *cobra.Command, args []string) error { + cli.Log.Debugw("updating honeyvent", "dataset", HoneyDataset) + cli.Event.Command = cmd.CommandPath() + cli.Event.Args = args + cli.Event.Flags = parseFlags(os.Args[1:]) + + switch cmd.Use { + case "help [command]", "configure", "version", "docs ", "generate-pkg-manifest": + return nil + default: + if cmd.HasParent() { + switch cmd.Parent().Use { + case "configure", "completion": + // @afiune no need to create a client for any configure + // command or any completion command + return nil + } + } + if err := cli.NewClient(); err != nil { + if !strings.Contains(err.Error(), "Invalid Account") { + return err + } + + if err := cli.Migrations(); err != nil { + return err + } + } + } + cli.SendHoneyvent() + return nil +} + +func componentPersistentPreRun(cmd *cobra.Command, args []string) error { + cli.Event.Command = cmd.CommandPath() + cli.Event.Component = cmd.Use + cli.Event.Flags = parseFlags(os.Args[1:]) + defer cli.SendHoneyvent() + + // For components, we disable flag parsing, therefore we + // split args into those handled by the CLI and those + // we pass to the component manually + cli.componentParser.parseArgs(cmd.Flags(), args) + cli.Event.Args = cli.componentParser.componentArgs + err := cmd.Flags().Parse(cli.componentParser.cliArgs) + + // We call initConfig() again after global flags have been parsed. + initConfig() + + if err != nil { + cli.Event.Error = err.Error() + cli.Log.Debugw("unable to parse global flags", "error", err, + "provided_flags", cli.componentParser.cliArgs) + } + + cli.Log.Debugw("honeyvent updated", "dataset", HoneyDataset) + + return cli.NewClient() +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() (err error) { + defer func() { + switch err := err.(type) { + case *vulnerabilityPolicyError: + exitwithCode(err, err.ExitCode) + case *queryFailonError: + exitwithCode(err, err.ExitCode) + case *lwcomponent.RunError: + // by default, all our components should display the error to + // the end user, which is why we don't output it, but we still + // exit the main program with the exit code from the component + os.Exit(err.ExitCode) + } + }() + defer cli.Wait() + + setupRootHelpCommand() + + // first, verify if the user provided a command to execute, + // if no command was provided, only print out the usage message + if noCommandProvided() { + errcheckWARN(rootCmd.Help()) + os.Exit(127) + } + + if err = rootCmd.Execute(); err != nil { + // send a new error event to Honeycomb + cli.Event.Error = err.Error() + cli.SendHoneyvent() + } + + return +} + +func setupRootHelpCommand() { + // We want 'lacework help iac org list' to invoke 'iac help org list' + // instead of just showing help for lacework. The command in question is + // the default help command for the root command, so we peek at the + // target and if it's a component then invoke the component. + rootCmd.InitDefaultHelpCmd() + helpCommand, _, _ := rootCmd.Find([]string{"help"}) + defaultRunHelp := helpCommand.Run + helpCommand.Run = nil + helpCommand.RunE = func(cmd *cobra.Command, args []string) error { + target, _, _ := rootCmd.Find(args) + if target != nil && isComponent(target.Annotations) { + f, ok := cli.LwComponents.GetComponent(target.Use) + if ok { + envs := []string{ + fmt.Sprintf("LW_COMPONENT_NAME=%s", target.Use), + } + helpArgs := append([]string{"help"}, args[1:]...) + return f.RunAndOutput(helpArgs, envs...) + } + } + defaultRunHelp(cmd, args) + return nil + } +} + +func init() { + // initialize cobra + cobra.OnInitialize(initConfig) + + // Note - do not add new global flags without considering the consequences + // on components. These global flags will be consumed by the CLI and + // NOT passed onto a component. A new global flag runs the risk of not + // allowing a component to accept a flag that it previously was expecting. + rootCmd.PersistentFlags().Bool("debug", false, + "turn on debug logging", + ) + rootCmd.PersistentFlags().Bool("nocolor", false, + "turn off colors", + ) + rootCmd.PersistentFlags().Bool("nocache", false, + "turn off caching", + ) + rootCmd.PersistentFlags().Bool("noninteractive", false, + "turn off interactive mode (disable spinners, prompts, etc.)", + ) + rootCmd.PersistentFlags().Bool("json", false, + "switch commands output from human-readable to json format", + ) + rootCmd.PersistentFlags().StringP("profile", "p", "", + "switch between profiles configured at ~/.lacework.toml", + ) + rootCmd.PersistentFlags().StringP("api_key", "k", "", + "access key id", + ) + rootCmd.PersistentFlags().StringP("api_secret", "s", "", + "secret access key", + ) + rootCmd.PersistentFlags().String("api_token", "", + "access token (replaces the use of api_key and api_secret)", + ) + rootCmd.PersistentFlags().StringP("account", "a", "", + "account subdomain of URL (i.e. .lacework.net)", + ) + rootCmd.PersistentFlags().String("subaccount", "", + "sub-account name inside your organization (org admins only)", + ) + rootCmd.PersistentFlags().Bool("organization", false, + "access organization level data sets (org admins only)", + ) + + errcheckWARN(viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))) + errcheckWARN(viper.BindPFlag("nocolor", rootCmd.PersistentFlags().Lookup("nocolor"))) + errcheckWARN(viper.BindPFlag("nocache", rootCmd.PersistentFlags().Lookup("nocache"))) + errcheckWARN(viper.BindPFlag("noninteractive", rootCmd.PersistentFlags().Lookup("noninteractive"))) + errcheckWARN(viper.BindPFlag("json", rootCmd.PersistentFlags().Lookup("json"))) + errcheckWARN(viper.BindPFlag("profile", rootCmd.PersistentFlags().Lookup("profile"))) + errcheckWARN(viper.BindPFlag("account", rootCmd.PersistentFlags().Lookup("account"))) + errcheckWARN(viper.BindPFlag("api_key", rootCmd.PersistentFlags().Lookup("api_key"))) + errcheckWARN(viper.BindPFlag("api_secret", rootCmd.PersistentFlags().Lookup("api_secret"))) + errcheckWARN(viper.BindPFlag("api_token", rootCmd.PersistentFlags().Lookup("api_token"))) + errcheckWARN(viper.BindPFlag("subaccount", rootCmd.PersistentFlags().Lookup("subaccount"))) + errcheckWARN(viper.BindPFlag("organization", rootCmd.PersistentFlags().Lookup("organization"))) + + cobra.AddTemplateFunc("isComponent", isComponent) + cobra.AddTemplateFunc("hasInstalledCommands", hasInstalledCommands) + rootCmd.SetUsageTemplate(usageTemplate()) +} + +func usageTemplate() string { + // nolint:lll + return `Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: +{{.Example}}{{end}}{{if .HasAvailableSubCommands}} + +Available Commands:{{range .Commands}}{{if not (isComponent .Annotations)}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{if (and hasInstalledCommands (not .HasParent))}} + +Commands from components:{{range .Commands}}{{if isComponent .Annotations}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} + +Global Flags: +{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} + +Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` +} + +// initConfig reads in config file and ENV variables if set +func initConfig() { + // Find home directory + home, err := homedir.Dir() + errcheckEXIT(err) + + // Search config in home directory with name ".lacework" (without extension) + viper.AddConfigPath(home) + viper.SetConfigName(".lacework") + + viper.SetConfigType("toml") // set TOML as the config format + viper.SetEnvPrefix("LW") // set prefix for all env variables LW_ABC + viper.AutomaticEnv() // read in environment variables that match + + logLevel := "" + if viper.GetBool("debug") { + logLevel = "DEBUG" + } + + // initialize a Lacework logger + cli.Log = lwlogger.New(logLevel).Sugar() + + if viper.GetBool("nocolor") { + cli.Log.Info("turning off colors") + cli.JsonF.DisabledColor = true + color.NoColor = true + os.Setenv("NO_COLOR", "true") + } + + if b := viper.Get("noninteractive"); b != nil { + if cast.ToBool(b) { + cli.NonInteractive() + } else { + cli.Interactive() + } + } + + if viper.GetBool("nocache") { + cli.NoCache() + } + + if viper.GetBool("json") { + cli.EnableJSONOutput() + } + + // try to read config file + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // the config file was not found; ignore error + cli.Log.Debugw("configuration file not found") + } else { + // the config file was found but another error was produced + exitwith(errors.Wrap(err, "unable to read in config file ~/.lacework.toml")) + } + } else { + cli.Log.Debugw("using configuration file", + "path", viper.ConfigFileUsed(), + ) + } + + // initialize cli cache library + cli.InitCache() + + // get the profile passed as a parameter or environment variable + // if any, set it into the CLI state, that will trigger to load the + // state, if no profile was specified just load the default state + if p := viper.GetString("profile"); len(p) != 0 { + err = cli.SetProfile(p) + } else if p, cacheErr := cli.Cache.Read("global/profile"); cacheErr == nil { + cli.Log.Debugw("loading profile from cache", "profile", string(p)) + err = cli.SetProfile(string(p)) + } else { + err = cli.LoadState() + } + + if err != nil { + if isCommand("configure") { + cli.Log.Debugw( + "error ignored", + "reason", "running configure cmd", + "error", err, + ) + } else { + // TODO @afiune figure out how to propagate this to main() + exitwith(err) + } + } +} + +// isCommand checks the overall arguments passed to the lacework cli +// and returns true if the provided command name is the one running +func isCommand(cmd string) bool { + if len(os.Args) <= 1 { + return false + } + + if os.Args[1] == cmd { + return true + } + + return false +} + +// noCommandProvided checks if a command or argument was provided +func noCommandProvided() bool { + return len(os.Args) <= 1 +} + +// errcheckEXIT is a simple macro to check Golang errors, if the provided +// error is nil, it doesn't do anything, but if the error has something, +// it exits the program +func errcheckEXIT(err error) { + if err != nil { + exitwith(err) + } +} + +// errcheckWARN is similar to errcheckEXIT but it doesn't exit the program, +// it only prints a WARNING message to the user, useful for those cases where +// we know there won't be aproblem but the linter still asks to check all errors +func errcheckWARN(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "WARN %s\n", err) + } +} + +// exitwith prints out an error message and exits the program with exit code 1 +func exitwith(err error) { + exitwithCode(err, 1) +} + +// exitwithCode prints out an error message and exits the program with +// the provided exit code +func exitwithCode(err error, code int) { + fmt.Fprintf(os.Stderr, "\nERROR %s\n", err) + os.Exit(code) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions.go new file mode 100644 index 000000000..c9ad322f8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions.go @@ -0,0 +1,271 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/fatih/color" + + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/lacework/go-sdk/api" + "github.com/spf13/cobra" + "golang.org/x/exp/slices" +) + +var ( + // top-level suppressions command + suppressionsCommand = &cobra.Command{ + Use: "suppressions", + Hidden: true, + Aliases: []string{"suppression", "sup", "sups"}, + Short: "Manage legacy suppressions", + Long: "Manage legacy suppressions", + } + + // suppressionsAwsCmd represents the aws sub-command inside the suppressions command + suppressionsAwsCmd = &cobra.Command{ + Use: "aws", + Short: "Manage legacy suppressions for aws", + } + + // suppressionsAzureCmd represents the aws sub-command inside the suppressions command + suppressionsAzureCmd = &cobra.Command{ + Use: "azure", + Short: "Manage legacy suppressions for azure", + } + + // suppressionsGcpCmd represents the aws sub-command inside the suppressions command + suppressionsGcpCmd = &cobra.Command{ + Use: "gcp", + Short: "Manage legacy suppressions for gcp", + } +) + +func init() { + rootCmd.AddCommand(suppressionsCommand) + // aws + suppressionsCommand.AddCommand(suppressionsAwsCmd) + suppressionsAwsCmd.AddCommand(suppressionsListAwsCmd) + suppressionsAwsCmd.AddCommand(suppressionsMigrateAwsCmd) + // azure + suppressionsCommand.AddCommand(suppressionsAzureCmd) + suppressionsAzureCmd.AddCommand(suppressionsListAzureCmd) + suppressionsAzureCmd.AddCommand(suppressionsMigrateAzureCmd) + // gcp + suppressionsCommand.AddCommand(suppressionsGcpCmd) + suppressionsGcpCmd.AddCommand(suppressionsListGcpCmd) + suppressionsGcpCmd.AddCommand(suppressionsMigrateGcpCmd) +} + +func autoConvertSuppressions(convertedPolicyExceptions []map[string]api.PolicyException) { + cli.StartProgress("Creating policy exceptions...") + for _, exceptionMap := range convertedPolicyExceptions { + for policyId, exception := range exceptionMap { + response, err := cli.LwApi.V2.Policy.Exceptions.Create(policyId, exception) + if err != nil { + cli.Log.Debug(err, "unable to create exception") + cli.OutputHuman(color.RedString( + "Error creating policy exception to create exception. %s"), + err) + continue + } + cli.OutputHuman("Exception created for PolicyId: %s - ExceptionId: %s\n\n", + color.GreenString(policyId), color.BlueString(response.Data.ExceptionID)) + } + } + + cli.StopProgress() +} + +func printPayloadsText(payloadsText []string) { + if len(payloadsText) >= 1 { + cli.OutputHuman(color.YellowString("#### Legacy Suppressions --> Exceptions payloads\n\n")) + for _, payload := range payloadsText { + cli.OutputHuman(color.GreenString("%s \n\n", payload)) + } + } else { + cli.OutputHuman("No legacy suppressions found that could be migrated\n") + } +} + +func printConvertedSuppressions(convertedSuppressions []map[string]api.PolicyException) { + if len(convertedSuppressions) >= 1 { + cli.OutputHuman(color.YellowString("#### Converted legacy suppressions in Policy Exception" + + " format" + + "\n")) + for _, exception := range convertedSuppressions { + err := cli.OutputJSON(exception) + if err != nil { + return + } + } + colorizeR := color.New(color.FgRed, color.Bold) + cli.OutputHuman(colorizeR.Sprintf("WARNING: Before continuing, " + + "please thoroughly inspect the above exceptions to ensure they are valid and" + + " required. By continuing, you accept liability for any compliance violations" + + " missed as a result of the above exceptions!\n\n")) + + } +} + +func printDiscardedSuppressions(discardedSuppressions []map[string]api.SuppressionV2) { + if len(discardedSuppressions) >= 1 { + cli.OutputHuman(color.YellowString("#### Discarded legacy suppressions\n")) + for _, suppression := range discardedSuppressions { + err := cli.OutputJSON(suppression) + if err != nil { + return + } + } + } +} + +func convertSupCondition(supConditions []string, fieldKey string, + policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint { + if len(supConditions) >= 1 && slices.Contains( + policyIdExceptionsTemplate, fieldKey) { + + var condition []any + // verify for aws: + // if "ALL_ACCOUNTS" OR "ALL_REGIONS" is in the suppression condition slice + // verify for azure: + // if "ALL_TENANTS" OR "ALL_SUBSCRIPTIONS" is in the suppression condition slice + // verify for gcp: + // if "ALL_ORGANIZATIONS" OR "ALL_PROJECTS" is in the suppression condition slice + // if so we should ignore the supplied conditions and replace with a wildcard * + if (slices.Contains(supConditions, "ALL_ACCOUNTS") && fieldKey == "accountIds") || + (slices.Contains(supConditions, "ALL_REGIONS") && fieldKey == "regionNames") { + condition = append(condition, "*") + } else if (slices.Contains(supConditions, "ALL_ORGANIZATIONS") && fieldKey == "organizations") || + (slices.Contains(supConditions, "ALL_PROJECTS") && fieldKey == "projects") { + condition = append(condition, "*") + } else if (slices.Contains(supConditions, "ALL_TENANTS") && fieldKey == "tenants") || + (slices.Contains(supConditions, "ALL_SUBSCRIPTIONS") && fieldKey == "subscriptions") { + condition = append(condition, "*") + } else if fieldKey == "resourceNames" || fieldKey == "resourceName" { + condition = convertResourceNamesSupConditions(supConditions) + } else { + condition = convertToAnySlice(supConditions) + } + + return api.PolicyExceptionConstraint{ + FieldKey: fieldKey, + FieldValues: condition, + } + } + return api.PolicyExceptionConstraint{} +} + +func convertResourceNamesSupConditions(supConditions []string) []any { + var conditions []any + for _, condition := range supConditions { + ok := arn.IsARN(condition) + // If the legacy suppression resourceNames field contains an ARN, we should parse and pull + // out the resource name. ARNs are not supported in policy exceptions + if ok { + parsedEntry, _ := arn.Parse(condition) + condition = parsedEntry.Resource + } + conditions = append(conditions, condition) + } + return conditions +} + +func convertGcpResourceNameSupConditions(supConditions []string, fieldKey string, + policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint { + if len(supConditions) >= 1 && slices.Contains( + policyIdExceptionsTemplate, fieldKey) { + var conditions []any + for _, condition := range supConditions { + // skip this logic if we already have a wildcard + if condition != "*" { + // It appears that for GCP, the resourceName field for policy exceptions is in fact expecting + // users to provider the full GCP resource_id. + // Example resourceId: + // => //compute.googleapis.com/projects/gke-project-01-c8403ba1/zones/us-central1-a/instances/squid-proxy + // This was not the case for legacy suppressions and in most cases it's unlikely that the + // users will have provided this. Instead, we are more likely to have + // the resource name provided. To cover this scenario we prepend the resource name + // from the legacy suppression with "*/" to make it match the resource name while + // wildcarding the rest of the resourceId + condition = "*/" + condition + } + conditions = append(conditions, condition) + } + return api.PolicyExceptionConstraint{ + FieldKey: fieldKey, + FieldValues: conditions, + } + } + return api.PolicyExceptionConstraint{} +} + +func convertSupConditionTags(supCondition []map[string]string, fieldKey string, + policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint { + if len(supCondition) >= 1 && slices.Contains( + policyIdExceptionsTemplate, fieldKey) { + + // api.PolicyExceptionConstraint expects []any for the FieldValues + // Therefore we need to take the supCondition []map[string]string and append each map to + // the new convertedTags []any var + var convertedTags []any + for _, tagMap := range supCondition { + convertedTags = append(convertedTags, tagMap) + } + + return api.PolicyExceptionConstraint{ + FieldKey: fieldKey, + FieldValues: convertedTags, + } + } + return api.PolicyExceptionConstraint{} +} + +func getPoliciesExceptionConstraintsMap() map[string][]string { + // get a list of all policies and parse the valid exception constraints and return a map of + // {"": []} + policies, err := cli.LwApi.V2.Policy.List() + if err != nil { + return nil + } + + policiesSupportedConstraints := make(map[string][]string) + for _, policy := range policies.Data { + exceptionConstraints := getPolicyExceptionConstraintsSlice(policy.ExceptionConfiguration) + policiesSupportedConstraints[policy.PolicyID] = exceptionConstraints + } + + return policiesSupportedConstraints +} + +func convertToAnySlice(slice []string) []any { + s := make([]interface{}, len(slice)) + for i, v := range slice { + s[i] = v + } + return s +} + +func updateDiscardedSupConditionsComments(suppressionInfo api.SuppressionV2, comment string) api.SuppressionV2 { + var updatedSupInfo api.SuppressionV2 + for _, suppression := range suppressionInfo.SuppressionConditions { + suppression.Comment = comment + updatedSupInfo.SuppressionConditions = append(updatedSupInfo.SuppressionConditions, suppression) + } + return updatedSupInfo +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_aws.go new file mode 100644 index 000000000..cded4d9c7 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_aws.go @@ -0,0 +1,437 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/fatih/color" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // https://docs.lacework.com/console/aws-compliance-policy-exceptions-criteria#lacework-custom-policies-for-aws-iam + // https://docs.lacework.com/console/cis-aws-140-benchmark-report#identity-and-access-management + // old ID to new ID mapping, using the old Constraints with the hope they match the new Constraints + awsEquivalencesMap = map[string]string{ + "AWS_CIS_1_2": "lacework-global-39", + "AWS_CIS_1_3": "lacework-global-41", + "AWS_CIS_1_4": "lacework-global-43", + "AWS_CIS_1_9": "lacework-global-37", + "AWS_CIS_1_10": "lacework-global-38", + "AWS_CIS_1_12": "lacework-global-34", + "AWS_CIS_1_13": "lacework-global-35", + "AWS_CIS_1_14": "lacework-global-69", + "AWS_CIS_1_15": "lacework-global-33", + "AWS_CIS_1_16": "lacework-global-44", // no iam policies to users + //"AWS_CIS_1_19": "lacework-global-31", manual + //"AWS_CIS_1_20": "lacework-global-32", manual + //"AWS_CIS_1_21": "lacework-global-70", manual + "AWS_CIS_1_22": "lacework-global-46", + "AWS_CIS_1_23": "lacework-global-40", + "AWS_CIS_1_24": "lacework-global-45", + "AWS_CIS_2_1": "lacework-global-53", + "AWS_CIS_2_2": "lacework-global-75", + "AWS_CIS_2_3": "lacework-global-54", // s3 bucket cloudtrail log + "AWS_CIS_2_4": "lacework-global-55", + "AWS_CIS_2_5": "lacework-global-76", + "AWS_CIS_2_6": "lacework-global-56", // s3 bucket cloudtrail log + "AWS_CIS_2_7": "lacework-global-77", + "AWS_CIS_2_8": "lacework-global-78", + "AWS_CIS_2_9": "lacework-global-79", + "AWS_CIS_3_1": "lacework-global-57", + "AWS_CIS_3_2": "lacework-global-58", + "AWS_CIS_3_3": "lacework-global-59", + "AWS_CIS_3_4": "lacework-global-60", + "AWS_CIS_3_5": "lacework-global-61", + "AWS_CIS_3_6": "lacework-global-82", + "AWS_CIS_3_7": "lacework-global-83", + "AWS_CIS_3_8": "lacework-global-62", + "AWS_CIS_3_9": "lacework-global-84", + "AWS_CIS_3_10": "lacework-global-85", + "AWS_CIS_3_11": "lacework-global-86", + "AWS_CIS_3_12": "lacework-global-63", + "AWS_CIS_3_13": "lacework-global-64", + "AWS_CIS_3_14": "lacework-global-65", + "AWS_CIS_4_1": "lacework-global-68", + "AWS_CIS_4_2": "lacework-global-68", + "AWS_CIS_4_3": "lacework-global-79", + "AWS_CIS_4_4": "lacework-global-87", + "LW_S3_1": "lacework-global-130", + "LW_S3_2": "lacework-global-131", + "LW_S3_3": "lacework-global-132", + "LW_S3_4": "lacework-global-133", + "LW_S3_5": "lacework-global-134", + "LW_S3_6": "lacework-global-135", + "LW_S3_7": "lacework-global-136", + "LW_S3_8": "lacework-global-137", + "LW_S3_9": "lacework-global-138", + "LW_S3_10": "lacework-global-139", + "LW_S3_11": "lacework-global-140", + "LW_S3_12": "lacework-global-94", + "LW_S3_13": "lacework-global-95", + "LW_S3_14": "lacework-global-217", + "LW_S3_15": "lacework-global-96", + "LW_S3_16": "lacework-global-97", + "LW_S3_18": "lacework-global-98", + "LW_S3_19": "lacework-global-99", + "LW_S3_20": "lacework-global-100", + "LW_S3_21": "lacework-global-101", + "LW_AWS_IAM_1": "lacework-global-115", + "LW_AWS_IAM_2": "lacework-global-116", + "LW_AWS_IAM_3": "lacework-global-117", + "LW_AWS_IAM_4": "lacework-global-118", + "LW_AWS_IAM_5": "lacework-global-119", + "LW_AWS_IAM_6": "lacework-global-120", + "LW_AWS_IAM_7": "lacework-global-121", + "LW_AWS_IAM_11": "lacework-global-181", // non-root user + "LW_AWS_IAM_12": "lacework-global-142", + "LW_AWS_IAM_13": "lacework-global-141", + "LW_AWS_IAM_14": "lacework-global-105", + // "AWS_CIS_4_5" : "88 (Manual)", + "LW_AWS_NETWORKING_1": "lacework-global-227", // sec-group + "LW_AWS_NETWORKING_2": "lacework-global-145", // network acl + "LW_AWS_NETWORKING_3": "lacework-global-146", // network acl + "LW_AWS_NETWORKING_4": "lacework-global-147", + "LW_AWS_NETWORKING_5": "lacework-global-148", + "LW_AWS_NETWORKING_6": "lacework-global-149", + "LW_AWS_NETWORKING_7": "lacework-global-228", + "LW_AWS_NETWORKING_8": "lacework-global-229", + "LW_AWS_NETWORKING_9": "lacework-global-230", + "LW_AWS_NETWORKING_10": "lacework-global-231", + "LW_AWS_NETWORKING_11": "lacework-global-199", + "LW_AWS_NETWORKING_12": "lacework-global-150", + "LW_AWS_NETWORKING_13": "lacework-global-151", + "LW_AWS_NETWORKING_14": "lacework-global-152", + "LW_AWS_NETWORKING_15": "lacework-global-153", + "LW_AWS_NETWORKING_16": "lacework-global-225", + "LW_AWS_NETWORKING_17": "lacework-global-226", + "LW_AWS_NETWORKING_18": "lacework-global-154", + "LW_AWS_NETWORKING_19": "lacework-global-155", + "LW_AWS_NETWORKING_20": "lacework-global-156", + "LW_AWS_NETWORKING_21": "lacework-global-104", + "LW_AWS_NETWORKING_22": "lacework-global-106", + "LW_AWS_NETWORKING_23": "lacework-global-107", + "LW_AWS_NETWORKING_24": "lacework-global-108", + "LW_AWS_NETWORKING_25": "lacework-global-109", + "LW_AWS_NETWORKING_26": "lacework-global-110", + "LW_AWS_NETWORKING_27": "lacework-global-111", + "LW_AWS_NETWORKING_28": "lacework-global-112", + "LW_AWS_NETWORKING_29": "lacework-global-113", + "LW_AWS_NETWORKING_30": "lacework-global-114", + "LW_AWS_NETWORKING_31": "lacework-global-218", + "LW_AWS_NETWORKING_32": "lacework-global-219", + "LW_AWS_NETWORKING_33": "lacework-global-220", + "LW_AWS_NETWORKING_34": "lacework-global-221", + "LW_AWS_NETWORKING_35": "lacework-global-222", + "LW_AWS_NETWORKING_36": "lacework-global-148", + "LW_AWS_NETWORKING_37": "lacework-global-102", + "LW_AWS_NETWORKING_38": "lacework-global-223", + "LW_AWS_NETWORKING_39": "lacework-global-184", + "LW_AWS_NETWORKING_40": "lacework-global-103", + "LW_AWS_NETWORKING_41": "lacework-global-125", // cloudfront + "LW_AWS_NETWORKING_42": "lacework-global-126", // cloudfront + "LW_AWS_NETWORKING_43": "lacework-global-127", + "LW_AWS_NETWORKING_44": "lacework-global-231", + "LW_AWS_NETWORKING_45": "lacework-global-482", + "LW_AWS_NETWORKING_46": "lacework-global-157", + "LW_AWS_NETWORKING_47": "lacework-global-128", + "LW_AWS_NETWORKING_49": "lacework-global-159", + "LW_AWS_NETWORKING_50": "lacework-global-129", // cloudfront + "LW_AWS_NETWORKING_51": "lacework-global-483", + "LW_AWS_MONGODB_1": "lacework-global-196", // not documented + "LW_AWS_MONGODB_2": "lacework-global-196", + "LW_AWS_MONGODB_3": "lacework-global-197", + "LW_AWS_MONGODB_4": "lacework-global-197", + "LW_AWS_MONGODB_5": "lacework-global-198", + "LW_AWS_MONGODB_6": "lacework-global-198", + "LW_AWS_GENERAL_SECURITY_1": "lacework-global-89", // ec2 tags + "LW_AWS_GENERAL_SECURITY_2": "lacework-global-90", + "LW_AWS_GENERAL_SECURITY_3": "lacework-global-160", + "LW_AWS_GENERAL_SECURITY_4": "lacework-global-171", + "LW_AWS_GENERAL_SECURITY_5": "lacework-global-91", + "LW_AWS_GENERAL_SECURITY_6": "lacework-global-92", + "LW_AWS_GENERAL_SECURITY_7": "lacework-global-182", + "LW_AWS_GENERAL_SECURITY_8": "lacework-global-183", + "LW_AWS_SERVERLESS_1": "lacework-global-179", + "LW_AWS_SERVERLESS_2": "lacework-global-180", + "LW_AWS_SERVERLESS_4": "lacework-global-143", + "LW_AWS_SERVERLESS_5": "lacework-global-144", + "LW_AWS_RDS_1": "lacework-global-93", + "LW_AWS_ELASTICSEARCH_1": "lacework-global-122", + "LW_AWS_ELASTICSEARCH_2": "lacework-global-123", + "LW_AWS_ELASTICSEARCH_3": "lacework-global-124", + "LW_AWS_ELASTICSEARCH_4": "lacework-global-161", + } + + // suppressionsMigrateAwsCmd represents the aws sub-command inside the suppressions migrate command + suppressionsMigrateAwsCmd = &cobra.Command{ + Use: "migrate", + Aliases: []string{"mig"}, + Short: "Migrate legacy suppressions for AWS to mapped policy exceptions", + RunE: suppressionsAwsMigrate, + } + + // suppressionsListAwsCmd represents the aws sub-command inside the suppressions list command + suppressionsListAwsCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List legacy suppressions for AWS", + RunE: suppressionsAwsList, + } +) + +func suppressionsAwsList(_ *cobra.Command, _ []string) error { + var ( + suppressions map[string]api.SuppressionV2 + err error + ) + + suppressions, err = cli.LwApi.V2.Suppressions.Aws.List() + if err != nil { + if strings.Contains(err.Error(), "No active AWS accounts") { + cli.OutputHuman("No active AWS accounts found. " + + "Unable to get legacy aws suppressions\n") + return nil + } + return errors.Wrap(err, "Unable to get legacy aws suppressions") + } + + if len(suppressions) == 0 { + cli.OutputHuman("No legacy AWS suppressions found.\n") + return nil + } + return cli.OutputJSON(suppressions) +} + +func suppressionsAwsMigrate(_ *cobra.Command, _ []string) error { + var ( + suppressionsMap map[string]api.SuppressionV2 + err error + + convertedPolicyExceptions []map[string]api.PolicyException + payloadsText []string + discardedSuppressions []map[string]api.SuppressionV2 + ) + suppressionsMap, err = cli.LwApi.V2.Suppressions.Aws.List() + if err != nil { + if strings.Contains(err.Error(), "No active AWS accounts") { + cli.OutputHuman("No active AWS accounts found. " + + "Unable to get legacy aws suppressions") + return nil + } + return errors.Wrap(err, "Unable to get legacy aws suppressions") + } + + if len(suppressionsMap) == 0 { + cli.OutputHuman("No legacy AWS suppressions found.\n") + return nil + } + + answer := "" + manualMigration := "Output translated legacy suppressions as policy exception commands to be" + + " run manually (Recommended)" + autoMigration := "Auto migrate legacy suppressions.\nDISCLAIMER: " + + "By selecting this option, you accept liability for the migration and " + + "any compliance violations missed as a result of the added exceptions" + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: "Select your legacy suppression migration approach?", + Options: []string{ + manualMigration, + autoMigration, + }, + }, + Response: &answer, + }); err != nil { + return err + } + + // get a list of all policies and parse the valid exception constraints and create a map of + // {"": []} + policyExceptionsConstraintsMap := getPoliciesExceptionConstraintsMap() + + switch answer { + case manualMigration: + _, payloadsText, discardedSuppressions = convertAwsSuppressions( + suppressionsMap, + policyExceptionsConstraintsMap, + ) + printPayloadsText(payloadsText) + printDiscardedSuppressions(discardedSuppressions) + case autoMigration: + convertedPolicyExceptions, _, discardedSuppressions = convertAwsSuppressions( + suppressionsMap, + policyExceptionsConstraintsMap, + ) + printConvertedSuppressions(convertedPolicyExceptions) + confirm := false + err := survey.AskOne(&survey.Confirm{ + Message: "Confirm the above exceptions have been reviewed and you wish to continue" + + " with the auto migration.", + }, &confirm) + if err != nil { + return err + } + if confirm { + autoConvertSuppressions(convertedPolicyExceptions) + printDiscardedSuppressions(discardedSuppressions) + cli.OutputHuman(color.GreenString("To view the newly created Exceptions, " + + "try running `lacework policy-exceptions list ")) + } else { + cli.OutputHuman("Cancelled Legacy Suppression to Exception migration!") + } + } + + return nil +} + +func convertAwsSuppressions( + suppressionsMap map[string]api.SuppressionV2, + policyExceptionsConstraintsMap map[string][]string, +) ([]map[string]api.PolicyException, + []string, []map[string]api.SuppressionV2) { + var ( + convertedPolicyExceptions []map[string]api.PolicyException + payloadsText []string + discardedSuppressions []map[string]api.SuppressionV2 + ) + + for id, suppressionInfo := range suppressionsMap { + // verify there is a mapped policy for this recommendation + // if the recommendation is not a key in the map we can assume this is not mapped and + // continue + mappedPolicyId, ok := awsEquivalencesMap[id] + if !ok { + // when we don't have a mapped policy, add the legacy suppression info + if suppressionInfo.SuppressionConditions != nil { + suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, + "Legacy suppression discarded as there is no equivalent policy") + discardedSuppressions = append( + discardedSuppressions, + map[string]api.SuppressionV2{id: suppressionInfo}, + ) + } + continue + } + + // get the supported policy exception fields for the mapped policy + // in order to ensure we have an up-to-date list of exception constraints we need to + // get the policy from the /api/v2/Policies/ api. + // We then parse this into a list of constraints + policyIdExceptionsTemplate := policyExceptionsConstraintsMap[mappedPolicyId] + if policyIdExceptionsTemplate == nil { + // Updating the suppression conditions comments to make it clear why these were + // discarded + if len(suppressionInfo.SuppressionConditions) >= 1 { + suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, + fmt.Sprintf("Legacy suppression discarded as the new policy: %s does not"+ + " support exception conditions", mappedPolicyId)) + + // if the list of supported constraints is empty for a policy, + // we should let the customers know that we have discarded this suppression + discardedSuppressions = append( + discardedSuppressions, + map[string]api.SuppressionV2{id: suppressionInfo}, + ) + } + continue + } + if len(suppressionInfo.SuppressionConditions) >= 1 { + for _, suppression := range suppressionInfo.SuppressionConditions { + // used to store the converted legacy suppressions + var convertedConstraints []api.PolicyExceptionConstraint + + accountIdsConstraint := convertSupCondition(suppression.AccountIds, + "accountIds", + policyIdExceptionsTemplate) + if accountIdsConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, accountIdsConstraint) + } + + regionNamesConstraint := convertSupCondition(suppression.RegionNames, + "regionNames", + policyIdExceptionsTemplate) + if regionNamesConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, regionNamesConstraint) + } + + resourceNamesConstraint := convertSupCondition(suppression.ResourceNames, + "resourceNames", + policyIdExceptionsTemplate) + if resourceNamesConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, resourceNamesConstraint) + } + + resourceTagsConstraint := convertSupConditionTags(suppression.ResourceTags, + "resourceTags", + policyIdExceptionsTemplate) + if resourceTagsConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, resourceTagsConstraint) + } + + description := fmt.Sprintf( + "Migrated exception from legacy compliance policy %s. ", id, + ) + if suppression.Comment != "" { + description = description + fmt.Sprintf( + "Legacy Policy comment: %s", suppression.Comment, + ) + } + if len(convertedConstraints) >= 1 { + convertedPolicyExceptions = append( + convertedPolicyExceptions, + map[string]api.PolicyException{ + mappedPolicyId: { + Description: description, + Constraints: convertedConstraints, + }, + }, + ) + + exception := api.PolicyException{ + Description: description, + Constraints: convertedConstraints, + } + jsonException, err := json.Marshal(exception) + if err != nil { + cli.Log.Error(err) + } + + payloadsText = append( + payloadsText, + fmt.Sprintf( + "lacework api post '/Exceptions?policyId=%s' -d '%s'", + mappedPolicyId, + jsonException, + ), + ) + } + } + } + } + + return convertedPolicyExceptions, payloadsText, discardedSuppressions +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_azure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_azure.go new file mode 100644 index 000000000..c90247456 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_azure.go @@ -0,0 +1,414 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/fatih/color" + + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + azureEquivalencesMap = map[string]string{ + "Azure_CIS_131_1_1": "lacework-global-514", + "Azure_CIS_131_1_2": "lacework-global-597", + "Azure_CIS_131_1_3": "lacework-global-499", + "Azure_CIS_131_1_4": "lacework-global-500", + "Azure_CIS_131_1_5": "lacework-global-501", + "Azure_CIS_131_1_6": "lacework-global-503", + "Azure_CIS_131_1_7": "lacework-global-504", + "Azure_CIS_131_1_8": "lacework-global-505", + "Azure_CIS_131_1_9": "lacework-global-506", + "Azure_CIS_131_1_10": "lacework-global-507", + "Azure_CIS_131_1_11": "lacework-global-508", + "Azure_CIS_131_1_12": "lacework-global-509", + //"Azure_CIS_131_1_13": "N/A", + "Azure_CIS_131_1_14": "lacework-global-590", + "Azure_CIS_131_1_15": "lacework-global-510", + "Azure_CIS_131_1_16": "lacework-global-591", + "Azure_CIS_131_1_17": "lacework-global-592", + "Azure_CIS_131_1_18": "lacework-global-593", + "Azure_CIS_131_1_19": "lacework-global-594", + "Azure_CIS_131_1_20": "lacework-global-511", + "Azure_CIS_131_1_21": "lacework-global-512", + "Azure_CIS_131_1_22": "lacework-global-513", + "Azure_CIS_131_1_23": "lacework-global-595", + "Azure_CIS_131_2_1": "lacework-global-598", + "Azure_CIS_131_2_2": "lacework-global-599", + "Azure_CIS_131_2_3": "lacework-global-601", + "Azure_CIS_131_2_4": "lacework-global-602", + "Azure_CIS_131_2_5": "lacework-global-604", + //"Azure_CIS_131_2_6": "N/A", + "Azure_CIS_131_2_7": "lacework-global-605", + "Azure_CIS_131_2_8": "lacework-global-607", + "Azure_CIS_131_2_9": "lacework-global-614", + "Azure_CIS_131_2_10": "lacework-global-613", + "Azure_CIS_131_2_11": "lacework-global-524", + "Azure_CIS_131_2_12": "lacework-global-523", + "Azure_CIS_131_2_13": "lacework-global-526", + "Azure_CIS_131_2_14": "lacework-global-527", + "Azure_CIS_131_2_15": "lacework-global-525", + "Azure_CIS_131_3_1": "lacework-global-528", + "Azure_CIS_131_3_2": "lacework-global-530", + "Azure_CIS_131_3_3": "lacework-global-616", + "Azure_CIS_131_3_4": "lacework-global-531", + "Azure_CIS_131_3_5": "lacework-global-532", + "Azure_CIS_131_3_6": "lacework-global-533", + "Azure_CIS_131_3_7": "lacework-global-617", + "Azure_CIS_131_3_8": "lacework-global-535", + "Azure_CIS_131_3_9": "lacework-global-618", + "Azure_CIS_131_3_10": "lacework-global-619", + "Azure_CIS_131_3_11": "lacework-global-620", + "Azure_CIS_131_4_1_1": "lacework-global-537", + "Azure_CIS_131_4_1_2": "lacework-global-540", + "Azure_CIS_131_4_1_3": "lacework-global-541", + "Azure_CIS_131_4_2_1": "lacework-global-622", + "Azure_CIS_131_4_2_2": "lacework-global-623", + "Azure_CIS_131_4_2_3": "lacework-global-624", + "Azure_CIS_131_4_2_4": "lacework-global-625", + "Azure_CIS_131_4_2_5": "lacework-global-542", + "Azure_CIS_131_4_3_1": "lacework-global-543", + "Azure_CIS_131_4_3_2": "lacework-global-551", + "Azure_CIS_131_4_3_3": "lacework-global-544", + "Azure_CIS_131_4_3_4": "lacework-global-545", + "Azure_CIS_131_4_3_5": "lacework-global-546", + "Azure_CIS_131_4_3_6": "lacework-global-547", + "Azure_CIS_131_4_3_7": "lacework-global-548", + "Azure_CIS_131_4_3_8": "lacework-global-549", + "Azure_CIS_131_4_4": "lacework-global-539", + "Azure_CIS_131_4_5": "lacework-global-621", + "Azure_CIS_131_5_1_1": "lacework-global-554", + "Azure_CIS_131_5_1_2": "lacework-global-555", + "Azure_CIS_131_5_1_3": "lacework-global-556", + "Azure_CIS_131_5_1_4": "lacework-global-630", + "Azure_CIS_131_5_1_5": "lacework-global-557", + "Azure_CIS_131_5_2_1": "lacework-global-558", + "Azure_CIS_131_5_2_2": "lacework-global-559", + "Azure_CIS_131_5_2_3": "lacework-global-560", + "Azure_CIS_131_5_2_4": "lacework-global-561", + //"Azure_CIS_131_5_2_5": "N/A", + //"Azure_CIS_131_5_2_6": "N/A", + "Azure_CIS_131_5_2_7": "lacework-global-562", + "Azure_CIS_131_5_2_8": "lacework-global-563", + "Azure_CIS_131_5_2_9": "lacework-global-564", + "Azure_CIS_131_5_3": "lacework-global-553", + "Azure_CIS_131_6_1": "lacework-global-568", + "Azure_CIS_131_6_2": "lacework-global-569", + "Azure_CIS_131_6_3": "lacework-global-538", + "Azure_CIS_131_6_4": "lacework-global-633", + "Azure_CIS_131_6_5": "lacework-global-634", + "Azure_CIS_131_6_6": "lacework-global-570", + "Azure_CIS_131_7_1": "lacework-global-573", + "Azure_CIS_131_7_2": "lacework-global-635", + "Azure_CIS_131_7_3": "lacework-global-636", + "Azure_CIS_131_7_4": "lacework-global-574", + "Azure_CIS_131_7_5": "lacework-global-522", + "Azure_CIS_131_7_6": "lacework-global-637", + "Azure_CIS_131_7_7": "lacework-global-638", + "Azure_CIS_131_8_1": "lacework-global-575", + "Azure_CIS_131_8_2": "lacework-global-577", + "Azure_CIS_131_8_3": "lacework-global-645", + "Azure_CIS_131_8_4": "lacework-global-579", + //"Azure_CIS_131_8_5": "N/A", + "Azure_CIS_131_9_1": "lacework-global-642", + "Azure_CIS_131_9_2": "lacework-global-580", + "Azure_CIS_131_9_3": "lacework-global-581", + "Azure_CIS_131_9_4": "lacework-global-643", + "Azure_CIS_131_9_5": "lacework-global-582", + "Azure_CIS_131_9_6": "lacework-global-583", + "Azure_CIS_131_9_7": "lacework-global-584", + "Azure_CIS_131_9_8": "lacework-global-585", + "Azure_CIS_131_9_9": "lacework-global-586", + "Azure_CIS_131_9_10": "lacework-global-587", + "Azure_CIS_131_9_11": "lacework-global-644", + } + + // suppressionsMigrateAzureCmd represents the azure sub-command inside the suppressions migrate + //command + suppressionsMigrateAzureCmd = &cobra.Command{ + Use: "migrate", + Aliases: []string{"mig"}, + Short: "Migrate legacy suppressions for Azure to mapped policy exceptions", + RunE: suppressionsAzureMigrate, + } + + // suppressionsListAzureCmd represents the azure sub-command inside the suppressions list + //command + suppressionsListAzureCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List legacy suppressions for Azure", + RunE: suppressionsAzureList, + } +) + +func suppressionsAzureList(_ *cobra.Command, _ []string) error { + var ( + suppressions map[string]api.SuppressionV2 + err error + ) + + suppressions, err = cli.LwApi.V2.Suppressions.Azure.List() + if err != nil { + if strings.Contains(err.Error(), "No active Azure accounts") { + cli.OutputHuman("No active Azure accounts found. " + + "Unable to get legacy Azure suppressions\n") + return nil + } + return errors.Wrap(err, "Unable to get legacy Azure suppressions") + } + + if len(suppressions) == 0 { + cli.OutputHuman("No legacy Azure suppressions found.\n") + return nil + } + return cli.OutputJSON(suppressions) +} + +func suppressionsAzureMigrate(_ *cobra.Command, _ []string) error { + var ( + suppressionsMap map[string]api.SuppressionV2 + err error + + convertedPolicyExceptions []map[string]api.PolicyException + payloadsText []string + discardedSuppressions []map[string]api.SuppressionV2 + ) + suppressionsMap, err = cli.LwApi.V2.Suppressions.Azure.List() + if err != nil { + if strings.Contains(err.Error(), "No active Azure accounts") { + cli.OutputHuman("No active Azure accounts found. " + + "Unable to get legacy Azure suppressions\n") + return nil + } + return errors.Wrap(err, "Unable to get legacy Azure suppressions") + } + + if len(suppressionsMap) == 0 { + cli.OutputHuman("No legacy Azure suppressions found.\n") + return nil + } + + answer := "" + manualMigration := "Output translated legacy suppressions as policy exception commands to be" + + " run manually (Recommended)" + autoMigration := "Auto migrate legacy suppressions.\nDISCLAIMER: " + + "By selecting this option, you accept liability for the migration and " + + "any compliance violations missed as a result of the added exceptions" + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: "Select your legacy suppression migration approach?", + Options: []string{ + manualMigration, + autoMigration, + }, + }, + Response: &answer, + }); err != nil { + return err + } + + // get a list of all policies and parse the valid exception constraints and create a map of + // {"": []} + policyExceptionsConstraintsMap := getPoliciesExceptionConstraintsMap() + + switch answer { + case manualMigration: + _, payloadsText, discardedSuppressions = convertAzureSuppressions( + suppressionsMap, + policyExceptionsConstraintsMap, + ) + printPayloadsText(payloadsText) + printDiscardedSuppressions(discardedSuppressions) + case autoMigration: + convertedPolicyExceptions, _, discardedSuppressions = convertAzureSuppressions( + suppressionsMap, + policyExceptionsConstraintsMap, + ) + printConvertedSuppressions(convertedPolicyExceptions) + confirm := false + err := survey.AskOne(&survey.Confirm{ + Message: "Confirm the above exceptions have been reviewed and you wish to continue" + + " with the auto migration.", + }, &confirm) + if err != nil { + return err + } + if confirm { + autoConvertSuppressions(convertedPolicyExceptions) + printDiscardedSuppressions(discardedSuppressions) + cli.OutputHuman(color.GreenString("To view the newly created Exceptions, " + + "try running `lacework policy-exceptions list ")) + } else { + cli.OutputHuman("Cancelled Legacy Suppression to Exception migration!") + } + } + + return nil +} + +func convertAzureSuppressions( + suppressionsMap map[string]api.SuppressionV2, + policyExceptionsConstraintsMap map[string][]string, +) ([]map[string]api.PolicyException, + []string, []map[string]api.SuppressionV2) { + var ( + convertedPolicyExceptions []map[string]api.PolicyException + payloadsText []string + discardedSuppressions []map[string]api.SuppressionV2 + ) + + for id, suppressionInfo := range suppressionsMap { + // verify there is a mapped policy for this recommendation + // if the recommendation is not a key in the map we can assume this is not mapped and + // continue + mappedPolicyId, ok := azureEquivalencesMap[id] + if !ok { + // when we don't have a mapped policy, add the legacy suppression info + if suppressionInfo.SuppressionConditions != nil { + suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, + "Legacy suppression discarded as there is no equivalent policy") + discardedSuppressions = append( + discardedSuppressions, + map[string]api.SuppressionV2{id: suppressionInfo}, + ) + } + continue + } + + // get the supported policy exception fields for the mapped policy + // in order to ensure we have an up-to-date list of exception constraints we need to + // get the policy from the /api/v2/Policies/ api. + // We then parse this into a list of constraints + policyIdExceptionsTemplate := policyExceptionsConstraintsMap[mappedPolicyId] + if policyIdExceptionsTemplate == nil { + // Updating the suppression conditions comments to make it clear why these were + // discarded + if len(suppressionInfo.SuppressionConditions) >= 1 { + suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, + fmt.Sprintf("Legacy suppression discarded as the new policy: %s does not"+ + " support exception conditions", mappedPolicyId)) + + // if the list of supported constraints is empty for a policy, + // we should let the customers know that we have discarded this suppression + discardedSuppressions = append( + discardedSuppressions, + map[string]api.SuppressionV2{id: suppressionInfo}, + ) + } + continue + } + if len(suppressionInfo.SuppressionConditions) >= 1 { + for _, suppression := range suppressionInfo.SuppressionConditions { + // used to store the converted legacy suppressions + var convertedConstraints []api.PolicyExceptionConstraint + + resourceGroupNamesConstraint := convertSupCondition(suppression.ResourceGroupNames, + "azureResourceGroup", + policyIdExceptionsTemplate) + if resourceGroupNamesConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, resourceGroupNamesConstraint) + } + + regionNamesConstraint := convertSupCondition(suppression.RegionNames, + "regionNames", + policyIdExceptionsTemplate) + if regionNamesConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, regionNamesConstraint) + } + + tenantsConstraint := convertSupCondition(suppression.TenantIds, + "tenants", + policyIdExceptionsTemplate) + if tenantsConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, tenantsConstraint) + } + + subscriptionsConstraint := convertSupCondition(suppression.SubscriptionIds, + "subscriptions", + policyIdExceptionsTemplate) + if subscriptionsConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, subscriptionsConstraint) + } + + resourceNamesConstraint := convertSupCondition(suppression.ResourceNames, + "resourceName", + policyIdExceptionsTemplate) + if resourceNamesConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, resourceNamesConstraint) + } + + resourceTagsConstraint := convertSupConditionTags(suppression.ResourceTags, + "resourceTags", + policyIdExceptionsTemplate) + if resourceTagsConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, resourceTagsConstraint) + } + + description := fmt.Sprintf( + "Migrated exception from legacy compliance policy %s. ", id, + ) + if suppression.Comment != "" { + description = description + fmt.Sprintf( + "Legacy Policy comment: %s", suppression.Comment, + ) + } + if len(convertedConstraints) >= 1 { + convertedPolicyExceptions = append( + convertedPolicyExceptions, + map[string]api.PolicyException{ + mappedPolicyId: { + Description: description, + Constraints: convertedConstraints, + }, + }, + ) + + exception := api.PolicyException{ + Description: description, + Constraints: convertedConstraints, + } + jsonException, err := json.Marshal(exception) + if err != nil { + cli.Log.Error(err) + } + + payloadsText = append( + payloadsText, + fmt.Sprintf( + "lacework api post '/Exceptions?policyId=%s' -d '%s'", + mappedPolicyId, + jsonException, + ), + ) + } + } + } + } + + return convertedPolicyExceptions, payloadsText, discardedSuppressions +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_gcp.go new file mode 100644 index 000000000..18836b0a8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_gcp.go @@ -0,0 +1,368 @@ +// +// Author:: Ross Moles () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/fatih/color" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + gcpEquivalencesMap = map[string]string{ + "GCP_CIS12_1_2": "lacework-global-233", + "GCP_CIS12_1_3": "lacework-global-293", + "GCP_CIS12_1_4": "lacework-global-234", + "GCP_CIS12_1_5": "lacework-global-235", + "GCP_CIS12_1_6": "lacework-global-236", + "GCP_CIS12_1_7": "lacework-global-237", + "GCP_CIS12_1_8": "lacework-global-294", + "GCP_CIS12_1_9": "lacework-global-238", + "GCP_CIS12_1_10": "lacework-global-239", + "GCP_CIS12_1_11": "lacework-global-295", + "GCP_CIS12_1_12": "lacework-global-296", + "GCP_CIS12_1_13": "lacework-global-240", + "GCP_CIS12_1_14": "lacework-global-241", + "GCP_CIS12_1_15": "lacework-global-242", + "GCP_CIS12_2_1": "lacework-global-245", + "GCP_CIS12_2_2": "lacework-global-246", + "GCP_CIS12_2_3": "lacework-global-298", + "GCP_CIS12_2_4": "lacework-global-247", + "GCP_CIS12_2_5": "lacework-global-248", + "GCP_CIS12_2_6": "lacework-global-249", + "GCP_CIS12_2_7": "lacework-global-250", + "GCP_CIS12_2_8": "lacework-global-251", + "GCP_CIS12_2_9": "lacework-global-252", + "GCP_CIS12_2_10": "lacework-global-253", + "GCP_CIS12_2_11": "lacework-global-254", + "GCP_CIS12_2_12": "lacework-global-255", + "GCP_CIS12_3_1": "lacework-global-300", + "GCP_CIS12_3_2": "lacework-global-258", + "GCP_CIS12_3_3": "lacework-global-259", + "GCP_CIS12_3_4": "lacework-global-260", + "GCP_CIS12_3_5": "lacework-global-261", + "GCP_CIS12_3_6": "lacework-global-301", + "GCP_CIS12_3_7": "lacework-global-302", + "GCP_CIS12_3_8": "lacework-global-262", + "GCP_CIS12_3_9": "lacework-global-263", + "GCP_CIS12_3_10": "lacework-global-303", + "GCP_CIS12_4_1": "lacework-global-264", + "GCP_CIS12_4_2": "lacework-global-265", + "GCP_CIS12_4_3": "lacework-global-266", + "GCP_CIS12_4_4": "lacework-global-267", + "GCP_CIS12_4_5": "lacework-global-268", + "GCP_CIS12_4_6": "lacework-global-269", + "GCP_CIS12_4_7": "lacework-global-304", + "GCP_CIS12_4_8": "lacework-global-305", + "GCP_CIS12_4_9": "lacework-global-306", + "GCP_CIS12_4_10": "lacework-global-307", + "GCP_CIS12_4_11": "lacework-global-308", + "GCP_CIS12_5_1": "lacework-global-270", + "GCP_CIS12_5_2": "lacework-global-310", + "GCP_CIS12_6_1_1": "lacework-global-274", + "GCP_CIS12_6_1_2": "lacework-global-275", + "GCP_CIS12_6_1_3": "lacework-global-276", + //"GCP_CIS12_6_2_1": "N/A", + "GCP_CIS12_6_2_2": "lacework-global-312", + "GCP_CIS12_6_2_3": "lacework-global-277", + "GCP_CIS12_6_2_4": "lacework-global-278", + //"GCP_CIS12_6_2_5": "N/A", + //"GCP_CIS12_6_2_6": "N/A", + "GCP_CIS12_6_2_7": "lacework-global-279", + "GCP_CIS12_6_2_8": "lacework-global-280", + //"GCP_CIS12_6_2_9": "N/A", + //"GCP_CIS12_6_2_10": "N/A", + //"GCP_CIS12_6_2_11": "N/A", + //"GCP_CIS12_6_2_12": "N/A", + "GCP_CIS12_6_2_13": "lacework-global-281", + "GCP_CIS12_6_2_14": "lacework-global-282", + //"GCP_CIS12_6_2_15": "N/A", + "GCP_CIS12_6_2_16": "lacework-global-283", + "GCP_CIS12_6_3_1": "lacework-global-285", + "GCP_CIS12_6_3_2": "lacework-global-286", + "GCP_CIS12_6_3_3": "lacework-global-287", + "GCP_CIS12_6_3_4": "lacework-global-288", + "GCP_CIS12_6_3_5": "lacework-global-289", + "GCP_CIS12_6_3_6": "lacework-global-290", + "GCP_CIS12_6_3_7": "lacework-global-291", + "GCP_CIS12_6_4": "lacework-global-271", + "GCP_CIS12_6_5": "lacework-global-272", + "GCP_CIS12_6_6": "lacework-global-311", + "GCP_CIS12_6_7": "lacework-global-273", + "GCP_CIS12_7_1": "lacework-global-292", + "GCP_CIS12_7_2": "lacework-global-313", + "GCP_CIS12_7_3": "lacework-global-314", + } + + // suppressionsMigrateGcpCmd represents the gcp sub-command inside the suppressions migrate command + suppressionsMigrateGcpCmd = &cobra.Command{ + Use: "migrate", + Aliases: []string{"mig"}, + Short: "Migrate legacy suppressions for Gcp to mapped policy exceptions", + RunE: suppressionsGcpMigrate, + } + + // suppressionsListGcpCmd represents the gcp sub-command inside the suppressions list command + suppressionsListGcpCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List legacy suppressions for GCP", + RunE: suppressionsGcpList, + } +) + +func suppressionsGcpList(_ *cobra.Command, _ []string) error { + var ( + suppressions map[string]api.SuppressionV2 + err error + ) + + suppressions, err = cli.LwApi.V2.Suppressions.Gcp.List() + if err != nil { + if strings.Contains(err.Error(), "No active GCP accounts") { + cli.OutputHuman("No active GCP accounts found. " + + "Unable to get legacy GCP suppressions\n") + return nil + } + return errors.Wrap(err, "Unable to get legacy GCP suppressions") + } + + if len(suppressions) == 0 { + cli.OutputHuman("No legacy GCP suppressions found.\n") + return nil + } + return cli.OutputJSON(suppressions) +} + +func suppressionsGcpMigrate(_ *cobra.Command, _ []string) error { + var ( + suppressionsMap map[string]api.SuppressionV2 + err error + + convertedPolicyExceptions []map[string]api.PolicyException + payloadsText []string + discardedSuppressions []map[string]api.SuppressionV2 + ) + suppressionsMap, err = cli.LwApi.V2.Suppressions.Gcp.List() + if err != nil { + if strings.Contains(err.Error(), "No active GCP accounts") { + cli.OutputHuman("No active GCP accounts found. " + + "Unable to get legacy gcp suppressions") + return nil + } + return errors.Wrap(err, "Unable to get legacy gcp suppressions") + } + + if len(suppressionsMap) == 0 { + cli.OutputHuman("No legacy GCP suppressions found.\n") + return nil + } + + answer := "" + manualMigration := "Output translated legacy suppressions as policy exception commands to be" + + " run manually (Recommended)" + autoMigration := "Auto migrate legacy suppressions.\nDISCLAIMER: " + + "By selecting this option, you accept liability for the migration and " + + "any compliance violations missed as a result of the added exceptions" + if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ + Prompt: &survey.Select{ + Message: "Select your legacy suppression migration approach?", + Options: []string{ + manualMigration, + autoMigration, + }, + }, + Response: &answer, + }); err != nil { + return err + } + + // get a list of all policies and parse the valid exception constraints and create a map of + // {"": []} + policyExceptionsConstraintsMap := getPoliciesExceptionConstraintsMap() + + switch answer { + case manualMigration: + _, payloadsText, discardedSuppressions = convertGcpSuppressions( + suppressionsMap, + policyExceptionsConstraintsMap, + ) + printPayloadsText(payloadsText) + printDiscardedSuppressions(discardedSuppressions) + case autoMigration: + convertedPolicyExceptions, _, discardedSuppressions = convertGcpSuppressions( + suppressionsMap, + policyExceptionsConstraintsMap, + ) + printConvertedSuppressions(convertedPolicyExceptions) + confirm := false + err := survey.AskOne(&survey.Confirm{ + Message: "Confirm the above exceptions have been reviewed and you wish to continue" + + " with the auto migration.", + }, &confirm) + if err != nil { + return err + } + if confirm { + autoConvertSuppressions(convertedPolicyExceptions) + printDiscardedSuppressions(discardedSuppressions) + cli.OutputHuman(color.GreenString("To view the newly created Exceptions, " + + "try running `lacework policy-exceptions list ")) + } else { + cli.OutputHuman("Cancelled Legacy Suppression to Exception migration!\n") + } + } + + return nil +} + +func convertGcpSuppressions( + suppressionsMap map[string]api.SuppressionV2, + policyExceptionsConstraintsMap map[string][]string, +) ([]map[string]api.PolicyException, + []string, []map[string]api.SuppressionV2) { + var ( + convertedPolicyExceptions []map[string]api.PolicyException + payloadsText []string + discardedSuppressions []map[string]api.SuppressionV2 + ) + + for id, suppressionInfo := range suppressionsMap { + // verify there is a mapped policy for this recommendation + // if the recommendation is not a key in the map we can assume this is not mapped and + // continue + mappedPolicyId, ok := gcpEquivalencesMap[id] + if !ok { + // when we don't have a mapped policy, add the legacy suppression info + if suppressionInfo.SuppressionConditions != nil { + suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, + "Legacy suppression discarded as there is no equivalent policy") + discardedSuppressions = append( + discardedSuppressions, + map[string]api.SuppressionV2{id: suppressionInfo}, + ) + } + continue + } + + // get the supported policy exception fields for the mapped policy + // in order to ensure we have an up-to-date list of exception constraints we need to + // get the policy from the /api/v2/Policies/ api. + // We then parse this into a list of constraints + policyIdExceptionsTemplate := policyExceptionsConstraintsMap[mappedPolicyId] + if policyIdExceptionsTemplate == nil { + // Updating the suppression conditions comments to make it clear why these were + // discarded + if len(suppressionInfo.SuppressionConditions) >= 1 { + suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, + fmt.Sprintf("Legacy suppression discarded as the new policy: %s does not"+ + " support exception conditions", mappedPolicyId)) + + // if the list of supported constraints is empty for a policy, + // we should let the customers know that we have discarded this suppression + discardedSuppressions = append( + discardedSuppressions, + map[string]api.SuppressionV2{id: suppressionInfo}, + ) + } + continue + } + if len(suppressionInfo.SuppressionConditions) >= 1 { + for _, suppression := range suppressionInfo.SuppressionConditions { + // used to store the converted legacy suppressions + var convertedConstraints []api.PolicyExceptionConstraint + + organizationIdsConstraint := convertSupCondition(suppression.OrganizationIds, + "organizations", + policyIdExceptionsTemplate) + if organizationIdsConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, organizationIdsConstraint) + } + + projectIdsConstraint := convertSupCondition(suppression.ProjectIds, + "projects", + policyIdExceptionsTemplate) + if projectIdsConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, projectIdsConstraint) + } + + resourceNamesConstraint := convertGcpResourceNameSupConditions(suppression.ResourceNames, + "resourceName", + policyIdExceptionsTemplate) + if resourceNamesConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, resourceNamesConstraint) + } + + resourceTagsConstraint := convertSupConditionTags(suppression.ResourceTags, + "resourceTags", + policyIdExceptionsTemplate) + if resourceTagsConstraint.FieldKey != "" { + convertedConstraints = append(convertedConstraints, resourceTagsConstraint) + } + + description := fmt.Sprintf( + "Migrated exception from legacy compliance policy %s. ", id, + ) + if suppression.Comment != "" { + description = description + fmt.Sprintf( + "Legacy Policy comment: %s", suppression.Comment, + ) + } + if len(convertedConstraints) >= 1 { + convertedPolicyExceptions = append( + convertedPolicyExceptions, + map[string]api.PolicyException{ + mappedPolicyId: { + Description: description, + Constraints: convertedConstraints, + }, + }, + ) + + exception := api.PolicyException{ + Description: description, + Constraints: convertedConstraints, + } + jsonException, err := json.Marshal(exception) + if err != nil { + cli.Log.Error(err) + } + + payloadsText = append( + payloadsText, + fmt.Sprintf( + "lacework api post '/Exceptions?policyId=%s' -d '%s'", + mappedPolicyId, + jsonException, + ), + ) + } + } + } + } + + return convertedPolicyExceptions, payloadsText, discardedSuppressions +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/table_render.go b/vendor/github.com/lacework/go-sdk/cli/cmd/table_render.go new file mode 100644 index 000000000..69916e97f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/table_render.go @@ -0,0 +1,80 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strings" + + "github.com/olekukonko/tablewriter" +) + +// renderSimpleTable is used to render any simple table within the Lacework CLI, +// every command should leverage this function unless there are extra customizations +// required, if so, use instead renderCustomTable(). The benefit of this function +// is the ability to switch/update the look and feel of the human-readable format +// across the entire project +func renderSimpleTable(headers []string, data [][]string) string { + var ( + tblBldr = &strings.Builder{} + tbl = tablewriter.NewWriter(tblBldr) + ) + tbl.SetHeader(headers) + tbl.SetRowLine(false) + tbl.SetBorder(false) + tbl.SetAutoWrapText(true) + tbl.SetAlignment(tablewriter.ALIGN_LEFT) + tbl.SetColumnSeparator(" ") + tbl.AppendBulk(data) + tbl.Render() + return tblBldr.String() +} + +type tableOption interface { + apply(t *tablewriter.Table) +} + +type tableFunc func(t *tablewriter.Table) + +func (fn tableFunc) apply(t *tablewriter.Table) { + fn(t) +} + +// renderCustomTable should be used on special cases where we need to render a table +// with very specific settings, we normally should use renderSimpleTable() as much +// as possible to have consistency across the CLI +func renderCustomTable(headers []string, data [][]string, opts ...tableOption) string { + var ( + tblBldr = &strings.Builder{} + tbl = tablewriter.NewWriter(tblBldr) + ) + + for _, opt := range opts { + opt.apply(tbl) + } + + tbl.SetHeader(headers) + tbl.AppendBulk(data) + tbl.Render() + + return tblBldr.String() +} + +func renderOneLineCustomTable(title, content string, opts ...tableOption) string { + return renderCustomTable([]string{title}, [][]string{[]string{content}}, opts...) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/team_members.go b/vendor/github.com/lacework/go-sdk/cli/cmd/team_members.go new file mode 100644 index 000000000..9a5d79463 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/team_members.go @@ -0,0 +1,405 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/lacework/go-sdk/api" +) + +var ( + // team-members command is used to manage lacework team members + teamMembersCommand = &cobra.Command{ + Use: "team-member", + Aliases: []string{"team-members", "tm"}, + Short: "Manage team members", + Long: `Manage Team Members to grant or restrict access to multiple Lacework Accounts. + Team members can also be granted organization-level roles. +`, + } + + // list command is used to list all lacework team members + teamMembersListCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all team members", + Long: "List all team members configured in your Lacework account.", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + cli.StartProgress(" Fetching team members...") + teamMembers, err := cli.LwApi.V2.TeamMembers.List() + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get team members") + } + if len(teamMembers.Data) == 0 { + msg := `There are no team members configured in your account. + +Get started by configuring your team members using the command: + + lacework team-member create + +If you prefer to configure team members via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Settings > Team Members. +` + cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) + return nil + } + if cli.JSONOutput() { + return cli.OutputJSON(teamMembers) + } + + var rows [][]string + for _, tm := range teamMembers.Data { + rows = append(rows, []string{tm.UserGuid, tm.UserName, enabled(tm.UserEnabled)}) + } + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "STATUS"}, rows)) + return nil + }, + } + // show command is used to retrieve a lacework team member by guid + teamMembersShowCommand = &cobra.Command{ + Use: "show ", + Short: "Show a team member by id", + Long: "Show a single team member by it's id.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var response api.TeamMemberResponse + cli.StartProgress(" Fetching team member...") + + err := cli.LwApi.V2.TeamMembers.Get(args[0], &response) + if err != nil { + cli.StopProgress() + return errors.Wrap(err, "unable to get team member") + } + cli.StopProgress() + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + + teamMember := response.Data + headers := [][]string{{teamMember.UserGuid, teamMember.UserName, enabled(teamMember.UserEnabled)}} + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "STATUS"}, headers)) + cli.OutputHuman("\n") + cli.OutputHuman(buildTeamMemberDetailsTable(teamMember)) + + return nil + }, + } + + // delete command is used to remove a lacework team member by id + teamMembersDeleteCommand = &cobra.Command{ + Use: "delete ", + Short: "Delete a team member", + Long: "Delete a single team member by it's ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress(" Deleting team member...") + err := cli.LwApi.V2.TeamMembers.Delete(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to delete team member") + } + cli.OutputHuman("The team member with GUID %s was deleted\n", args[0]) + return nil + }, + } + + // create command is used to create a new lacework team member + teamMembersCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new team member", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, args []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + + userID, err := promptCreateTeamMember() + if err != nil { + return errors.Wrap(err, "unable to create team member") + } + + cli.OutputHuman("The team member was created with GUID %s\n", userID) + return nil + }, + } +) + +func init() { + // add the team-member command + rootCmd.AddCommand(teamMembersCommand) + + // add sub-commands to the team-member command + teamMembersCommand.AddCommand(teamMembersListCommand) + teamMembersCommand.AddCommand(teamMembersShowCommand) + teamMembersCommand.AddCommand(teamMembersCreateCommand) + teamMembersCommand.AddCommand(teamMembersDeleteCommand) +} + +func buildTeamMemberDetailsTable(tm api.TeamMember) string { + var ( + details [][]string + updatedTime string + ) + + if tm.Props.UpdatedTime != nil { + updatedTime = fmt.Sprintf("%v", tm.Props.UpdatedTime) + } + + details = append(details, []string{"FIRST NAME", tm.Props.FirstName}) + details = append(details, []string{"LAST NAME", tm.Props.LastName}) + details = append(details, []string{"COMPANY", tm.Props.Company}) + details = append(details, []string{"ACCOUNT ADMIN", strconv.FormatBool(tm.Props.AccountAdmin)}) + details = append(details, []string{"ORG ADMIN", strconv.FormatBool(tm.Props.OrgAdmin)}) + details = append(details, []string{"CREATED AT", tm.Props.CreatedTime}) + details = append(details, []string{"JIT CREATED", strconv.FormatBool(tm.Props.JitCreated)}) + details = append(details, []string{"UPDATED BY", tm.Props.UpdatedBy}) + details = append(details, []string{"UPDATED AT", updatedTime}) + + detailsTable := &strings.Builder{} + detailsTable.WriteString(renderOneLineCustomTable("TEAM MEMBER DETAILS", + renderCustomTable([]string{}, details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ), + ) + + detailsTable.WriteString("\n") + return detailsTable.String() +} + +func promptCreateTeamMember() (string, error) { + questions := []*survey.Question{ + { + Name: "email", + Prompt: &survey.Input{Message: "Email: "}, + Validate: validateEmail(), + }, + { + Name: "firstName", + Prompt: &survey.Input{Message: "First Name: "}, + Validate: survey.Required, + }, + { + Name: "lastName", + Prompt: &survey.Input{Message: "Last Name: "}, + Validate: survey.Required, + }, + { + Name: "company", + Prompt: &survey.Input{Message: "Company: "}, + Validate: survey.Required, + }, + { + Name: "orgTeamMemberPrompt", + Prompt: &survey.Confirm{Message: "Create at Organization Level?"}, + Validate: survey.Required, + }, + } + + answers := struct { + Email string `survey:"email"` + Description string `survey:"description"` + Company string `survey:"company"` + FirstName string `survey:"firstName"` + LastName string `survey:"lastName"` + OrgTeamMemberPrompt bool `survey:"orgTeamMemberPrompt"` + OrgAdminRole bool + UserRole bool + }{} + + err := survey.Ask(questions, &answers, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return "", err + } + + if answers.OrgTeamMemberPrompt { + orgConfig, err := askConfigureOrgTeamMemberPrompt() + if err != nil { + return "", err + } + + var ( + adminRoleAccounts []string + userRoleAccounts []string + ) + + switch orgConfig { + case "User Role": + answers.UserRole = true + case "Admin Role": + answers.OrgAdminRole = true + case "Manage Roles for Accounts": + adminRoleAccounts, userRoleAccounts, err = askManageOrgTeamMemberRolesPrompt() + if err != nil { + return "", err + } + } + + teamMember := api.NewTeamMemberOrg(answers.Email, api.TeamMemberProps{ + Company: answers.Company, + FirstName: answers.FirstName, + LastName: answers.LastName, + OrgAdmin: answers.OrgAdminRole, + OrgUser: answers.UserRole, + }) + + if len(adminRoleAccounts) > 0 { + teamMember.AdminRoleAccounts = sliceToUpper(adminRoleAccounts) + } + if len(userRoleAccounts) > 0 { + teamMember.UserRoleAccounts = sliceToUpper(userRoleAccounts) + } + + cli.StartProgress(" Creating team member...") + defer cli.StopProgress() + + res, err := cli.LwApi.V2.TeamMembers.CreateOrg(teamMember) + if err != nil { + return "", err + } + userID := res.Data.Accounts[0].UserGuid + return userID, nil + } + var accountAdmin bool + err = survey.AskOne(&survey.Confirm{Message: "Account Admin?"}, &accountAdmin) + if err != nil { + return "", err + } + + teamMember := api.NewTeamMember(answers.Email, api.TeamMemberProps{ + Company: answers.Company, + FirstName: answers.FirstName, + LastName: answers.LastName, + AccountAdmin: accountAdmin, + }) + + cli.StartProgress(" Creating team member...") + defer cli.StopProgress() + + res, err := cli.LwApi.V2.TeamMembers.Create(teamMember) + if err != nil { + return "", err + } + userID := res.Data.UserGuid + return userID, nil +} + +func askManageOrgTeamMemberRolesPrompt() ([]string, []string, error) { + res, err := cli.LwApi.V2.UserProfile.Get() + if err != nil { + return nil, nil, err + } + accountsList := res.Data[0].SubAccountNames() + + accounts := struct { + UserRoleAccounts []string `survey:"userRoleAccounts"` + AdminRoleAccounts []string `survey:"adminRoleAccounts"` + }{} + + questions := []*survey.Question{ + { + Name: "userRoleAccounts", + Prompt: &survey.MultiSelect{ + Message: "Select Accounts Team Member will have User role: ", + Options: accountsList}, + }, + { + Name: "adminRoleAccounts", + Prompt: &survey.MultiSelect{ + Message: "Select Accounts Team Member will have Admin role: ", + Options: accountsList}, + }, + } + + err = survey.Ask(questions, &accounts, + survey.WithIcons(promptIconsFunc), + ) + if err != nil { + return nil, nil, err + } + return accounts.AdminRoleAccounts, accounts.UserRoleAccounts, nil +} + +func askConfigureOrgTeamMemberPrompt() (string, error) { + var configureOrgMember string + + err := survey.AskOne(&survey.Select{ + Message: "Select a role for all accounts: ", + Options: []string{"User Role", "Admin Role", "Manage Roles for Accounts"}}, + &configureOrgMember, + survey.WithIcons(promptIconsFunc)) + + if err != nil { + return "", err + } + return configureOrgMember, nil +} + +func enabled(status int) string { + if status == 1 { + return "Enabled" + } + return "Disabled" +} + +func sliceToUpper(list []string) (upper []string) { + for _, item := range list { + upper = append(upper, strings.ToUpper(item)) + } + return +} + +func validateEmail() survey.Validator { + return func(val interface{}) error { + emailRegex, _ := regexp.Compile( + "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", //nolint + ) + if !emailRegex.MatchString(val.(string)) { + return fmt.Errorf("not a valid email %s", val.(string)) + } + return nil + } +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/telemetry.go b/vendor/github.com/lacework/go-sdk/cli/cmd/telemetry.go new file mode 100644 index 000000000..b87aa10fe --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/telemetry.go @@ -0,0 +1,96 @@ +package cmd + +import ( + "context" + "encoding/json" + "errors" + "io" + "os" + + "github.com/spf13/cobra" + + cdk "github.com/lacework/go-sdk/cli/cdk/go/proto/v1" +) + +var ( + // telemetryUploadName is the name of the feature that telemetry is being uploaded for + telemetryUploadName string + + // telemetryUploadFile is a path to a JSON file containing key value pairs to upload + telemetryUploadFile string + + // telemetryCmd represents the telemetry command + telemetryCmd = &cobra.Command{ + Hidden: true, + Use: "telemetry", + Short: "Manage telemetry sent by the Lacework CLI", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + return runConfigureSetup() + }, + } + + telemetryUploadCmd = &cobra.Command{ + Use: "upload", + Short: "Manually send some telemetry back to Lacework", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + if telemetryUploadName == "" { + return errors.New("--name flag is required for this command") + } + if telemetryUploadFile == "" { + return errors.New("--data flag is required for this command") + } + return runUpload(telemetryUploadName, telemetryUploadFile) + }, + } +) + +func runUpload(name string, file string) error { + err := cli.NewClient() + if err != nil { + return err + } + request, err := prepareUpload(name, file) + if err != nil { + return err + } + _, err = cli.Honeyvent(context.Background(), request) + return err +} + +func prepareUpload(name string, file string) (request *cdk.HoneyventRequest, err error) { + jsonFile, err := os.Open(file) + if err != nil { + return nil, err + } + defer func(jsonFile *os.File) { + err = jsonFile.Close() + }(jsonFile) + telemetryBytes, err := io.ReadAll(jsonFile) + if err != nil { + return nil, err + } + var telemetryData map[string]string + err = json.Unmarshal(telemetryBytes, &telemetryData) + if err != nil { + return nil, err + } + request = &cdk.HoneyventRequest{ + Feature: name, + FeatureData: telemetryData, + } + return request, nil +} + +func init() { + rootCmd.AddCommand(telemetryCmd) + telemetryCmd.AddCommand(telemetryUploadCmd) + + telemetryUploadCmd.Flags().StringVar(&telemetryUploadName, + "name", "", "Name of the feature the telemetry upload is for", + ) + telemetryUploadCmd.Flags().StringVar(&telemetryUploadFile, + "data", "", "Path to JSON file containing key-value pairs to upload", + ) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/.version b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/.version new file mode 100644 index 000000000..6c6aa7cb0 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/.version @@ -0,0 +1 @@ +0.1.0 \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh new file mode 100644 index 000000000..afe8ade3e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh @@ -0,0 +1,2 @@ +#!/bin/bash +exit 1 diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh.sig b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh.sig new file mode 100644 index 000000000..4e566fd4f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh.sig @@ -0,0 +1 @@ +dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpUy9XRjJUWjk4bUlNM1BYNmU5YjhMaXh6ajZiKytTK1MxcU1kTDE1NmN0bEZkbFI5ak1xZWx1N21tNWJlMGM0Z1pETmxSc1FiSStieTFWV1F1aGk5Zzg9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0KM1NIRVVBTG80RjZTeVo3SHhVbGNVZmtHQ0x6SzdRUmlGZWF1ZHJUK21uODQrUUJrMjhpcEpwNmhtckU3aEVpYUxobHlab2Z3WHplRTc2bWlObnBSQ0E9PQ== \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh new file mode 100644 index 000000000..67464fb16 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Hello World!" diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh.sig b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh.sig new file mode 100644 index 000000000..27a910457 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh.sig @@ -0,0 +1 @@ +dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpUmwrdm03ZE5wcDdKcnQrcUxBTnN1OW0rVGFIU2dWeUpPOUxkZW81WlB3L0dRSnUxZkpaQnVDcnNNVlc1S2J1QjZkRVB6UXZpOGN0MXpKZk1rZ1ZzZ3M9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0KY0p3aDdYRGN0R1I4ZHBjMU5FSm5QR1U1SVhOdk1OWGE2aGRNQTBCQlp2dm5zS1FIZVZWRWtNYzd6bzcyaUZzSEVLUnhlK0FtWGtyeU5nVmk3R2cwRFE9PQ== \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/version.go b/vendor/github.com/lacework/go-sdk/cli/cmd/version.go new file mode 100644 index 000000000..b7ab861c3 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/version.go @@ -0,0 +1,294 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/lacework/go-sdk/internal/cache" + "github.com/lacework/go-sdk/internal/file" + "github.com/lacework/go-sdk/lwcomponent" + "github.com/lacework/go-sdk/lwupdater" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // All the following "unknown" variables are being injected at + // build time via the cross-platform directive inside the Makefile + // + // Version is the semver coming from the VERSION file + Version = "unknown" + + // GitSHA is the git ref that the cli was built from + GitSHA = "unknown" + + // BuildTime is a human-readable time when the cli was built at + BuildTime = "unknown" + + // The name of the version cache file needed for daily version checks + VersionCacheFile = "version_cache" + + // versionCmd represents the version command + versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the Lacework CLI version", + Long: ` +Prints out the installed version of the Lacework CLI and checks for newer +versions available for update. + +Set the environment variable 'LW_UPDATES_DISABLE=1' to avoid checking for updates.`, + Args: cobra.NoArgs, + Run: func(_ *cobra.Command, _ []string) { + componentVersionsOutput := &strings.Builder{} + if cli.LwComponents != nil { + for _, component := range cli.LwComponents.Components { + if component.IsInstalled() { + v, err := component.CurrentVersion() + if err == nil { + componentVersionsOutput.WriteString( + fmt.Sprintf(" > %s v%s\n", component.Name, v.String()), + ) + continue + } + cli.Log.Errorw("unable to determine component version", + "error", err.Error(), "component", component.Name, + ) + } + } + } + + if cli.JSONOutput() { + vJSON := versionJSON{ + Version: fmt.Sprintf("v%s", Version), + GitSHA: GitSHA, + BuildTime: BuildTime, + } + + if componentVersionsOutput.String() != "" { + vJSON.CDK = cli.LwComponents + } + + errcheckEXIT(cli.OutputJSON(vJSON)) + return + } + + cli.OutputHuman("lacework v%s (sha:%s) (time:%s)\n", Version, GitSHA, BuildTime) + if componentVersionsOutput.String() != "" { + cli.OutputHuman("\nComponents:\n\n%s", componentVersionsOutput.String()) + } + + // check the latest version of the cli + if _, err := versionCheck(); err != nil { + cli.Log.Debugw("unable to perform lacework cli version check", + "error", err.Error()) + } + }, + } +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +// versionCheck checks if the user is running the latest version of the cli, +// if not, displays a friendly message about the new version available +func versionCheck() (*lwupdater.Version, error) { + cli.Log.Debugw("check version of the lacework-cli", "repository", "github.com/lacework/go-sdk") + sdk, err := lwupdater.Check("go-sdk", fmt.Sprintf("v%s", Version)) + if err != nil { + return nil, errors.Wrap(err, "unable to check updates") + } + + if sdk.Outdated { + cli.OutputHumanErr( + "\nA newer version of the Lacework CLI is available! The latest version is %s,\n"+ + "to update execute the following command:\n%s\n", + sdk.LatestVersion, cli.UpdateCommand()) + } + + return sdk, nil +} + +func isCheckEnabled() bool { + if disabled := os.Getenv(lwupdater.DisableEnv); disabled != "" { + return false + } + + if !cli.InteractiveMode() { + return false + } + return true +} + +// dailyComponentUpdateAvailable returns true if the cli should print that a new version of a component is available. +// It uses the file ~/.config/lacework/version_cache to track the last check time +func dailyComponentUpdateAvailable(componentName string) (bool, error) { + if cli.JSONOutput() || !isCheckEnabled() { + return false, nil + } + cacheDir, err := cache.CacheDir() + if err != nil { + return false, err + } + + cacheFile := path.Join(cacheDir, VersionCacheFile) + if !file.FileExists(cacheFile) { + // The file should have already been created by dailyVersionCheck + return false, err + } + + cli.Log.Debugw("verifying cached version", "cache_file", cacheFile) + versionCache, err := lwupdater.LoadCache(cacheFile) + if err != nil { + return false, err + } + + cli.Log.Debugw("component version cache", "content", versionCache.ComponentsLastCheck) + + // since our check is daily, substract one day from now and compare it + var ( + nowTime = time.Now() + checkTime = nowTime.AddDate(0, 0, -1) + ) + + if versionCache.CheckComponentBefore(componentName, checkTime) { + cli.Event.Feature = featDailyCompVerCheck + defer cli.SendHoneyvent() + + versionCache.ComponentsLastCheck[componentName] = nowTime + cli.Log.Debugw("storing new version cache", "content", versionCache) + err := versionCache.StoreCache(cacheFile) + + if err != nil { + cli.Event.FeatureData = map[string]interface{}{"silent_error": err.Error()} + return false, err + } + + cli.Event.DurationMs = time.Since(nowTime).Milliseconds() + cli.Event.FeatureData = versionCache + return true, nil + } else { + return false, nil + } +} + +func firstTimeVersionCheck(dir, file string) error { + var ( + err error + currentVersion *lwupdater.Version + ) + defer func() { + cli.Event.Feature = featDailyVerCheck + if err != nil { + cli.Event.FeatureData = map[string]interface{}{"silent_error": err.Error()} + } + cli.SendHoneyvent() + }() + + if err = os.MkdirAll(dir, 0755); err != nil { + return err + } + + currentVersion, err = versionCheck() + if err != nil { + return err + } + + cli.Log.Debugw("storing version cache", "content", currentVersion) + err = currentVersion.StoreCache(file) + return err +} + +// dailyVersionCheck will execute a version check on a daily basis, the function uses +// the file ~/.config/lacework/version_cache to track the last check time +func dailyVersionCheck() error { + if cli.JSONOutput() || !isCheckEnabled() { + return nil + } + + cacheDir, err := cache.CacheDir() + if err != nil { + return err + } + + cacheFile := path.Join(cacheDir, VersionCacheFile) + if !file.FileExists(cacheFile) { + // first time running the daily version check, create directory + return firstTimeVersionCheck(cacheDir, cacheFile) + } + + cli.Log.Debugw("verifying cached version", "cache_file", cacheFile) + versionCache, err := lwupdater.LoadCache(cacheFile) + if err != nil { + return err + } + + cli.Log.Debugw("version cache", "content", versionCache) + + // since our check is daily, substract one day from now and compare it + var ( + nowTime = time.Now() + checkTime = nowTime.AddDate(0, 0, -1) + ) + + if versionCache.LastCheckTime.Before(checkTime) { + cli.Event.Feature = featDailyVerCheck + defer cli.SendHoneyvent() + + versionCache.LastCheckTime = nowTime + cli.Log.Debugw("storing new version cache", "content", versionCache) + err := versionCache.StoreCache(cacheFile) + if err != nil { + cli.Event.FeatureData = map[string]interface{}{"silent_error": err.Error()} + return err + } + + lwv, err := versionCheck() + cli.Event.DurationMs = time.Since(nowTime).Milliseconds() + if err != nil { + cli.Event.FeatureData = map[string]interface{}{"silent_error": err.Error()} + return err + } + + cli.Event.FeatureData = lwv + return nil + } + + cli.Log.Debugw("threshold not yet met. skipping daily version check", + "threshold", "1d", + "current_version", versionCache.CurrentVersion, + "latest_version", versionCache.LatestVersion, + "version_outdated", versionCache.Outdated, + "last_check_time", versionCache.LastCheckTime, + "next_check_time", versionCache.LastCheckTime.AddDate(0, 0, 1)) + + return nil +} + +type versionJSON struct { + Version string `json:"version"` + GitSHA string `json:"git_sha"` + BuildTime string `json:"build_time"` + CDK *lwcomponent.State `json:"cdk,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container.go new file mode 100644 index 000000000..e0c855983 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container.go @@ -0,0 +1,142 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/lacework/go-sdk/internal/array" + "github.com/pkg/errors" + flag "github.com/spf13/pflag" +) + +func init() { + // add sub-commands to the 'vulnerability container' command + vulContainerCmd.AddCommand(vulContainerScanCmd) + vulContainerCmd.AddCommand(vulContainerListAssessmentsCmd) + vulContainerCmd.AddCommand(vulContainerListRegistriesCmd) + vulContainerCmd.AddCommand(vulContainerShowAssessmentCmd) + + // add start flag to list-assessments command + vulContainerListAssessmentsCmd.Flags().StringVar(&vulCmdState.Start, + "start", "-24h", "start of the time range", + ) + // add end flag to list-assessments command + vulContainerListAssessmentsCmd.Flags().StringVar(&vulCmdState.End, + "end", "now", "end of the time range", + ) + // range time flag + vulContainerListAssessmentsCmd.Flags().StringVar(&vulCmdState.Range, + "range", "", "natural time range for query", + ) + // add repository flag to list-assessments command + vulContainerListAssessmentsCmd.Flags().StringSliceVarP(&vulCmdState.Repositories, + "repository", "r", []string{}, "filter assessments for specific repositories", + ) + + // add registry flag to list-assessments command + vulContainerListAssessmentsCmd.Flags().StringSliceVarP(&vulCmdState.Registries, + "registry", "", []string{}, "filter assessments for specific registries", + ) + + setPollFlag( + vulContainerScanCmd.Flags(), + ) + + setHtmlFlag( + vulContainerScanCmd.Flags(), + vulContainerShowAssessmentCmd.Flags(), + ) + + setDetailsFlag( + vulContainerScanCmd.Flags(), + vulContainerShowAssessmentCmd.Flags(), + ) + + setSeverityFlag( + vulContainerScanCmd.Flags(), + vulContainerShowAssessmentCmd.Flags(), + ) + + setFailOnSeverityFlag( + vulContainerScanCmd.Flags(), + vulContainerShowAssessmentCmd.Flags(), + ) + + setFailOnFixableFlag( + vulContainerScanCmd.Flags(), + vulContainerShowAssessmentCmd.Flags(), + ) + + setActiveFlag( + vulContainerListAssessmentsCmd.Flags(), + ) + + setFixableFlag( + vulContainerScanCmd.Flags(), + vulContainerShowAssessmentCmd.Flags(), + vulContainerListAssessmentsCmd.Flags(), + ) + + setPackagesFlag( + vulContainerScanCmd.Flags(), + vulContainerShowAssessmentCmd.Flags(), + ) + + setCsvFlag( + vulContainerShowAssessmentCmd.Flags(), + vulContainerListAssessmentsCmd.Flags(), + ) + + // DEPRECATED + vulContainerShowAssessmentCmd.Flags().BoolVar( + &vulCmdState.ImageID, "image_id", false, + "tread the provided sha256 hash as image id", + ) + errcheckWARN(vulContainerShowAssessmentCmd.Flags().MarkDeprecated( + "image_id", "by default we now look up both, image_id and image_digest at once.", + )) +} + +func setPollFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.BoolVar(&vulCmdState.Poll, "poll", false, "poll until the vulnerability scan completes") + } + } +} + +func getContainerRegistries() ([]string, error) { + var ( + registries = make([]string, 0) + regsIntegrations, err = cli.LwApi.V2.ContainerRegistries.List() + ) + if err != nil { + return registries, errors.Wrap(err, "unable to get container registry integrations") + } + + for _, i := range regsIntegrations.Data { + // avoid adding empty registries coming from the new local_scanner and avoid adding duplicate registries + if i.ContainerRegistryDomain() == "" || array.ContainsStr(registries, i.ContainerRegistryDomain()) { + continue + } + + registries = append(registries, i.ContainerRegistryDomain()) + } + + return registries, nil +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_assessments.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_assessments.go new file mode 100644 index 000000000..be3a56ea6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_assessments.go @@ -0,0 +1,679 @@ +package cmd + +import ( + "fmt" + "sort" + "strings" + "time" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/lwtime" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // vulContainerListAssessmentsCmd represents the list-assessments sub-command inside the container + // vulnerability command + vulContainerListAssessmentsCmd = &cobra.Command{ + Use: "list-assessments", + Aliases: []string{"list", "ls"}, + Short: "List container vulnerability assessments (default last 24 hours)", + Long: `List all container vulnerability assessments for the last 24 hours by default. + +To customize the time range use use '--start', '--end', or '--range'. + +The start and end times can be specified in one of the following formats: + + A. A relative time specifier + B. RFC3339 date and time + C. Epoch time in milliseconds + +Or use a natural time range like. + + lacework vuln container list --range yesterday + +The natural time range of 'yesterday' would represent a relative start time of '-1d@d' +and a relative end time of '@d'. + +You can also pass '--fixable' to filter on containers with vulnerabilities that have +fixes available, or '--active' to filter on container images actively running in your +environment.`, + Args: cobra.NoArgs, + PreRunE: func(cmd *cobra.Command, args []string) error { + if vulCmdState.Csv { + cli.EnableCSVOutput() + } + + return nil + }, + RunE: func(_ *cobra.Command, args []string) error { + var ( + // the cache key changes depending on some filters that + // will affect the data returned from our API's + cacheKey = generateContainerVulnListCacheKey() + assessments []vulnerabilityAssessmentSummary + filter api.SearchFilter + start time.Time + end time.Time + err error + ) + + expired := cli.ReadCachedAsset(cacheKey, &assessments) + if expired { + if vulCmdState.Range != "" { + cli.Log.Debugw("retrieving natural time range", "range", vulCmdState.Range) + start, end, err = lwtime.ParseNatural(vulCmdState.Range) + if err != nil { + return errors.Wrap(err, "unable to parse natural time range") + } + + } else { + cli.Log.Debugw("parsing start time", "start", vulCmdState.Start) + start, err = parseQueryTime(vulCmdState.Start) + if err != nil { + return errors.Wrap(err, "unable to parse start time") + } + + cli.Log.Debugw("parsing end time", "end", vulCmdState.End) + end, err = parseQueryTime(vulCmdState.End) + if err != nil { + return errors.Wrap(err, "unable to parse end time") + } + } + + // search for all active containers + cli.Log.Infow("using filter with", "start_time", start, "end_time", end) + filter.TimeFilter = &api.TimeFilter{ + StartTime: &start, + EndTime: &end, + } + + timeRangeMsg := fmt.Sprintf(" in time range (%s to %s)", + start.Format(time.RFC3339), end.Format(time.RFC3339)) + + cli.StartProgress(fmt.Sprintf("Searching for active containers%s...", timeRangeMsg)) + activeContainers, err := cli.LwApi.V2.Entities.ListAllContainersWithFilters( + api.SearchFilter{ + TimeFilter: filter.TimeFilter, + Returns: []string{"mid", "imageId", "startTime"}, + }) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to search for active containers") + } + + cli.Log.Infow("active containers found", + "active_count", activeContainers.Total(), + "entities_count", len(activeContainers.Data), + ) + + // get all container vulnerability assessments + cli.Log.Infow("requesting list of assessments", "start_time", start, "end_time", end) + cli.StartProgress(fmt.Sprintf("Fetching assessments%s...", timeRangeMsg)) + assessments, err = listVulnCtrAssessments(activeContainers, &filter) + cli.StopProgress() + if err != nil { + return err + } + + // write to cache if the request was successful + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), assessments) + } else { + cli.Log.Infow("assessments loaded from cache", "count", len(assessments)) + } + + // apply vuln ctr list-assessment filters (--active, --registries, --repositories, --fixable) + if vulnCtrListAssessmentFiltersEnabled() { + assessments = applyVulnCtrFilters(assessments) + } + + if cli.JSONOutput() { + return cli.OutputJSON(assessments) + } + + if len(assessments) == 0 { + cli.OutputHuman(buildContainerAssessmentsError()) + return nil + } + + // Build table output + assessmentOutput := assessmentSummaryToOutputFormat(assessments) + rows := vulAssessmentsToTable(assessmentOutput) + headers := []string{"Registry", "Repository", "Last Scan", + "Status", "Containers", "Vulnerabilities", "Image Digest"} + switch { + case len(rows) == 0: + cli.OutputHuman(buildContainerAssessmentsError()) + case cli.CSVOutput(): + if err := cli.OutputCSV(headers, rows); err != nil { + return errors.Wrap(err, "failed to create csv output") + } + default: + cli.OutputHuman(renderSimpleTable(headers, rows)) + if !vulCmdState.Active { + cli.OutputHuman( + "\nTry adding '--active' to only show assessments of active containers.\n", + ) + } else if !vulCmdState.Fixable { + cli.OutputHuman( + "\nTry adding '--fixable' to only show assessments with fixable vulnerabilities.\n", + ) + } + } + + return nil + }, + } +) + +func vulnCtrListAssessmentFiltersEnabled() bool { + return len(vulCmdState.Repositories) > 0 || + len(vulCmdState.Registries) > 0 || + vulCmdState.Fixable || + vulCmdState.Active +} + +func applyVulnCtrFilters(assessments []vulnerabilityAssessmentSummary) (filtered []vulnerabilityAssessmentSummary) { + for _, a := range assessments { + if len(vulCmdState.Repositories) > 0 { + if !array.ContainsStr(vulCmdState.Repositories, a.Repository) { + continue + } + } + if len(vulCmdState.Registries) > 0 { + if !array.ContainsStr(vulCmdState.Registries, a.Registry) { + continue + } + } + if vulCmdState.Active { + if a.ActiveContainers == 0 { + continue + } + } + if vulCmdState.Fixable { + if !a.HasFixableVulns() { + continue + } + } + + filtered = append(filtered, a) + } + return +} + +// The process to get the list of container assessments is +// +// 1. Check if the user provided a list of registries and repositories, +// if so, use those filters instead of fetching the entire data from +// all registries, repositories, local scanners, etc. (This is a memory +// utilization improvement) +// 2. If no filter by registries and/or repos, then fetch all data from all +// registries and all local scanners, we purposely split them in two search +// requests since there could be so much data that we get to the 500,000 rows +// if data and we could potentially miss some information +// 3. Either 1) or 2) will generate a tree of unique container vulnerability +// assessments (see the `treeCtrVuln` type), with this tree we will generate +// one last API request to unique evaluations per image (This is a memory +// utilization improvement) +// 4. Finally, if we get information from the queried assessments, we build a +// summary that will ultimately get stored in the cache for subsequent commands +func listVulnCtrAssessments( + activeContainers api.ContainersEntityResponse, filter *api.SearchFilter, +) (assessments []vulnerabilityAssessmentSummary, err error) { + + // Collect only the image ID and the start time to build a tree of + // images, the time they were evaluated, and the evaluation GUID. + // This will tell us all images and their latest evaluation + filter.Returns = []string{"imageId", "startTime", "evalGuid"} + filter.Filters = []api.Filter{} + treeOfContainerVuln := treeCtrVuln{} + + // if the user wants to only list assessments from a subset of registries, + // use that filter instead of fetching data from all registries + if len(vulCmdState.Registries) != 0 { + filter.Filters = append(filter.Filters, + api.Filter{ + Expression: "in", + Field: "evalCtx.image_info.registry", + Values: vulCmdState.Registries, + }) + } + + // if the user wants to only list assessments from a subset of repositories, + // use that filter instead of fetching data from all repositories + if len(vulCmdState.Repositories) != 0 { + filter.Filters = append(filter.Filters, + api.Filter{ + Expression: "in", + Field: "evalCtx.image_info.repo", + Values: vulCmdState.Repositories, + }) + } + + if len(filter.Filters) == 0 { + // if not, then we need to fetch information from 1) all + // container registries and 2) local scanners in two separate + // searches since platform scanners might have way too much + // data which may cause losing the local scanners data + // + // find all container registries + // cli.StartProgress("Fetching container registries...") + registries, err := getContainerRegistries() + // cli.StopProgress() + if err != nil { + return nil, err + } + cli.Log.Infow("container registries found", "count", len(registries)) + + if len(registries) != 0 { + // 1) search for all assessments from configured container registries + filter.Filters = []api.Filter{ + { + Expression: "in", + Field: "evalCtx.image_info.registry", + Values: registries, + }, + } + response, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(*filter) + if err != nil { + return assessments, errors.Wrap(err, "unable to search for container assessments") + } + + treeOfContainerVuln.ParseData(response.Data) + + // 2) search for assessments from local scanners, that is, non container registries + filter.Filters = []api.Filter{ + { + Expression: "not_in", + Field: "evalCtx.image_info.registry", + Values: registries, + }, + } + } else { + response, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(*filter) + if err != nil { + return assessments, errors.Wrap(err, "unable to search for container assessments") + } + + treeOfContainerVuln.ParseData(response.Data) + } + } else { + response, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(*filter) + if err != nil { + return assessments, errors.Wrap(err, "unable to search for container assessments") + } + + treeOfContainerVuln.ParseData(response.Data) + } + + evalGuids := treeOfContainerVuln.ListEvalGuid() + + cli.Log.Infow("evaluation guids", "count", len(evalGuids)) + if len(evalGuids) != 0 { + // Update the filter with the list of evaluation GUIDs and remove the "returns" + filter.Returns = nil + filter.Filters = []api.Filter{ + { + Expression: "in", + Field: "evalGuid", + Values: evalGuids, + }, + } + + response, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(*filter) + if err != nil { + return assessments, errors.Wrap(err, "unable to search for container assessments") + } + + assessments = buildVulnCtrAssessmentSummary(response.Data, activeContainers) + } + return +} + +// treeCtrVuln and ctrVuln are types that help us generate an tree of container +// vulnerability assessments that are unique per image ID, that is, there will +// never be duplicates of the same image with different evaluation guids (evalGuid) +type treeCtrVuln []ctrVuln +type ctrVuln struct { + EvalGUID string + ImageID string + StartTime time.Time +} + +func (v treeCtrVuln) Len() int { + return len(v) +} +func (v treeCtrVuln) Get(imageID string) (*ctrVuln, bool) { + for _, ctr := range v { + if ctr.ImageID == imageID { + return &ctr, true + } + } + return nil, false +} + +func (v treeCtrVuln) ListEvalGuid() (guids []string) { + for _, ctr := range v { + guids = append(guids, ctr.EvalGUID) + } + return +} +func (v treeCtrVuln) ListImageIDs() (ids []string) { + for _, ctr := range v { + ids = append(ids, ctr.ImageID) + } + return +} + +func (v *treeCtrVuln) ParseData(data []api.VulnerabilityContainer) { + for _, ctr := range data { + latestContainer, exist := v.Get(ctr.ImageID) + if exist { + if latestContainer.EvalGUID == ctr.EvalGUID { + continue + } + if ctr.StartTime.After(latestContainer.StartTime) { + v.Replace(*latestContainer, ctrVuln{ + EvalGUID: ctr.EvalGUID, + ImageID: ctr.ImageID, + StartTime: ctr.StartTime, + }) + } + } else { + // @afiune this is NOT thread safe!! But it is also not used in parallel executions + *v = append(*v, ctrVuln{ctr.EvalGUID, ctr.ImageID, ctr.StartTime}) + } + } +} + +// Replace updates an existing ctrVuln in the treeCtrVuln slice with a new ctrVuln +func (v treeCtrVuln) Replace(old ctrVuln, new ctrVuln) { + for i, ctrVuln := range v { + if ctrVuln.EvalGUID == old.EvalGUID { + v[i] = new + break + } + } +} + +type vulnerabilityAssessmentSummary struct { + ImageID string `json:"image_id"` + Repository string `json:"repository"` + Registry string `json:"registry"` + Digest string `json:"digest"` + ScanTime time.Time `json:"scan_time"` + Cves []vulnerabilityCtrSummary `json:"cves"` + ScanStatus string `json:"scan_status"` + ActiveContainers int `json:"active_containers"` + FixableCount int `json:"fixable_count"` + vulnerabilities []string + StatusList []string `json:"-"` +} + +type vulnerabilityCtrSummary struct { + Id string `json:"vuln_id"` + Pkg string `json:"package_name"` + Fixable int `json:"fixable"` + Severity string `json:"severity"` +} + +func (v vulnerabilityAssessmentSummary) Status() string { + if array.ContainsStr(v.StatusList, "VULNERABLE") { + return "VULNERABLE" + } + return "GOOD" +} + +func (v vulnerabilityAssessmentSummary) HasFixableVulns() bool { + for _, c := range v.Cves { + if c.Fixable != 0 { + return true + } + } + return false +} + +func buildVulnCtrAssessmentSummary( + assessments []api.VulnerabilityContainer, activeContainers api.ContainersEntityResponse, +) (uniqueAssessments []vulnerabilityAssessmentSummary) { + + imageMap := map[string]vulnerabilityAssessmentSummary{} + + // build a map for our assessments per image + for _, a := range assessments { + i := fmt.Sprintf("%s-%s-%s", a.EvalCtx.ImageInfo.Registry, a.EvalCtx.ImageInfo.Repo, a.EvalCtx.ImageInfo.ID) + if _, ok := imageMap[i]; ok { + // if the image id assessment has already been added, then append the vulnerabilities + summary := imageMap[i] + summary.StatusList = append(imageMap[i].StatusList, a.Status) + + // check duplicate cves + vulnKey := fmt.Sprintf("%s-%s", a.VulnID, a.FeatureKey.Name) + if !array.ContainsStr(imageMap[i].vulnerabilities, vulnKey) && a.VulnID != "" { + summary.vulnerabilities = append(imageMap[i].vulnerabilities, vulnKey) + summary.Cves = append(imageMap[i].Cves, + vulnerabilityCtrSummary{a.VulnID, a.FeatureKey.Name, a.FixInfo.FixAvailable, a.Severity}, + ) + if a.FixInfo.FixAvailable != 0 { + summary.FixableCount++ + } + } + + imageMap[i] = summary + continue + } + + fixableCount := 0 + if a.FixInfo.FixAvailable != 0 { + fixableCount = 1 + } + + // search for active containers + imageMap[i] = vulnerabilityAssessmentSummary{ + ImageID: a.ImageID, + Repository: a.EvalCtx.ImageInfo.Repo, + Registry: a.EvalCtx.ImageInfo.Registry, + Digest: a.EvalCtx.ImageInfo.Digest, + ScanTime: a.StartTime, + Cves: []vulnerabilityCtrSummary{{a.VulnID, a.FeatureKey.Name, a.FixInfo.FixAvailable, a.Severity}}, + ScanStatus: a.EvalCtx.ImageInfo.Status, + ActiveContainers: activeContainers.Count(a.ImageID), + vulnerabilities: []string{fmt.Sprintf("%s-%s", a.VulnID, a.FeatureKey.Name)}, + StatusList: []string{a.Status}, + FixableCount: fixableCount, + } + } + + // Loop over image map and build result + for _, v := range imageMap { + uniqueAssessments = append(uniqueAssessments, v) + } + return +} + +func buildContainerAssessmentsError() string { + msg := "There are no" + if vulCmdState.Active { + msg = fmt.Sprintf("%s active containers", msg) + } else { + msg = fmt.Sprintf("%s assessments", msg) + } + + if len(vulCmdState.Repositories) != 0 { + msg = fmt.Sprintf("%s for the specified", msg) + if len(vulCmdState.Repositories) == 1 { + msg = fmt.Sprintf("%s repository", msg) + } else { + msg = fmt.Sprintf("%s repositories", msg) + } + } + + if len(vulCmdState.Registries) != 0 { + msg = fmt.Sprintf("%s for the specified", msg) + if len(vulCmdState.Registries) == 1 { + msg = fmt.Sprintf("%s registry", msg) + } else { + msg = fmt.Sprintf("%s registries", msg) + } + } + + if vulCmdState.Fixable { + msg = fmt.Sprintf("%s with fixable vulnerabilities", msg) + } + + return fmt.Sprintf("%s in your environment.\n", msg) +} + +// assessmentSummaryToOutputFormat builds assessmentOutput from the raw response +func assessmentSummaryToOutputFormat(assessments []vulnerabilityAssessmentSummary) []assessmentOutput { + var out []assessmentOutput + + // sort by active containers + sort.Slice(assessments, func(i, j int) bool { + return assessments[i].ActiveContainers > assessments[j].ActiveContainers + }) + + for _, ctr := range assessments { + severities := []string{} + fixableCount := 0 + for _, cve := range ctr.Cves { + severities = append(severities, cve.Severity) + if cve.Fixable != 0 { + fixableCount++ + } + } + + summaryString := severityCtrSummary(severities, ctr.FixableCount) + + out = append(out, assessmentOutput{ + imageRegistry: ctr.Registry, + imageRepo: ctr.Repository, + startTime: ctr.ScanTime.UTC().Format(time.RFC3339), + imageScanStatus: ctr.ScanStatus, + ndvContainers: fmt.Sprintf("%d", ctr.ActiveContainers), + assessmentSummary: summaryString, + imageDigest: ctr.Digest, + }) + } + return out +} + +func severityCtrSummary(severities []string, fixable int) string { + summary := &strings.Builder{} + sevSummaries := make(map[string]int) + for _, s := range severities { + switch s { + case "Critical": + if v, ok := sevSummaries["Critical"]; ok { + sevSummaries["Critical"] = v + 1 + continue + } + sevSummaries["Critical"] = 1 + case "High": + if v, ok := sevSummaries["High"]; ok { + sevSummaries["High"] = v + 1 + continue + } + sevSummaries["High"] = 1 + case "Medium": + if v, ok := sevSummaries["Medium"]; ok { + sevSummaries["Medium"] = v + 1 + continue + } + sevSummaries["Medium"] = 1 + case "Low": + if v, ok := sevSummaries["Low"]; ok { + sevSummaries["Low"] = v + 1 + continue + } + sevSummaries["Low"] = 1 + case "Info": + if v, ok := sevSummaries["Info"]; ok { + sevSummaries["Info"] = v + 1 + continue + } + sevSummaries["Info"] = 1 + } + } + + var keys []string + for k := range sevSummaries { + keys = append(keys, k) + } + + sort.Slice(keys, func(i, j int) bool { + return api.SeverityOrder(keys[i]) < api.SeverityOrder(keys[j]) + }) + + // only output the highest severity + if len(keys) != 0 { + v := sevSummaries[keys[0]] + summary.WriteString(fmt.Sprintf("%d %s", v, keys[0])) + } + + if fixable != 0 { + summary.WriteString(fmt.Sprintf(" %d Fixable", fixable)) + } + + if len(keys) == 0 && fixable == 0 { + summary.WriteString(fmt.Sprintf("None! Time for %s", randomEmoji())) + } + return summary.String() +} + +// vulAssessmentsToTable returns assessments in format compatible with table output +func vulAssessmentsToTable(assessments []assessmentOutput) [][]string { + var out [][]string + for _, assessment := range assessments { + out = append(out, []string{ + assessment.imageRegistry, + assessment.imageRepo, + assessment.startTime, + assessment.imageScanStatus, + assessment.ndvContainers, + assessment.assessmentSummary, + assessment.imageDigest, + }) + } + return out +} + +type assessmentOutput struct { + imageRegistry string + imageRepo string + startTime string + imageScanStatus string + ndvContainers string + assessmentSummary string + imageDigest string +} + +// generateContainerVulnListCacheKey returns the cache key where the CLI will store vulnerability +// assessments for the next few minutes so that consecutive commands run faster and we avoid sending +// duplicate requests to our APIs. +// +// The criteria to generate this cache key is to use the prefix 'vulnerability/container/v2_{HASH}' +// with a hash value at the end of the prefix. The hash is generated based of filters that, if changed +// by the user, will change the data returned from our APIs +func generateContainerVulnListCacheKey() string { + var cacheFiltersHash = cacheFiltersToBuildVulnContainerHash{ + Start: vulCmdState.Start, // mapped to '--start' + End: vulCmdState.End, // mapped to '--end' + Range: vulCmdState.Range, // mapped to '--range' + Repositories: vulCmdState.Repositories, // mapped to '--repository' (multi-flag) + Registries: vulCmdState.Registries, // mapped to '--registry' (multi-flag) + } + return fmt.Sprintf("vulnerability/container/v2_%d", hash(cacheFiltersHash)) +} + +// struct that defines the filters we care about, that is, filters that +// when changed, will generate a different hash +type cacheFiltersToBuildVulnContainerHash struct { + Start string + End string + Range string + Repositories []string + Registries []string +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_registries.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_registries.go new file mode 100644 index 000000000..bc8af7db6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_registries.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var ( + // vulContainerListRegistriesCmd represents the list-registries sub-command inside the container + // vulnerability command + vulContainerListRegistriesCmd = &cobra.Command{ + Use: "list-registries", + Aliases: []string{"list-reg", "registries"}, + Short: "List all container registries configured", + Long: `List all container registries configured in your account.`, + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, args []string) error { + cli.StartProgress("Fetching container registries...") + registries, err := getContainerRegistries() + cli.StopProgress() + if err != nil { + return err + } + if len(registries) == 0 { + msg := `There are no container registries configured in your account. + +Get started by integrating your container registry using the command: + + lacework container-registry create + +If you prefer to configure the integration via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Settings > Integrations > Container Registry. +` + cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) + return nil + } + + if cli.JSONOutput() { + return cli.OutputJSON(registries) + } + + var rows [][]string + for _, acc := range registries { + rows = append(rows, []string{acc}) + } + + cli.OutputHuman(renderSimpleTable([]string{"Container Registries"}, rows)) + return nil + }, + } +) diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_scan.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_scan.go new file mode 100644 index 000000000..d3298b3bc --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_scan.go @@ -0,0 +1,332 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strings" + "time" + + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // vulContainerScanCmd represents the scan sub-command inside the container vulnerability command + vulContainerScanCmd = &cobra.Command{ + Use: "scan ", + Short: "Request an on-demand container vulnerability assessment", + Long: `Request on-demand container vulnerability assessments and view the generated results. + +To list all container registries configured in your account: + + lacework vulnerability container list-registries + +**NOTE:** Scans can take up to 15 minutes to return results. + +Arguments: + container registry where the container image has been published + repository name that contains the container image + either a tag or an image digest to scan (digest format: sha256:1ee...1d3b) + `, + Args: cobra.ExactArgs(3), + RunE: func(c *cobra.Command, args []string) error { + if err := validateSeverityFlags(); err != nil { + return err + } + + err := requestOnDemandContainerVulnerabilityScan(args) + var e *vulnerabilityPolicyError + if errors.As(err, &e) { + c.SilenceUsage = true + } + + return err + }, + } +) + +func requestOnDemandContainerVulnerabilityScan(args []string) error { + cli.Log.Debugw("requesting vulnerability scan", + "registry", args[0], + "repository", args[1], + "tag_or_digest", args[2], + ) + scan, err := cli.LwApi.V2.Vulnerabilities.Containers.Scan(args[0], args[1], args[2]) + if err != nil { + return userFriendlyErrorForOnDemandCtrVulnScan(err, args[0], args[1], args[2]) + } + + cli.Log.Debugw("vulnerability scan", "details", scan) + if scan.Data.RequestID == "" { + return errors.Errorf( + "there is a problem with the vulnerability scan: %s", + scan.Message, + ) + } + + cli.OutputHuman( + "A new vulnerability scan has been requested. (request_id: %s)\n\n", + scan.Data.RequestID, + ) + + if vulCmdState.Poll { + cli.Log.Infow("tracking scan progress", + "param", "--poll", + "request_id", scan.Data.RequestID, + ) + return pollScanStatus(scan.Data.RequestID, args) + } + + if cli.JSONOutput() { + return cli.OutputJSON(scan.Data) + } + return nil +} + +// Creates a user-friendly error message +func userFriendlyErrorForOnDemandCtrVulnScan(err error, registry, repo, tag string) error { + if strings.Contains(err.Error(), + "Could not find integration matching the registry provided", + ) || strings.Contains(err.Error(), + "Could not find vulnerability integrations", + ) { + + registries, errReg := getContainerRegistries() + if errReg != nil { + cli.Log.Debugw("error trying to retrieve configured registries", "error", errReg) + return errors.Errorf("container registry '%s' not found", registry) + } + + if len(registries) == 0 { + msg := `there are no container registries configured in your account. + +Get started by integrating your container registry using the command: + + lacework container-registry create + +If you prefer to configure the integration via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Settings > Integrations > Container Registry. +` + return errors.New(fmt.Sprintf(msg, cli.Account)) + } + + msg := `container registry '%s' not found + +Your account has the following container registries configured: + + > %s + +To integrate a new container registry use the command: + + lacework container-registry create +` + return errors.New(fmt.Sprintf(msg, registry, strings.Join(registries, "\n > "))) + } + + if strings.Contains( + err.Error(), + "Could not successfully send scan request to available integrations for given repo and label", + ) { + + msg := `container image '%s@%s' not found in registry '%s'. + +This error is likely due to a problem with the container registry integration +configured in your account. Verify that the integration was configured with +Lacework using the correct permissions, and that the repository belongs +to the provided registry. + +To view all container registries configured in your account use the command: + + lacework vulnerability container list-registries +` + return errors.Errorf(msg, repo, tag, registry) + } + + return errors.Wrap(err, "unable to request on-demand vulnerability scan") +} + +func pollScanStatus(requestID string, args []string) error { + cli.StartProgress(" Scan running...") + time.Sleep(time.Second * 40) + var ( + retries = 0 + start = time.Now().UTC() + durationTime = start + expPollTime = time.Second + params = map[string]interface{}{"request_id": requestID} + ) + + for { + retries++ + params["retries"] = retries + + cli.Event.DurationMs = time.Since(durationTime).Milliseconds() + durationTime = time.Now() + + cli.Event.Feature = featPollCtrScan + cli.Event.FeatureData = params + + evalGUID, err := checkScanStatus(requestID) + if err != nil { + cli.Event.Error = err.Error() + cli.SendHoneyvent() + return err + } + + if evalGUID == "" { + cli.Log.Debugw("waiting for a retry", "request_id", requestID, "sleep", expPollTime) + cli.SendHoneyvent() + time.Sleep(expPollTime) + expPollTime = time.Duration(retries*retries) * time.Second + continue + } + + cli.Event.DurationMs = time.Since(durationTime).Milliseconds() + params["total_duration_ms"] = time.Since(start).Milliseconds() + params["eval_guid"] = evalGUID + cli.Event.FeatureData = params + cli.SendHoneyvent() + + // reset event fields + cli.Event.DurationMs = 0 + cli.Event.FeatureData = nil + + cli.StopProgress() + + // scan is completed, fetch results using the Search() API but avoid + // using a time range of 7 days and instead just pass the last 24 hours + now := time.Now().UTC() + before := now.AddDate(0, 0, -1) // 1 day from now + filter := api.SearchFilter{ + TimeFilter: &api.TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + Filters: []api.Filter{ + { + Expression: "eq", + Field: "evalCtx.image_info.registry", + Value: args[0], + }, + { + Expression: "eq", + Field: "evalGuid", + Value: evalGUID, + }, + { + Expression: "eq", + Field: "evalCtx.image_info.repo", + Value: args[1], + }, + { + Expression: "eq", + Field: "evalCtx.is_reeval", + Value: "false", + }, + { + Expression: "eq", + Field: "evalCtx.scan_request_props.reqId", + Value: requestID, + }, + { + Expression: "eq", + Field: getTagOrDigestField(args[2]), + Value: args[2], + }, + }, + } + + cli.Log.Debugw("retrieve assessment", "filters", filter.Filters) + + cli.StartProgress("Fetching assessment results...") + assessment, err := cli.LwApi.V2.Vulnerabilities.Containers.Search(filter) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to fetch assessment results") + } + + if len(assessment.Data) == 0 { + return errors.Errorf( + "unable to fetch assessment results from evaluation with id '%s'", evalGUID, + ) + } + + cli.Log.Infow("raw assessment", "data_points", len(assessment.Data)) + filterContainerAssessmentByVulnerable(&assessment) + cli.Log.Infow("filtered assessment (status = vulnerable)", "data_points", len(assessment.Data)) + + if err := outputContainerVulnerabilityAssessment(assessment); err != nil { + return err + } + + if vulFailureFlagsEnabled() { + cli.Log.Infow("failure flags enabled", + "fail_on_severity", vulCmdState.FailOnSeverity, + "fail_on_fixable", vulCmdState.FailOnFixable, + ) + vulnPolicy := NewVulnerabilityPolicyErrorV2( + assessment, + vulCmdState.FailOnSeverity, + vulCmdState.FailOnFixable, + ) + if vulnPolicy.NonCompliant() { + return vulnPolicy + } + } + + return nil + } +} + +func getTagOrDigestField(arg string) string { + // Check if we need to search for a digest or a tag id + if strings.HasPrefix(arg, "sha256:") { + return "evalCtx.image_info.digest" + } + return "evalCtx.image_info.tags[0]" +} + +// checkScanStatus returns the evaluation GUID once the scan is completed, +// if it is not completed, it returns an empty string +func checkScanStatus(requestID string) (string, error) { + cli.Log.Infow("verifying status of vulnerability scan", "request_id", requestID) + scan, err := cli.LwApi.V2.Vulnerabilities.Containers.ScanStatus(requestID) + if err != nil { + return "", errors.Wrap(err, "unable to verify status of the vulnerability scan") + } + + cli.Log.Debugw("vulnerability scan", "details", scan) + status := scan.CheckStatus() + switch status { + case "completed": + cli.Log.Infow("vulnerability scan completed", + "request_id", requestID, "eval_guid", scan.Data.EvalGuid) + return scan.Data.EvalGuid, nil + case "scanning": + return "", nil + default: + return "", errors.Errorf( + "unable to get status: '%s' from vulnerability scan. Use '--debug' to troubleshoot.", status) + } +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_show_assessments.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_show_assessments.go new file mode 100644 index 000000000..8d09b74e5 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_show_assessments.go @@ -0,0 +1,783 @@ +package cmd + +import ( + "fmt" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/lwseverity" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +const regexAllTabs = "(\\t){1,}" + +var ( + // vulContainerShowAssessmentCmd represents the show-assessment sub-command inside the container + // vulnerability command + vulContainerShowAssessmentCmd = &cobra.Command{ + Use: "show-assessment [cve_id]", + Aliases: []string{"show"}, + Short: "Show results of a container vulnerability assessment", + Long: `Show the vulnerability assessment results of the specified container. + +Arguments: + a sha256 hash of a container image (format: sha256:1ee...1d3b) + +Note that the provided SHA is treated first as the image digest, but if no results +are found, this commands tries to use the SHA as the image id. + +To request an on-demand vulnerability scan: + + lacework vulnerability container scan + +To see details for a single cve result in an assessment: + + lacework vulnerability show-assessment [cve_id] +`, + Args: cobra.RangeArgs(1, 2), + PreRunE: func(cmd *cobra.Command, args []string) error { + if vulCmdState.Csv { + cli.EnableCSVOutput() + + // If --details or --packages is not passed, csv outputs nothing; defaulting to --details + if !vulCmdState.Details && !vulCmdState.Packages { + vulCmdState.Details = true + } + } + + if len(args) > 1 { + vulCmdState.Cve = args[1] + } + + return nil + }, + RunE: func(c *cobra.Command, args []string) error { + if err := validateSeverityFlags(); err != nil { + return err + } + err := showContainerAssessmentsWithSha256(args[0]) + var e *vulnerabilityPolicyError + if errors.As(err, &e) { + c.SilenceUsage = true + } + + return err + }, + } +) + +func searchLastestEvaluationGuid(sha string) (string, error) { + var ( + now = time.Now().UTC() + before = now.AddDate(0, 0, -7) // 7 days from ago + filter = api.SearchFilter{ + TimeFilter: &api.TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + Returns: []string{"evalGuid", "startTime"}, + } + ) + + // By default, we display the image digest in the command 'list-assessments', + // so we start by fetching the image using the digest + cli.Log.Infow("retrieve image assessment", "image_digest", sha) + assessment, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(api.SearchFilter{ + Returns: filter.Returns, + TimeFilter: filter.TimeFilter, + Filters: []api.Filter{{ + Expression: "eq", + Field: "evalCtx.image_info.digest", + Value: sha, + }}, + }) + if err != nil { + return "", err + } + + if len(assessment.Data) == 0 { + // provided sha was not an image digest, try using it as an image id instead + cli.Log.Infow("retrieve image assessment", "image_id", sha) + assessment, err = cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(api.SearchFilter{ + Returns: filter.Returns, + TimeFilter: filter.TimeFilter, + Filters: []api.Filter{{ + Expression: "eq", + Field: "evalCtx.image_info.id", + Value: sha, + }}, + }) + if err != nil { + return "", err + } + + if len(assessment.Data) == 0 { + return "", errors.New("no data found") + } + } + + return getUniqueEvalGUID(assessment), nil +} + +func getUniqueEvalGUID(assessment api.VulnerabilitiesContainersResponse) string { + var ( + guid string + startTime time.Time + ) + for _, ctr := range assessment.Data { + if ctr.EvalGUID != guid { + if ctr.StartTime.After(startTime) { + startTime = ctr.StartTime + guid = ctr.EvalGUID + } + } + } + return guid +} + +func showContainerAssessmentsWithSha256(sha string) error { + var ( + cacheKey = fmt.Sprintf("vulnerability/container/%s", sha) + assessment api.VulnerabilitiesContainersResponse + ) + expired := cli.ReadCachedAsset(cacheKey, &assessment) + if expired { + // search for the latest evaluation guid + cli.StartProgress("Searching for latest container evaluation...") + evalGUID, err := searchLastestEvaluationGuid(sha) + cli.StopProgress() + if err != nil { + return errors.Wrapf(err, "unable to find assessment information of image %s", sha) + } + + cli.Log.Infow("latest assessment found", "eval_guid", evalGUID) + + var ( + now = time.Now().UTC() + before = now.AddDate(0, 0, -7) // 7 days from ago + ) + + cli.StartProgress( + fmt.Sprintf("Fetching assessment from container evaluation '%s'...", evalGUID), + ) + assessment, err = cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(api.SearchFilter{ + TimeFilter: &api.TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + Filters: []api.Filter{{ + Expression: "eq", + Field: "evalGuid", + Value: evalGUID, + }}, + }) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to fetch assessment data") + } + + // write to cache if the request was successful + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), assessment) + } else { + cli.Log.Infow("assessment loaded from cache", "data_points", len(assessment.Data)) + } + + filterContainerAssessmentByVulnerable(&assessment) + cli.Log.Infow("filtered assessment (status = vulnerable)", "data_points", len(assessment.Data)) + + if err := outputContainerVulnerabilityAssessment(assessment); err != nil { + return err + } + + if vulFailureFlagsEnabled() { + cli.Log.Infow("failure flags enabled", + "fail_on_severity", vulCmdState.FailOnSeverity, + "fail_on_fixable", vulCmdState.FailOnFixable, + ) + vulnPolicy := NewVulnerabilityPolicyErrorV2( + assessment, + vulCmdState.FailOnSeverity, + vulCmdState.FailOnFixable, + ) + if vulnPolicy.NonCompliant() { + return vulnPolicy + } + } + + return nil +} + +func buildVulnContainerAssessmentCve(assessment api.VulnerabilitiesContainersResponse) error { + assessment.FilterSingleVulnIDData(vulCmdState.Cve) + + if cli.JSONOutput() { + return cli.OutputJSON(assessment.Data) + } + + if len(assessment.Data) == 0 { + cli.OutputHuman("Unable to find results for '%s'\n", vulCmdState.Cve) + return nil + } + + var details vulnerabilityDetailsReport + details.VulnerabilityDetails = filterVulnerabilityContainer(assessment.Data) + + cli.OutputHuman(buildVulnerabilitySingleCveReportTable(details)) + return nil +} + +func filterContainerAssessmentByVulnerable(assessment *api.VulnerabilitiesContainersResponse) { + var vulnerabilities []api.VulnerabilityContainer + for _, a := range assessment.Data { + if a.Status == "VULNERABLE" { + vulnerabilities = append(vulnerabilities, a) + } + } + assessment.Data = vulnerabilities +} + +func outputContainerVulnerabilityAssessment(assessment api.VulnerabilitiesContainersResponse) error { + if len(assessment.Data) == 0 { + if cli.JSONOutput() { + // if no assessments are found return empty array + return cli.OutputJSON([]any{}) + } + cli.OutputHuman( + "Great news! This container image has no vulnerabilities... (time for %s)\n", + randomEmoji(), + ) + return nil + } + + if vulCmdState.Cve != "" { + if err := buildVulnContainerAssessmentCve(assessment); err != nil { + return err + } + } else { + if err := buildVulnContainerAssessmentReports(assessment); err != nil { + return err + } + } + + return nil +} + +// Build the cli output for vuln ctr 'show-assessment' command +func buildVulnContainerAssessmentReports(assessment api.VulnerabilitiesContainersResponse) error { + var ( + filteredAssessment = assessment + summaryReport = buildVulnerabilitySummaryReportTable(assessment) + details vulnerabilityDetailsReport + ) + + details.VulnerabilityDetails = filterVulnerabilityContainer(assessment.Data) + filteredAssessment.Data = details.VulnerabilityDetails.Filtered + details.Packages = filterVulnContainerImagePackages(details.VulnerabilityDetails.Filtered) + details.Packages.totalUnfiltered = countVulnContainerImagePackages(assessment.Data) + + switch { + case cli.JSONOutput(): + return cli.OutputJSON(filteredAssessment.Data) + case cli.CSVOutput(): + if !(vulCmdState.Details || vulCmdState.Packages || vulFiltersEnabled()) { + return nil + } + + if vulCmdState.Packages { + return cli.OutputCSV([]string{ + "CVE Count", "Severity", "Package", "Current Version", "Fix Version"}, + vulContainerImagePackagesToTable(details.Packages)) + } + + return cli.OutputCSV([]string{ + "CVE ID", "Severity", "CVSSv2", "CVSSv3", "Package", "Current Version", + "Fix Version", "Version Format", "Feed", "Src", "Start Time", "Status", + "Namespace", "Image Digest", "Image ID", "Image Repo", "Image Registry", + "Image Size", "Introduced in Layer"}, + vulContainerImageLayersToCSV(filteredAssessment.Data)) + default: + if len(filteredAssessment.Data) == 0 { + if vulCmdState.Severity != "" { + cli.OutputHuman("There are no vulnerabilities found for this severity\n") + return nil + } + + cli.OutputHuman( + "Great news! This container image has no vulnerabilities... (time for %s)\n", + randomEmoji(), + ) + return nil + } + + detailsReport := buildVulnerabilityDetailsReportTable(details) + cli.OutputHuman(buildVulnContainerAssessmentReportTable(summaryReport, detailsReport)) + if vulCmdState.Html { + if err := generateVulnAssessmentHTML(filteredAssessment); err != nil { + return err + } + } + } + + return nil +} + +func buildVulnerabilitySingleCveReportTable(details vulnerabilityDetailsReport) string { + var singleCveTable = strings.Builder{} + var singleCveTableContent = strings.Builder{} + + // sort by highest severity + sort.Slice(details.VulnerabilityDetails.Vulnerabilities, func(i, j int) bool { + sev1 := lwseverity.NewSeverity(details.VulnerabilityDetails.Vulnerabilities[i].Severity) + sev2 := lwseverity.NewSeverity(details.VulnerabilityDetails.Vulnerabilities[j].Severity) + return sev1 < sev2 + }) + + for _, cveData := range details.VulnerabilityDetails.Vulnerabilities { + detailsTable := renderCustomTable([]string{}, + [][]string{ + {"PACKAGE", cveData.PackageName}, + {"CURRENT VERSION", cveData.CurrentVersion}, + {"NAMESPACE", cveData.Namespace}, + {"SEVERITY", cveData.Severity}, + {"FEED", cveData.Feed}, + {"SRC", cveData.Src}, + {"START TIME", cveData.StartTime}, + {"VERSION FORMAT", cveData.VersionFormat}, + {"FIX VERSION", cveData.FixVersion}, + {"STATUS", cveData.Status}, + }, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetColWidth(150) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ) + + layerTable := renderCustomTable([]string{"INTRODUCED IN LAYERS"}, + introducedInLayerToTable(cveData), + tableFunc(func(t *tablewriter.Table) { + t.SetRowLine(true) + t.SetBorder(false) + t.SetAutoWrapText(true) + t.SetAlignment(tablewriter.ALIGN_LEFT) + t.SetColumnSeparator(" ") + }), + ) + + singleCveTableContent.WriteString(fmt.Sprintf("%s\n", detailsTable)) + singleCveTableContent.WriteString(layerTable) + } + + cveSummaryTable := renderOneLineCustomTable(details.VulnerabilityDetails.Vulnerabilities[0].Name, + singleCveTableContent.String(), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ) + + singleCveTable.WriteString(cveSummaryTable) + return singleCveTable.String() +} + +func introducedInLayerToTable(vuln vulnTable) (resourceTable [][]string) { + for _, layer := range vuln.CreatedBy { + resourceTable = append(resourceTable, []string{layer}) + } + return +} + +func buildVulnerabilityDetailsReportTable(details vulnerabilityDetailsReport) string { + report := &strings.Builder{} + + if vulCmdState.Details || vulCmdState.Packages || vulFiltersEnabled() || vulCmdState.Cve != "" { + if vulCmdState.Packages { + vulnPackagesTable := vulContainerImagePackagesToTable(details.Packages) + + report.WriteString( + renderSimpleTable( + []string{"CVE Count", "Severity", "Package", "Current Version", "Fix Version"}, + vulnPackagesTable, + ), + ) + + if vulFiltersEnabled() { + filteredOutput := fmt.Sprintf("%v of %v packages showing \n", + details.Packages.totalPackages, details.Packages.totalUnfiltered) + report.WriteString(filteredOutput) + } + } else { + vulnImageTable := vulContainerImageLayersToTable(details.VulnerabilityDetails) + + report.WriteString( + renderCustomTable( + []string{"CVE ID", "Severity", "Package", "Current Version", + "Fix Version", "Introduced in Layer", "Status"}, + vulnImageTable, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetRowLine(true) + t.SetColumnSeparator(" ") + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + ) + + if vulFiltersEnabled() { + filteredOutput := fmt.Sprintf("%v of %v vulnerabilities showing \n", + details.VulnerabilityDetails.TotalVulnerabilitiesShowing, details.VulnerabilityDetails.TotalVulnerabilities) + report.WriteString(filteredOutput) + } + if !vulCmdState.Html { + report.WriteString("\nTry adding '--packages' to show a list of packages with CVE count.\n") + } + } + } + + return report.String() +} + +func buildVulnerabilitySummaryReportTable(response api.VulnerabilitiesContainersResponse) string { + assessment := response.Data + mainReport := &strings.Builder{} + mainReport.WriteString( + renderCustomTable( + []string{ + "Container Image Details", + "Vulnerabilities", + }, + [][]string{{ + renderCustomTable([]string{}, + vulContainerImageToTable(assessment[0].EvalCtx.ImageInfo), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator("") + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + renderCustomTable([]string{"Severity", "Count", "Fixable"}, + vulContainerAssessmentToCountsTable(response), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + }}, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + t.SetColumnSeparator(" ") + }), + ), + ) + + return mainReport.String() +} + +func filterVulnContainerImagePackages(image []api.VulnerabilityContainer) filteredPackageTable { + var filteredPackages []packageTable + var aggregatedPackages []packageTable + var ( + vulnKey string + vulnKeys = []string{} + ) + + for _, i := range image { + vulnKey = fmt.Sprintf("%s-%s-%s", i.VulnID, i.FeatureKey.Name, i.FeatureKey.Version) + if array.ContainsStr(vulnKeys, vulnKey) { + continue + } + vulnKeys = append(vulnKeys, vulnKey) + pack := packageTable{ + cveCount: 1, + severity: cases.Title(language.English).String(i.Severity), + packageName: i.FeatureKey.Name, + currentVersion: i.FeatureKey.Version, + fixVersion: i.FixInfo.FixedVersion, + } + + // filter fixable + if vulCmdState.Fixable && i.FixInfo.FixedVersion == "" { + filteredPackages = aggregatePackages(filteredPackages, pack) + continue + } + + // filter severity + if vulCmdState.Severity != "" { + if filterSeverity(i.Severity, vulCmdState.Severity) { + filteredPackages = aggregatePackages(filteredPackages, pack) + continue + } + } + aggregatedPackages = aggregatePackages(aggregatedPackages, pack) + } + + return filteredPackageTable{packages: aggregatedPackages, totalPackages: len(aggregatedPackages)} +} + +func countVulnContainerImagePackages(image []api.VulnerabilityContainer) int { + var aggregatedPackages []packageTable + + for _, i := range image { + pack := packageTable{ + cveCount: 1, + severity: cases.Title(language.English).String(i.Severity), + packageName: i.FeatureKey.Name, + currentVersion: i.FeatureKey.Version, + fixVersion: i.FixInfo.FixedVersion, + } + aggregatedPackages = aggregatePackages(aggregatedPackages, pack) + } + + return len(aggregatedPackages) +} + +func vulContainerImagePackagesToTable(packageTable filteredPackageTable) [][]string { + var out [][]string + + for _, p := range packageTable.packages { + out = append(out, []string{ + strconv.Itoa(p.cveCount), + p.severity, + p.packageName, + p.currentVersion, + p.fixVersion, + }) + } + + // order by severity and package name + sort.Slice(out, func(i, j int) bool { + if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { + return out[i][2] < out[j][2] + } + return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) + }) + + return out +} + +func filterVulnerabilityContainer(image []api.VulnerabilityContainer) filteredImageTable { + var ( + vulns = make(map[string]vulnTable) + introducedInMap = make(map[string][]string) + vulnIDs []string + vulnsCount int + vulnList []vulnTable + filtered []api.VulnerabilityContainer + ) + + for _, i := range image { + vulnKey := fmt.Sprintf("%s-%s-%s", i.VulnID, i.FeatureKey.Name, i.FeatureKey.Version) + vulnIDs = append(vulnIDs, vulnKey) + // filter: severity + if vulCmdState.Severity != "" { + if filterSeverity(i.Severity, vulCmdState.Severity) { + continue + } + } + // filter: fixable + if vulCmdState.Fixable && i.FixInfo.FixedVersion == "" { + continue + } + + // Format IntroducedIn Field. In v2 response this field is not formatted with new lines. + regex := regexp.MustCompile(regexAllTabs) + introducedIn := regex.ReplaceAllString(i.FeatureProps.IntroducedIn, "\n") + + introducedInMap[vulnKey] = append(introducedInMap[vulnKey], introducedIn) + + if _, ok := vulns[vulnKey]; !ok { + vulns[vulnKey] = vulnTable{ + Name: i.VulnID, + Severity: i.Severity, + PackageName: i.FeatureKey.Name, + CurrentVersion: i.FeatureKey.Version, + FixVersion: i.FixInfo.FixedVersion, + Namespace: i.FeatureKey.Namespace, + Feed: i.FeatureProps.Feed, + StartTime: i.StartTime.Format(time.RFC3339), + VersionFormat: i.FeatureProps.VersionFormat, + Src: i.FeatureProps.Src, + // Todo(v2): CVSSv3Score is missing from V2 + CVSSv3Score: 0, + // Todo(v2): CVSSv2Score is missing from V2 + CVSSv2Score: 0, + Status: i.Status, + } + } + + filtered = append(filtered, i) + } + + // Set the aggregated introduced by layers for each vuln + for k, v := range introducedInMap { + vulnTable := vulns[k] + vulnTable.CreatedBy = v + vulns[k] = vulnTable + } + + var uniqueIDs []string = array.Unique(vulnIDs) + vulnsCount = len(uniqueIDs) + + for _, v := range vulns { + vulnList = append(vulnList, v) + } + + return filteredImageTable{ + Vulnerabilities: vulnList, + TotalVulnerabilitiesShowing: len(vulns), + TotalVulnerabilities: vulnsCount, + Filtered: filtered, + } +} + +func vulContainerImageLayersToCSV(assessment []api.VulnerabilityContainer) [][]string { + var out [][]string + for _, vuln := range assessment { + out = append(out, []string{ + vuln.VulnID, + vuln.Severity, + strconv.FormatFloat(0.0, 'f', 1, 64), + strconv.FormatFloat(0.0, 'f', 1, 64), + vuln.FeatureKey.Name, + vuln.FeatureKey.Version, + vuln.FixInfo.FixedVersion, + vuln.FeatureProps.VersionFormat, + vuln.FeatureProps.Feed, + vuln.FeatureProps.Src, + vuln.StartTime.Format(time.RFC3339), + vuln.FeatureKey.Namespace, + vuln.EvalCtx.ImageInfo.ID, + vuln.EvalCtx.ImageInfo.Digest, + vuln.EvalCtx.ImageInfo.Repo, + vuln.EvalCtx.ImageInfo.Registry, + strconv.Itoa(vuln.EvalCtx.ImageInfo.Size), + vuln.FeatureProps.IntroducedIn, + }) + } + + sort.Slice(out, func(i, j int) bool { + if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { + return out[i][4] < out[j][4] + } + return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) + }) + + return out +} + +func vulContainerImageLayersToTable(imageTable filteredImageTable) [][]string { + var out [][]string + var createdByKeys = make(map[string]bool) + + for _, vuln := range imageTable.Vulnerabilities { + introducedBy := strings.Join(vuln.CreatedBy, ",") + // if the same vuln is introduced in more than 1 layer, only display the number of layers + if len(vuln.CreatedBy) > 1 { + introducedBy = fmt.Sprintf("introduced in %d layers...", len(vuln.CreatedBy)) + } + + if !createdByKeys[fmt.Sprintf("%s-%s", vuln.Name, vuln.CurrentVersion)] { + out = append(out, []string{ + vuln.Name, + vuln.Severity, + vuln.PackageName, + vuln.CurrentVersion, + vuln.FixVersion, + introducedBy, + vuln.Status, + }) + } + + createdByKeys[fmt.Sprintf("%s-%s", vuln.Name, vuln.CurrentVersion)] = true + } + + sort.Slice(out, func(i, j int) bool { + if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { + return out[i][2] < out[j][2] + } + return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) + }) + + return out +} + +func vulContainerAssessmentToCountsTable(assessment api.VulnerabilitiesContainersResponse) [][]string { + return [][]string{ + {"Critical", fmt.Sprint(assessment.CriticalVulnerabilities()), + fmt.Sprint(assessment.VulnFixableCount("critical"))}, + {"High", fmt.Sprint(assessment.HighVulnerabilities()), + fmt.Sprint(assessment.VulnFixableCount("high"))}, + {"Medium", fmt.Sprint(assessment.MediumVulnerabilities()), + fmt.Sprint(assessment.VulnFixableCount("medium"))}, + {"Low", fmt.Sprint(assessment.LowVulnerabilities()), + fmt.Sprint(assessment.VulnFixableCount("low"))}, + {"Info", fmt.Sprint(assessment.InfoVulnerabilities()), + fmt.Sprint(assessment.VulnFixableCount("info"))}, + } +} + +func vulContainerImageToTable(image api.ImageInfo) [][]string { + return [][]string{ + {"ID", image.ID}, + {"Digest", image.Digest}, + {"Registry", image.Registry}, + {"Repository", image.Repo}, + {"Size", byteCountBinary(image.Size)}, + {"Created At", time.UnixMilli(image.CreatedTime).UTC().Format(time.RFC3339)}, + {"Tags", strings.Join(image.Tags, ",")}, + } +} + +func aggregatePackages(slice []packageTable, s packageTable) []packageTable { + for i, item := range slice { + if item.packageName == s.packageName && + item.currentVersion == s.currentVersion && + item.severity == s.severity && + item.fixVersion == s.fixVersion { + slice[i].cveCount++ + return slice + } + } + return append(slice, s) +} + +type vulnerabilityDetailsReport struct { + VulnerabilityDetails filteredImageTable + Packages filteredPackageTable +} + +type filteredImageTable struct { + Vulnerabilities []vulnTable + TotalVulnerabilitiesShowing int + TotalVulnerabilities int + Filtered []api.VulnerabilityContainer +} + +type vulnTable struct { + Name string + Severity string + PackageName string + CurrentVersion string + FixVersion string + CreatedBy []string + CVSSv2Score float64 + CVSSv3Score float64 + Status string + Namespace string + Feed string + Src string + StartTime string + VersionFormat string +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host.go new file mode 100644 index 000000000..f5f290d4b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host.go @@ -0,0 +1,247 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" +) + +var ( + // the maximum number of packages per scan request + manifestPkgsCap = 1000 + + // the package manifest file + pkgManifestFile string + + // automatically generate the package manifest from the local host + pkgManifestLocal bool +) + +func init() { + // add sub-commands to the 'vulnerability host' command + vulHostCmd.AddCommand(vulHostScanPkgManifestCmd) + vulHostCmd.AddCommand(vulHostGenPkgManifestCmd) + vulHostCmd.AddCommand(vulHostListCvesCmd) + vulHostCmd.AddCommand(vulHostListHostsCmd) + vulHostCmd.AddCommand(vulHostShowAssessmentCmd) + + setFixableFlag( + vulHostListCvesCmd.Flags(), + vulHostShowAssessmentCmd.Flags(), + vulHostScanPkgManifestCmd.Flags(), + ) + + setPackagesFlag( + vulHostListCvesCmd.Flags(), + vulHostShowAssessmentCmd.Flags(), + vulHostScanPkgManifestCmd.Flags(), + ) + + setDetailsFlag( + vulHostShowAssessmentCmd.Flags(), + ) + + setSeverityFlag( + vulHostListCvesCmd.Flags(), + vulHostShowAssessmentCmd.Flags(), + ) + + setFailOnSeverityFlag( + vulHostShowAssessmentCmd.Flags(), + vulHostScanPkgManifestCmd.Flags(), + ) + + setFailOnFixableFlag( + vulHostShowAssessmentCmd.Flags(), + vulHostScanPkgManifestCmd.Flags(), + ) + + setActiveFlag( + vulHostShowAssessmentCmd.Flags(), + vulHostListCvesCmd.Flags(), + ) + + setCsvFlag( + vulHostListCvesCmd.Flags(), + vulHostListHostsCmd.Flags(), + vulHostShowAssessmentCmd.Flags(), + ) + + setTimeRangeFlags( + vulHostListCvesCmd.Flags(), + vulHostListHostsCmd.Flags(), + ) + + // the package manifest file + vulHostScanPkgManifestCmd.Flags().StringVarP(&pkgManifestFile, + "file", "f", "", + "path to a package manifest to scan", + ) + + // automatically generate the package manifest from the local host + vulHostScanPkgManifestCmd.Flags().BoolVarP(&pkgManifestLocal, + "local", "l", false, + "automatically generate the package manifest from the local host", + ) + + // the collector_type of the assessment + vulHostShowAssessmentCmd.Flags().StringVar(&vulCmdState.CollectorType, + "collector_type", vulnHostCollectorTypeAgentless, + "filter assessments by collector type (Agent or Agentless)", + ) +} + +func cvesSummary(hosts []api.VulnerabilityHost) map[string]VulnCveSummary { + uniqueCves := make(map[string]VulnCveSummary) + for _, host := range hosts { + if host.VulnID == "" { + continue + } + + if v, ok := uniqueCves[host.VulnID]; ok { + if array.ContainsStr(v.Hostnames, host.EvalCtx.Hostname) { + continue + } + + v.Count++ + v.Hostnames = append(v.Hostnames, host.EvalCtx.Hostname) + uniqueCves[host.VulnID] = v + continue + } + sum := VulnCveSummary{Host: host, Count: 1} + sum.Hostnames = append(sum.Hostnames, sum.Host.EvalCtx.Hostname) + uniqueCves[host.VulnID] = sum + } + return uniqueCves +} + +func aggregatePackagesWithHosts(slice []packageTable, s packageTable, host bool, hasFix bool) []packageTable { + for i, item := range slice { + // if packages are the same, group together + if item.equals(s) { + slice[i].cveCount++ + if host { + slice[i].hostCount++ + } + if hasFix { + slice[i].fixes++ + } + return slice + } + } + return append(slice, s) +} + +func filterHostCVEsTable(cves map[string]VulnCveSummary) (map[string]VulnCveSummary, string) { + var out map[string]VulnCveSummary + var filteredCves = 0 + var totalCves = 0 + + out, filtered, total := filterHostVulnCVEs(cves) + filteredCves += filtered + totalCves += total + + if filteredCves > 0 { + showing := totalCves - filteredCves + return out, fmt.Sprintf("\n%d of %d cve(s) showing\n", showing, totalCves) + } + + return out, "" +} + +func filterHostVulnCVEs(cves map[string]VulnCveSummary) (map[string]VulnCveSummary, int, int) { + var ( + filtered = 0 + total = 0 + out = make(map[string]VulnCveSummary) + ) + + for _, c := range cves { + cve := c.Host + total++ + // if the user wants to show only vulnerabilities of active packages + if vulCmdState.Active && cve.PackageActive() == "" { + filtered++ + continue + } + if vulCmdState.Fixable && (cve.FixInfo.FixAvailable == "" || cve.FixInfo.FixAvailable == "0") { + filtered++ + continue + } + + if vulCmdState.Severity != "" { + if filterSeverity(cve.Severity, vulCmdState.Severity) { + filtered++ + continue + } + } + out[cve.VulnID] = c + } + + return out, filtered, total +} + +func hostVulnAssessmentToCountsTable(counts api.HostVulnCounts) [][]string { + return [][]string{ + {"Critical", fmt.Sprint(counts.Critical), + fmt.Sprint(counts.CritFixable)}, + {"High", fmt.Sprint(counts.High), + fmt.Sprint(counts.HighFixable)}, + {"Medium", fmt.Sprint(counts.Medium), + fmt.Sprint(counts.MedFixable)}, + {"Low", fmt.Sprint(counts.Low), + fmt.Sprint(counts.LowFixable)}, + {"Info", fmt.Sprint(counts.Info), + fmt.Sprint(counts.InfoFixable)}, + } +} + +func buildHostVulnCVEsToTableError() string { + msg := "There are no" + if vulCmdState.Fixable { + msg = fmt.Sprintf("%s fixable", msg) + } + + if vulCmdState.Severity != "" { + msg = fmt.Sprintf("%s %s", msg, vulCmdState.Severity) + } + + msg = fmt.Sprintf("%s vulnerabilities", msg) + + if vulCmdState.Active { + msg = fmt.Sprintf("%s of packages actively running", msg) + } + return fmt.Sprintf("%s in your environment.\n", msg) +} + +func summaryToHostList(sum map[string]VulnCveSummary) (hosts []api.VulnerabilityHost) { + for _, v := range sum { + hosts = append(hosts, v.Host) + } + return +} + +type VulnCveSummary struct { + Host api.VulnerabilityHost + Count int + Hostnames []string +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_gen_package_manifest.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_gen_package_manifest.go new file mode 100644 index 000000000..3be5b4acb --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_gen_package_manifest.go @@ -0,0 +1,48 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // vulHostGenPkgManifestCmd represents the 'lacework vuln host generate-pkg-manifest' command + vulHostGenPkgManifestCmd = &cobra.Command{ + Use: "generate-pkg-manifest", + Args: cobra.NoArgs, + Short: "Generates a package-manifest from the local host", + Long: `Generates a package-manifest formatted for usage with the Lacework +scan package-manifest API. + +Additionally, you can automatically generate a package-manifest from +the local host and send it directly to the Lacework API with the command: + + lacework vulnerability host scan-pkg-manifest --local`, + RunE: func(_ *cobra.Command, _ []string) error { + manifest, err := cli.GeneratePackageManifest() + if err != nil { + return errors.Wrap(err, "unable to generate package manifest") + } + + return cli.OutputJSON(manifest) + }, + } +) diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_cves.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_cves.go new file mode 100644 index 000000000..5fc55eaba --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_cves.go @@ -0,0 +1,352 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sort" + "strconv" + "time" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/array" + "github.com/lacework/go-sdk/lwseverity" + "github.com/lacework/go-sdk/lwtime" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +var ( + // vulHostListCvesCmd represents the 'lacework vuln host list-cves' command + vulHostListCvesCmd = &cobra.Command{ + Use: "list-cves", + Args: cobra.NoArgs, + PreRunE: func(_ *cobra.Command, _ []string) error { + if vulCmdState.Csv { + cli.EnableCSVOutput() + } + return nil + }, + Short: "List the CVEs found in the hosts in your environment", + Long: `List the CVEs found in the hosts in your environment. + +Filter results to only show vulnerabilities actively running in your environment +with fixes: + + lacework vulnerability host list-cves --active --fixable`, + RunE: func(_ *cobra.Command, args []string) error { + if err := validateSeverityFlags(); err != nil { + return err + } + var ( + filter api.SearchFilter + start time.Time + end time.Time + err error + ) + + if vulCmdState.Range != "" { + cli.Log.Debugw("retrieving natural time range", "range", vulCmdState.Range) + start, end, err = lwtime.ParseNatural(vulCmdState.Range) + if err != nil { + return errors.Wrap(err, "unable to parse natural time range") + } + + } else { + cli.Log.Debugw("parsing start time", "start", vulCmdState.Start) + start, err = parseQueryTime(vulCmdState.Start) + if err != nil { + return errors.Wrap(err, "unable to parse start time") + } + + cli.Log.Debugw("parsing end time", "end", vulCmdState.End) + end, err = parseQueryTime(vulCmdState.End) + if err != nil { + return errors.Wrap(err, "unable to parse end time") + } + } + + filter.TimeFilter = &api.TimeFilter{ + StartTime: &start, + EndTime: &end, + } + + cli.StartProgress("Fetching CVEs in your environment...") + response, err := cli.LwApi.V2.Vulnerabilities.Hosts.SearchAllPages(filter) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get CVEs from hosts") + } + + if err := buildListCVEReports(response.Data); err != nil { + return err + } + return nil + }, + } +) + +// Build the cli output for vuln host list-cves +func buildListCVEReports(cves []api.VulnerabilityHost) error { + uniqueCves := cvesSummary(cves) + filteredCves, filtered := filterHostCVEsTable(uniqueCves) + + if cli.JSONOutput() { + if filteredCves == nil { + if err := cli.OutputJSON(buildHostVulnCVEsToTableError()); err != nil { + return err + } + } else { + // fix here too + if err := cli.OutputJSON(summaryToHostList(filteredCves)); err != nil { + return err + } + } + return nil + } + + if len(cves) == 0 { + // @afiune add a helpful message, possible things are: + // 1) host vuln feature is not enabled on the account + // 2) user doesn't have agents deployed + // 3) there are actually NO vulnerabilities on any host + cli.OutputHuman("There are no vulnerabilities on any host in your environment.\n") + return nil + } + + // packages output + if vulCmdState.Packages { + packages, filteredPackages := hostVulnListCvesPackagesTable(cves) + if cli.CSVOutput() { + + // order by cve count + + return cli.OutputCSV( + []string{"CVE Count", "Highest", "Package", "Current Version", "Fix Version", "Pkg Status", "Hosts"}, + packages, + ) + } + vulnListCvesPackagesOutput(packages, filteredPackages) + return nil + } + + rows := hostVulnCVEsTable(filteredCves) + if len(rows) == 0 { + cli.OutputHuman(buildHostVulnCVEsToTableError()) + return nil + } + + if cli.CSVOutput() { + return cli.OutputCSV( + []string{"CVE ID", "Severity", "CvssV2", "CvssV3", "Package", "Current Version", + "Fix Version", "OS Version", "Hosts", "Pkg Status", "Vuln Status"}, + rows, + ) + } + + cli.OutputHuman( + renderSimpleTable( + []string{"CVE ID", "Severity", "CvssV2", "CvssV3", "Package", "Current Version", + "Fix Version", "OS Version", "Hosts", "Pkg Status", "Vuln Status"}, + rows, + ), + ) + + if filtered != "" { + cli.OutputHuman(filtered) + } + + if !vulCmdState.Active { + cli.OutputHuman( + "\nTry adding '--active' to only show vulnerabilities of packages actively running.\n", + ) + } else if !vulCmdState.Fixable { + cli.OutputHuman( + "\nTry adding '--fixable' to only show fixable vulnerabilities.\n", + ) + } + return nil +} + +func vulnListCvesPackagesOutput(packages [][]string, filteredPackagesMsg string) { + // sort by highest cve count + sort.Slice(packages, func(i, j int) bool { + return stringToInt(packages[i][0]) > stringToInt(packages[j][0]) + }) + + cli.OutputHuman( + renderSimpleTable( + []string{ + "CVE Count", + "Highest Severity", + "Package", + "Current Version", + "Fix Version", + "Pkg Status", + "Hosts Impacted", + }, + packages, + ), + ) + if filteredPackagesMsg != "" { + cli.OutputHuman(filteredPackagesMsg) + } +} + +func hostVulnListCvesPackagesTable(cves []api.VulnerabilityHost) ([][]string, string) { + var ( + out [][]string + filteredPackages []string + aggregatedPackages []packageTable + ) + + // Get all unique package names + var packageNames []string + for _, c := range cves { + if c.VulnID != "" { + packageNames = append(packageNames, c.FeatureKey.Name) + } + } + + var uniquePackageNames []string = array.Unique(packageNames) + var added []string + + for _, u := range uniquePackageNames { + var ( + pack packageTable + cveIDs []string + hosts []string + severities []lwseverity.Severity + active string + packageIdentifier string + ) + for _, host := range cves { + packageIdentifier = fmt.Sprintf("%s-%s", host.VulnID, host.FeatureKey.VersionInstalled) + if host.FeatureKey.Name == u { + if host.PackageActive() == "ACTIVE" { + active = "ACTIVE" + } + + if array.ContainsStr(added, host.FeatureKey.Name) { + if host.Severity != "" { + cveIDs = append(cveIDs, packageIdentifier) + severities = append(severities, lwseverity.NewSeverity(host.Severity)) + hosts = append(hosts, host.EvalCtx.Hostname) + } + continue + } + + pack = packageTable{ + severity: cases.Title(language.English).String(host.Severity), + packageName: host.FeatureKey.Name, + currentVersion: host.FeatureKey.VersionInstalled, + fixVersion: host.FixInfo.FixedVersion, + } + + added = append(added, host.FeatureKey.Name) + cveIDs = append(cveIDs, fmt.Sprintf("%s-%s", host.VulnID, host.FeatureKey.VersionInstalled)) + severities = append(severities, lwseverity.NewSeverity(host.Severity)) + hosts = append(hosts, host.EvalCtx.Hostname) + } + } + + // Exclude filtered packages and packages without vulns + if !array.ContainsStr(filteredPackages, packageIdentifier) && len(cveIDs) > 0 { + var unqCves []string = array.Unique(cveIDs) + var unqHosts []string = array.Unique(hosts) + pack.packageStatus = active + pack.cveCount = len(unqCves) + pack.hostCount = len(unqHosts) + + // set highest known severity of the package + if len(severities) > 0 { + lwseverity.SortSlice(severities) + pack.severity = severities[0].GetSeverity() + } + aggregatedPackages = append(aggregatedPackages, pack) + } + } + + for _, p := range aggregatedPackages { + // apply package filters + if vulCmdState.Active && p.packageStatus == "" { + filteredPackages = append(filteredPackages, p.packageName) + continue + } + + if vulCmdState.Fixable && p.fixVersion == "" { + filteredPackages = append(filteredPackages, p.packageName) + continue + } + + if vulCmdState.Severity != "" { + if p.severity == "Unknown" { + continue + } + if lwseverity.ShouldFilter(p.severity, vulCmdState.Severity) { + filteredPackages = append(filteredPackages, p.packageName) + continue + } + } + + output := []string{ + strconv.Itoa(p.cveCount), + p.severity, + p.packageName, + p.currentVersion, + p.fixVersion, + p.packageStatus} + if p.hostCount > 0 { + output = append(output, strconv.Itoa(p.hostCount)) + } + out = append(out, output) + } + + filteredOutput := fmt.Sprintf("%d of %d package(s) showing\n", len(out), len(uniquePackageNames)) + return out, filteredOutput +} + +func hostVulnCVEsTable(hostSummary map[string]VulnCveSummary) [][]string { + var out [][]string + for _, sum := range hostSummary { + host := sum.Host + out = append(out, []string{ + host.VulnID, + host.Severity, + host.CvssV2(), + host.CvssV3(), + host.FeatureKey.Name, + host.FeatureKey.VersionInstalled, + host.FixInfo.FixedVersion, + host.FeatureKey.Namespace, + strconv.Itoa(sum.Count), + host.PackageActive(), + host.Status, + }) + } + + // order by the total number of host + sort.Slice(out, func(i, j int) bool { + return stringToInt(out[i][8]) > stringToInt(out[j][8]) + }) + + return out +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_hosts.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_hosts.go new file mode 100644 index 000000000..d61440bc8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_hosts.go @@ -0,0 +1,237 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" + + "github.com/lacework/go-sdk/lwtime" + + "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + // vulHostListHostsCmd represents the 'lacework vuln host list-hosts ' command + vulHostListHostsCmd = &cobra.Command{ + Use: "list-hosts ", + Args: cobra.ExactArgs(1), + PreRunE: func(_ *cobra.Command, _ []string) error { + if vulCmdState.Csv { + cli.EnableCSVOutput() + } + return nil + }, + Short: "List the hosts that contain a specified CVE ID in your environment", + Long: `List the hosts that contain a specified CVE ID in your environment. + +To list the CVEs found in the hosts of your environment run: + + lacework vulnerability host list-cves`, + RunE: func(_ *cobra.Command, args []string) error { + var ( + filter api.SearchFilter + start time.Time + end time.Time + err error + ) + + if vulCmdState.Range != "" { + cli.Log.Debugw("retrieving natural time range", "range", vulCmdState.Range) + start, end, err = lwtime.ParseNatural(vulCmdState.Range) + if err != nil { + return errors.Wrap(err, "unable to parse natural time range") + } + + } else { + cli.Log.Debugw("parsing start time", "start", vulCmdState.Start) + start, err = parseQueryTime(vulCmdState.Start) + if err != nil { + return errors.Wrap(err, "unable to parse start time") + } + + cli.Log.Debugw("parsing end time", "end", vulCmdState.End) + end, err = parseQueryTime(vulCmdState.End) + if err != nil { + return errors.Wrap(err, "unable to parse end time") + } + } + + filter.TimeFilter = &api.TimeFilter{ + StartTime: &start, + EndTime: &end, + } + + filter.Filters = []api.Filter{ + {Expression: "eq", + Field: "vulnId", + Value: args[0]}, + {Expression: "ne", + Field: "status", + Value: "Fixed"}, + } + + cli.StartProgress("Fetching Hosts...") + response, err := cli.LwApi.V2.Vulnerabilities.Hosts.SearchAllPages(filter) + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to get hosts with CVE "+args[0]) + } + + if cli.JSONOutput() { + return cli.OutputJSON(response.Data) + } + + if len(response.Data) == 0 { + cli.OutputHuman("There are no hosts in your environment with the CVE id '%s'\n", args[0]) + return nil + } + + rows := hostVulnHostsTable(response.Data) + if cli.CSVOutput() { + return cli.OutputCSV( + []string{"Machine ID", "Hostname", "External IP", "Internal IP", + "Os/Arch", "Provider", "Instance ID", "Vulnerabilities", "Status"}, + rows, + ) + } + + cli.OutputHuman( + renderSimpleTable( + []string{"Machine ID", "Hostname", "External IP", "Internal IP", + "Os/Arch", "Provider", "Instance ID", "Vulnerabilities", "Status"}, + rows, + ), + ) + return nil + }, + } +) + +func hostVulnHostsTable(hosts []api.VulnerabilityHost) [][]string { + var out [][]string + hostSummary := hostsSummary(hosts) + for _, sum := range hostSummary { + host := sum.host + summary := severitySummary(sum.severity, sum.fixable) + machineTags, err := host.GetMachineTags() + if err != nil { + cli.Log.Debug("failed to parse machine tags") + } + out = append(out, []string{ + strconv.Itoa(host.Mid), + host.EvalCtx.Hostname, + machineTags.ExternalIP, + machineTags.InternalIP, + fmt.Sprintf("%s/%s", machineTags.Os, machineTags.Arch), + machineTags.VMProvider, + machineTags.InstanceID, + summary, + host.Status, + }) + } + + return out +} + +func severitySummary(severities []string, fixable int) string { + summary := &strings.Builder{} + sevSummaries := make(map[string]int) + for _, s := range severities { + switch s { + case "Critical": + if v, ok := sevSummaries["Critical"]; ok { + sevSummaries["Critical"] = v + 1 + } + sevSummaries["Critical"] = 1 + case "High": + if v, ok := sevSummaries["High"]; ok { + sevSummaries["High"] = v + 1 + } + sevSummaries["High"] = 1 + case "Medium": + if v, ok := sevSummaries["Medium"]; ok { + sevSummaries["Medium"] = v + 1 + } + sevSummaries["Medium"] = 1 + case "Low": + if v, ok := sevSummaries["Low"]; ok { + sevSummaries["Low"] = v + 1 + } + sevSummaries["Low"] = 1 + case "Info": + if v, ok := sevSummaries["Info"]; ok { + sevSummaries["Info"] = v + 1 + } + sevSummaries["Info"] = 1 + } + } + + var keys []string + for k := range sevSummaries { + keys = append(keys, k) + } + + sort.Slice(keys, func(i, j int) bool { + return api.SeverityOrder(keys[i]) < api.SeverityOrder(keys[j]) + }) + + for _, k := range keys { + v := sevSummaries[k] + summary.WriteString(fmt.Sprintf(" %d %s", v, k)) + } + + if fixable != 0 { + summary.WriteString(fmt.Sprintf(" %d Fixable", fixable)) + } + return summary.String() +} + +func hostsSummary(hosts []api.VulnerabilityHost) map[int]vulnSummary { + uniqueHosts := make(map[int]vulnSummary) + for _, host := range hosts { + if v, ok := uniqueHosts[host.Mid]; ok { + v.severity = append(v.severity, host.Severity) + if host.FixInfo.FixAvailable != "" && host.FixInfo.FixAvailable != "0" { + v.fixable++ + } + uniqueHosts[host.Mid] = v + continue + } + + sum := vulnSummary{host: host} + sum.severity = append(sum.severity, host.Severity) + if host.FixInfo.FixAvailable != "" && host.FixInfo.FixAvailable != "0" { + sum.fixable++ + } + uniqueHosts[host.Mid] = sum + } + return uniqueHosts +} + +type vulnSummary struct { + host api.VulnerabilityHost + severity []string + fixable int +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_scan_package_manifest.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_scan_package_manifest.go new file mode 100644 index 000000000..7e3ab057b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_scan_package_manifest.go @@ -0,0 +1,329 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "encoding/json" + "fmt" + "os" + "sort" + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/lacework/go-sdk/api" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +var ( + // vulHostScanPkgManifestCmd represents the 'lacework vuln host scan-pkg-manifest' command + vulHostScanPkgManifestCmd = &cobra.Command{ + Use: "scan-pkg-manifest ", + Args: cobra.MaximumNArgs(1), + Short: "Request an on-demand host vulnerability assessment from a package-manifest", + Long: `Request an on-demand host vulnerability assessment of your software packages to +determine if the packages contain any common vulnerabilities and exposures. + +Simple usage: + + lacework vulnerability host scan-pkg-manifest '{ + "osPkgInfoList": [ + { + "os":"Ubuntu", + "osVer":"18.04", + "pkg": "openssl", + "pkgVer": "1.1.1-1ubuntu2.1~18.04.5" + } + ] + }' + +To generate a package-manifest from the local host and scan it automatically: + + lacework vulnerability host scan-pkg-manifest --local + +**NOTE:** + - Only packages managed by a package manager for supported OS's are reported. + - Calls to this operation are rate limited to 10 calls per hour, per access key. + - This operation is limited to 10k packages per command execution.`, + RunE: func(c *cobra.Command, args []string) error { + if err := validateSeverityFlags(); err != nil { + return err + } + + var ( + pkgManifest = new(api.VulnerabilitiesPackageManifest) + pkgManifestBytes []byte + err error + ) + + if len(args) != 0 && args[0] != "" { + pkgManifestBytes = []byte(args[0]) + cli.Log.Debugw("package manifest loaded from arguments", "raw", args[0]) + } else if pkgManifestFile != "" { + pkgManifestBytes, err = os.ReadFile(pkgManifestFile) + if err != nil { + return errors.Wrap(err, "unable to read file") + } + cli.Log.Debugw("package manifest loaded from file", "raw", string(pkgManifestBytes)) + } else if pkgManifestLocal { + pkgManifest, err = cli.GeneratePackageManifest() + if err != nil { + return errors.Wrap(err, "unable to generate package manifest") + } + cli.Log.Debugw("package manifest generated from localhost", "raw", pkgManifest) + } else { + // avoid asking for a confirmation before launching the editor + var content string + prompt := &survey.Editor{ + Message: "Provide a package manifest to scan", + FileName: "package-manifest*.json", + } + err = survey.AskOne(prompt, &content) + if err != nil { + return errors.Wrap(err, "unable to load package manifest from editor") + } + pkgManifestBytes = []byte(content) + cli.Log.Debugw("package manifest loaded via editor", "raw", content) + } + + if len(pkgManifestBytes) != 0 { + err = json.Unmarshal(pkgManifestBytes, pkgManifest) + if err != nil { + return errors.Wrap(err, "invalid package manifest json file") + } + } + + totalPkgs := len(pkgManifest.OsPkgInfoList) + cli.StartProgress(" Scanning packages...") + cli.Log.Infow("manifest", "total_packages", totalPkgs) + var response api.VulnerabilitySoftwarePackagesResponse + // check if the package manifest has more than the maximum + // number of packages, if so, make multiple API requests + if totalPkgs >= manifestPkgsCap { + cli.Log.Infow("manifest over the limit, splitting up") + cli.Event.Feature = featSplitPkgManifest + cli.Event.AddFeatureField("total_packages", totalPkgs) + response, err = fanOutHostScans( + splitPackageManifest(pkgManifest, manifestPkgsCap)..., + ) + } else { + response, err = cli.LwApi.V2.Vulnerabilities.SoftwarePackages.Scan(*pkgManifest) + } + cli.StopProgress() + if err != nil { + return errors.Wrap(err, "unable to request an on-demand host vulnerability scan") + } + + if err := buildVulnHostScanPkgManifestReports(&response); err != nil { + return err + } + + if vulFailureFlagsEnabled() { + cli.Log.Infow("failure flags enabled", + "fail_on_severity", vulCmdState.FailOnSeverity, + "fail_on_fixable", vulCmdState.FailOnFixable, + ) + assessmentCounts := response.VulnerabilityCounts() + vulnPolicy := NewVulnerabilityPolicyError( + &assessmentCounts, + vulCmdState.FailOnSeverity, + vulCmdState.FailOnFixable, + ) + if vulnPolicy.NonCompliant() { + c.SilenceUsage = true + return vulnPolicy + } + } + return nil + }, + } +) + +// Build the cli output for vuln host scan-package-manifest +func buildVulnHostScanPkgManifestReports(response *api.VulnerabilitySoftwarePackagesResponse) error { + response.Data = filterHostScanPackagesVulnDetails(response.Data) + + if cli.JSONOutput() { + return cli.OutputJSON(response) + } + + if len(response.Data) == 0 { + cli.OutputHuman(fmt.Sprintf("There are no vulnerabilities found! Time for %s\n", randomEmoji())) + } else { + cli.OutputHuman(hostScanPackagesVulnToTable(response)) + } + + return nil +} + +func hostScanPackagesVulnToTable(scan *api.VulnerabilitySoftwarePackagesResponse) string { + var ( + mainBldr = &strings.Builder{} + rows [][]string + headers []string + ) + + if vulCmdState.Packages { + rows = hostScanPackagesVulnPackagesTable(filterHostScanPackagesVulnPackages(scan.Data)) + headers = []string{ + "CVE Count", + "Severity", + "Package", + "Version", + "Fixes Available", + } + } else { + rows = hostScanPackagesVulnDetailsTable(scan.Data) + headers = []string{ + "CVE ID", + "Severity", + "Score", + "Package", + "Version", + "Fix Version", + } + } + + if len(rows) == 0 { + if vulCmdState.Fixable { + return "There are no fixable vulnerabilities.\n" + } + scannedVia := "package manifest" + if pkgManifestLocal { + scannedVia = "localhost" + } + return fmt.Sprintf( + "Great news! The %s has no vulnerabilities... (time for %s)\n", + scannedVia, randomEmoji(), + ) + } + + mainBldr.WriteString( + renderOneLineCustomTable("Vulnerabilities", + renderCustomTable( + []string{"Severity", "Count", "Fixable"}, + hostVulnAssessmentToCountsTable(scan.VulnerabilityCounts()), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + ), + ) + + mainBldr.WriteString(renderSimpleTable(headers, rows)) + + return mainBldr.String() +} + +func filterHostScanPackagesVulnDetails(vulns []api.VulnerabilitySoftwarePackage) []api.VulnerabilitySoftwarePackage { + out := make([]api.VulnerabilitySoftwarePackage, 0) + + for _, vuln := range vulns { + if !vuln.IsVulnerable() { + continue + } + + if vulCmdState.Fixable && !vuln.HasFix() { + continue + } + + out = append(out, vuln) + } + + return out +} + +func hostScanPackagesVulnDetailsTable(vulns []api.VulnerabilitySoftwarePackage) [][]string { + var out [][]string + for _, vuln := range vulns { + out = append(out, []string{ + vuln.VulnID, + vuln.Severity, + vuln.ScoreString(), + vuln.OsPkgInfo.Pkg, + vuln.OsPkgInfo.PkgVer, + vuln.FixInfo.FixedVersion, + }) + } + + // order by severity + sort.Slice(out, func(i, j int) bool { + return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) + }) + + return out +} + +func filterHostScanPackagesVulnPackages(vulns []api.VulnerabilitySoftwarePackage) filteredPackageTable { + var ( + filteredPackages []packageTable + aggregatedPackages []packageTable + ) + + for _, vuln := range vulns { + pack := packageTable{ + cveCount: 1, + severity: cases.Title(language.English).String(vuln.Severity), + packageName: vuln.OsPkgInfo.Pkg, + currentVersion: vuln.OsPkgInfo.PkgVer, + } + + if vulCmdState.Fixable && !vuln.HasFix() { + filteredPackages = aggregatePackagesWithHosts(aggregatedPackages, pack, false, false) + continue + } + + aggregatedPackages = aggregatePackagesWithHosts(aggregatedPackages, pack, false, vuln.HasFix()) + } + + return filteredPackageTable{ + packages: aggregatedPackages, + totalPackages: len(aggregatedPackages), + totalUnfiltered: len(filteredPackages) + len(aggregatedPackages), + } +} + +func hostScanPackagesVulnPackagesTable(pkgs filteredPackageTable) [][]string { + var out [][]string + for _, pkg := range pkgs.packages { + out = append(out, []string{ + "1", + pkg.severity, + pkg.packageName, + pkg.currentVersion, + strconv.Itoa(pkg.fixes), + }) + } + + // order by severity + sort.Slice(out, func(i, j int) bool { + return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) + }) + + return out +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_show_assessment.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_show_assessment.go new file mode 100644 index 000000000..2c08221cc --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_show_assessment.go @@ -0,0 +1,625 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" + + "github.com/lacework/go-sdk/api" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +var ( + // vulHostListCvesCmd represents the 'lacework vuln host list-cves' command + vulHostShowAssessmentCmd = &cobra.Command{ + Use: "show-assessment ", + Aliases: []string{"show"}, + Args: cobra.ExactArgs(1), + Short: "Show results of a host vulnerability assessment", + Long: `Show results of a host vulnerability assessment. + +To find the machine id from hosts in your environment, use the command: + + lacework vulnerability host list-cves + +Grab a CVE id and feed it to the command: + + lacework vulnerability host list-hosts my_cve_id`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + if vulCmdState.Csv { + cli.EnableCSVOutput() + + // when rendering csv output, default to details since there is no output with less verbosity + if !vulCmdState.Details && !vulCmdState.Packages { + vulCmdState.Details = true + } + } + + // validate collector_type flag + switch vulCmdState.CollectorType { + case vulnHostCollectorTypeAgentless, vulnHostCollectorTypeAgent: + default: + return errors.Errorf( + "collector_type must be either %s or %s", + vulnHostCollectorTypeAgent, vulnHostCollectorTypeAgentless, + ) + } + + if vulCmdState.CollectorType == vulnHostCollectorTypeAgentless { + // check for agentless cloud integrations + if !checkAgentlessCloudAccount() { + // if the user has set the flag '--collector_type Agentless' + if cmd.Flags().Changed("collector_type") { + return errors.New("No agentless integrations configured.\n" + + "See https://docs.lacework.net/onboarding/category/aws-agentless-workload-scanning-integrations") + } + // if the flag was not set by the user and no agentless integrations exist + vulCmdState.CollectorType = vulnHostCollectorTypeAgent + } + } + + return nil + }, + RunE: func(c *cobra.Command, args []string) error { + if err := validateSeverityFlags(); err != nil { + return err + } + + var ( + assessment api.VulnerabilitiesHostResponse + cacheKey = fmt.Sprintf("host/assessment/v2/%s", args[0]) + ) + + expired := cli.ReadCachedAsset(cacheKey, &assessment) + if expired { + // check machine exists + var machinesResponse api.MachinesEntityResponse + filter := api.SearchFilter{Filters: []api.Filter{{ + Expression: "eq", + Field: "mid", + Value: args[0], + }}} + + cli.StartProgress(fmt.Sprintf("Searching for machine with id '%s'...", args[0])) + err := cli.LwApi.V2.Entities.Search(&machinesResponse, filter) + cli.StopProgress() + + if err != nil { + return errors.Wrapf(err, "unable to get machine details id %s", args[0]) + } + + if len(machinesResponse.Data) == 0 { + return errors.Errorf("no hosts found with id %s\n", args[0]) + } + + machineDetails := machinesResponse.Data[0] + + cli.StartProgress( + fmt.Sprintf("Searching for latest host evaluation for machine %s (%d)...", + machineDetails.Hostname, machineDetails.Mid, + )) + evalGUID, err := searchLatestHostEvaluationGuid(args[0]) + cli.StopProgress() + if err != nil { + return errors.Wrapf(err, "unable to find information of host '%s'", args[0]) + } + + cli.Log.Infow("latest assessment found", "eval_guid", evalGUID, "collector_type", vulCmdState.CollectorType) + + var ( + now = time.Now().UTC() + before = now.AddDate(0, 0, -7) // 7 days from ago + ) + + filter.TimeFilter = &api.TimeFilter{ + StartTime: &before, + EndTime: &now, + } + filter.Filters = append(filter.Filters, api.Filter{ + Expression: "eq", + Field: "evalGuid", + Value: evalGUID, + }) + + // filter by collector_type + filter.Filters = append(filter.Filters, api.Filter{ + Expression: "eq", + Field: "evalCtx.collector_type", + Value: vulCmdState.CollectorType, + }) + + cli.StartProgress( + fmt.Sprintf("Fetching vulnerabilities from host evaluation '%s' (collector_type: %s) ...", + evalGUID, vulCmdState.CollectorType), + ) + assessment, err = cli.LwApi.V2.Vulnerabilities.Hosts.SearchAllPages(filter) + if err != nil { + return errors.Wrapf(err, "unable to get host assessment with id %s", args[0]) + } + cli.StopProgress() + + cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Hour*1), assessment) + } + + if err := buildVulnHostReports(assessment); err != nil { + return err + } + + if vulFailureFlagsEnabled() { + cli.Log.Infow("failure flags enabled", + "fail_on_severity", vulCmdState.FailOnSeverity, + "fail_on_fixable", vulCmdState.FailOnFixable, + ) + assessmentCounts := assessment.VulnerabilityCounts() + vulnPolicy := NewVulnerabilityPolicyError( + &assessmentCounts, + vulCmdState.FailOnSeverity, + vulCmdState.FailOnFixable, + ) + if vulnPolicy.NonCompliant() { + c.SilenceUsage = true + return vulnPolicy + } + } + return nil + }, + } +) + +// Build the cli output for vuln host show-assessment +func buildVulnHostReports(response api.VulnerabilitiesHostResponse) error { + // @afiune the UI today doesn't display any vulnerability that has been fixed + // but the APIs return them, this is causing confusion, to fix this issue we + // are filtering all of those "Fixed" vulnerabilities here + response.Data = removeFixedVulnerabilitiesFromAssessment(response.Data) + + hostVulnCounts := response.VulnerabilityCounts() + if hostVulnCounts.Total == 0 { + if cli.JSONOutput() { + return cli.OutputJSON(response.Data) + } + cli.OutputHuman("Great news! This host has no vulnerabilities... (time for %s)\n", randomEmoji()) + return nil + } + + var ( + mainReport = hostVulnHostDetailsMainReportTable(response) + filteredCves, filtered = filterHostCVEsTable(cvesSummary(response.Data)) + detailsReport = buildVulnHostsDetailsTable(filteredCves) + csvHeader, csvDetailsReport = buildVulnHostsDetailsTableCSV(filteredCves) + ) + + switch { + case cli.JSONOutput(): + return cli.OutputJSON(summaryToHostList(filteredCves)) + case cli.CSVOutput(): + return cli.OutputCSV(csvHeader, csvDetailsReport) + default: + cli.OutputHuman(mainReport) + cli.OutputHuman(detailsReport) + if filtered != "" { + cli.OutputHuman(filtered) + } + return nil + } +} + +func searchLatestHostEvaluationGuid(mid string) (string, error) { + var ( + now = time.Now().UTC() + before = now.AddDate(0, 0, -7) // 7 days from ago + filter = api.SearchFilter{ + TimeFilter: &api.TimeFilter{ + StartTime: &before, + EndTime: &now, + }, + Filters: []api.Filter{{ + Expression: "eq", + Field: "mid", + Value: mid, + }, + { + Expression: "eq", + Field: "evalCtx.collector_type", + Value: vulCmdState.CollectorType, + }, + }, + Returns: []string{"evalGuid", "startTime"}, + } + ) + + cli.Log.Infow("retrieve host evaluation information", "mid", mid) + response, err := cli.LwApi.V2.Vulnerabilities.Hosts.SearchAllPages(filter) + if err != nil { + return "", err + } + + if len(response.Data) == 0 { + cli.Log.Infow("no data found", "collector_type", vulCmdState.CollectorType) + if vulCmdState.CollectorType == vulnHostCollectorTypeAgentless { + vulCmdState.CollectorType = vulnHostCollectorTypeAgent + return searchLatestHostEvaluationGuid(mid) + } + + return "", errors.Errorf("no data found with %s collector\n", vulCmdState.CollectorType) + } + + return getUniqueHostEvalGUID(response), nil +} + +func getUniqueHostEvalGUID(host api.VulnerabilitiesHostResponse) string { + var ( + guid string + startTime time.Time + ) + for _, ctr := range host.Data { + if ctr.EvalGUID != guid { + if ctr.StartTime.After(startTime) { + startTime = ctr.StartTime + guid = ctr.EvalGUID + } + } + } + return guid +} + +func buildVulnHostsDetailsTableCSV(filteredCves map[string]VulnCveSummary) ([]string, [][]string) { + if !showPackages() { + return nil, nil + } + + if vulCmdState.Packages { + packages, _ := hostVulnPackagesTable(filteredCves, false) + return []string{"CVE Count", "Severity", "Package", "Current Version", "Fix Version", "Pkg Status"}, packages + } + + rows := hostVulnCVEsTableForHostViewCSV(filteredCves) + return []string{"CVE ID", "Severity", "Score", "Package", "Package Namespace", "Current Version", + "Fix Version", "Pkg Status", "First Seen", "Last Status Update", "Vuln Status"}, rows +} + +func buildVulnHostsDetailsTable(filteredCves map[string]VulnCveSummary) string { + mainBldr := &strings.Builder{} + + if showPackages() { + if vulCmdState.Packages { + packages, filtered := hostVulnPackagesTable(filteredCves, false) + // if the user wants to show only vulnerabilities of active packages + // and we don't have any, show a friendly message + if len(packages) == 0 { + mainBldr.WriteString(buildHostVulnCVEsToTableError()) + } else { + mainBldr.WriteString( + renderSimpleTable( + []string{"CVE Count", "Severity", "Package", "Current Version", "Fix Version", "Pkg Status"}, + packages, + ), + ) + if filtered != "" { + mainBldr.WriteString(filtered) + } + } + } else { + rows := hostVulnCVEsTableForHostView(filteredCves) + // if the user wants to show only vulnerabilities of active packages + // and we don't have any, show a friendly message + if len(rows) == 0 { + mainBldr.WriteString(buildHostVulnCVEsToTableError()) + } else { + mainBldr.WriteString(renderSimpleTable([]string{ + "CVE ID", "Severity", "CvssV2", "CvssV3", "Package", "Current Version", + "Fix Version", "Pkg Status", "Vuln Status"}, + rows, + )) + } + } + } + + if !vulCmdState.Details && !vulCmdState.Active && !vulCmdState.Fixable && !vulCmdState.Packages && + vulCmdState.Severity == "" && + vulCmdState.Cve == "" { + mainBldr.WriteString( + "\nTry adding '--details' to increase details shown about the vulnerability assessment.\n", + ) + } else if !vulCmdState.Active { + mainBldr.WriteString( + "\nTry adding '--active' to only show vulnerabilities of packages actively running.\n", + ) + } else if !vulCmdState.Fixable { + mainBldr.WriteString( + "\nTry adding '--fixable' to only show fixable vulnerabilities.\n", + ) + } + + return mainBldr.String() +} + +func hostVulnHostDetailsMainReportTable(assessment api.VulnerabilitiesHostResponse) string { + host := assessment.Data[0] + machineTags, err := host.GetMachineTags() + if err != nil { + cli.Log.Debug("failed to parse machine tags") + } + mainBldr := &strings.Builder{} + mainBldr.WriteString( + renderCustomTable([]string{"Host Details", "Vulnerabilities"}, + [][]string{{ + renderCustomTable([]string{}, + [][]string{ + {"Machine ID", strconv.Itoa(host.Mid)}, + {"Hostname", host.EvalCtx.Hostname}, + {"External IP", machineTags.ExternalIP}, + {"Internal IP", machineTags.InternalIP}, + {"Os", machineTags.Os}, + {"Arch", machineTags.Arch}, + {"Namespace", host.FeatureKey.Namespace}, + {"Provider", machineTags.VMProvider}, + {"Instance ID", machineTags.InstanceID}, + {"AMI", machineTags.AmiID}, + {"Collector Type", host.EvalCtx.CollectorType}, + }, + tableFunc(func(t *tablewriter.Table) { + t.SetColumnSeparator("") + t.SetBorder(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + + }), + ), + renderCustomTable( + []string{"Severity", "Count", "Fixable"}, + hostVulnAssessmentToCountsTable(assessment.VulnerabilityCounts()), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + }), + ), + }}, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + t.SetColumnSeparator(" ") + }), + ), + ) + + return mainBldr.String() +} + +func showPackages() bool { + return vulCmdState.Details || + vulCmdState.Fixable || + vulCmdState.Packages || + vulCmdState.Active || + vulCmdState.Severity != "" +} + +func hostVulnCVEsTableForHostViewCSV(cves map[string]VulnCveSummary) [][]string { + var ( + out [][]string + cveSlice []api.VulnerabilityHost + ) + + for _, cve := range cves { + cveSlice = append(cveSlice, cve.Host) + } + sortVulnHosts(cveSlice) + + for _, host := range cveSlice { + var ( + firstSeen string + lastUpdated string + ) + + if host.Props.FirstTimeSeen != nil { + firstSeen = host.Props.FirstTimeSeen.UTC().String() + } + + if host.Props.FirstTimeSeen != nil { + lastUpdated = host.Props.LastUpdatedTime.UTC().String() + } + + out = append(out, []string{ + host.VulnID, + host.Severity, + host.CvssV2(), + host.CvssV3(), + host.FeatureKey.Name, + host.FeatureKey.Namespace, + host.FeatureKey.VersionInstalled, + host.FixInfo.FixedVersion, + host.PackageActive(), + firstSeen, + lastUpdated, + host.PackageActive(), + }) + } + + // order by severity and package name + sort.Slice(out, func(i, j int) bool { + if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { + return out[i][4] < out[j][4] + } + return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) + }) + + return out +} + +func hostVulnCVEsTableForHostView(summary map[string]VulnCveSummary) [][]string { + var ( + out [][]string + cveSlice []api.VulnerabilityHost + ) + + for _, cve := range summary { + cveSlice = append(cveSlice, cve.Host) + } + sortVulnHosts(cveSlice) + + for _, host := range cveSlice { + out = append(out, []string{ + host.VulnID, + host.Severity, + host.CvssV2(), + host.CvssV3(), + host.FeatureKey.Name, + host.FeatureKey.VersionInstalled, + host.FixInfo.FixedVersion, + host.PackageActive(), + host.Status, + }) + } + + // order by severity and by package name + sort.Slice(out, func(i, j int) bool { + if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { + return api.SeverityOrder(out[i][4]) < api.SeverityOrder(out[j][4]) + } + return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) + }) + + return out +} + +func removeFixedVulnerabilitiesFromAssessment(assessment []api.VulnerabilityHost) []api.VulnerabilityHost { + var filteredCves []api.VulnerabilityHost + for _, cve := range assessment { + if cve.Status != "Fixed" { + filteredCves = append(filteredCves, cve) + } + } + return filteredCves +} + +func hostVulnPackagesTable(cves map[string]VulnCveSummary, withHosts bool) ([][]string, string) { + var ( + out [][]string + filteredPackages []packageTable + aggregatedPackages []packageTable + cveSlice []api.VulnerabilityHost + ) + + for _, cve := range cves { + cveSlice = append(cveSlice, cve.Host) + } + sortVulnHosts(cveSlice) + + for _, host := range cveSlice { + pack := packageTable{ + cveCount: 1, + severity: cases.Title(language.English).String(host.Severity), + packageName: host.FeatureKey.Name, + currentVersion: host.FeatureKey.VersionInstalled, + fixVersion: host.FixInfo.FixedVersion, + packageStatus: host.PackageActive(), + } + if withHosts { + pack.hostCount = 1 + } + + if vulCmdState.Active && host.PackageActive() == "" { + filteredPackages = aggregatePackagesWithHosts(filteredPackages, pack, withHosts, false) + continue + } + + if vulCmdState.Fixable && host.FixInfo.FixedVersion == "" { + filteredPackages = aggregatePackagesWithHosts(filteredPackages, pack, withHosts, false) + continue + } + + if vulCmdState.Severity != "" { + if filterSeverity(host.Severity, vulCmdState.Severity) { + filteredPackages = aggregatePackagesWithHosts(filteredPackages, pack, withHosts, false) + continue + } + } + // add all packages that have not been filtered + aggregatedPackages = aggregatePackagesWithHosts(aggregatedPackages, pack, withHosts, false) + } + + for _, p := range aggregatedPackages { + output := []string{ + strconv.Itoa(p.cveCount), + p.severity, + p.packageName, + p.currentVersion, + p.fixVersion, + p.packageStatus} + if p.hostCount > 0 { + output = append(output, strconv.Itoa(p.hostCount)) + } + out = append(out, output) + } + + // order by severity and by package name + sort.Slice(out, func(i, j int) bool { + if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { + return out[i][2] < out[j][2] + } + return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) + }) + + if len(filteredPackages) > 0 { + filteredOutput := fmt.Sprintf( + "%d of %d package(s) showing\n", + len(out), len(aggregatedPackages)+len(filteredPackages), + ) + return out, filteredOutput + } + + return out, "" +} + +func sortVulnHosts(slice []api.VulnerabilityHost) { + for range slice { + sort.Slice(slice[:], func(i, j int) bool { + switch strings.Compare(slice[i].VulnID, slice[j].VulnID) { + case -1: + return true + case 1: + return false + default: + return false + } + }) + } +} + +func checkAgentlessCloudAccount() bool { + resp, err := cli.LwApi.V2.CloudAccounts.List() + if err != nil { + return false + } + for _, ca := range resp.Data { + switch ca.Type { + case api.AwsSidekickCloudAccount.String(), api.AwsSidekickOrgCloudAccount.String(), + api.GcpSidekickCloudAccount.String(): + return true + } + } + return false +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_html.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_html.go new file mode 100644 index 000000000..d3a4c96d8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_html.go @@ -0,0 +1,370 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "bytes" + "fmt" + "html/template" + "os" + "regexp" + "sort" + "strings" + "time" + + "github.com/pkg/errors" + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/databox" +) + +const ( + magicBarChartAdjustment float32 = 5 + magicTableInitialHeight int32 = 130 // width 1524px can we make it magic? + magicTableHeightMultiplier int32 = 40 +) + +// arrays can't be constants +var magicLayerInstructions = []string{"ADD"} + +// struct used to render the HTML assessment +type vulnImageAssessmentHtml struct { + Account string + ID string + Digest string + Repository string + CreatedTime string + Size string + Tags []string + + TotalVulnerabilities int32 + CriticalVulnerabilities int32 + HighVulnerabilities int32 + MediumVulnerabilities int32 + LowVulnerabilities int32 + InfoVulnerabilities int32 + FixableVulnerabilities int32 + + TableHeight int32 + Vulnerabilities []htmlVuln +} + +type htmlVuln struct { + RowHeight int32 + CVE string + Severity string + SeverityHTMLClass string + Layer string + PkgName string + PkgVersion string + PkgFixed string + V3Score float64 + UseV3Score bool + V2Score float64 + UseV2Score bool + UseNoScore bool +} + +func (a *vulnImageAssessmentHtml) CriticalVulnPercent() float32 { + if a.CriticalVulnerabilities == 0 { + return magicBarChartAdjustment + } + percent := (float32(a.CriticalVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 + if a.SeverityToBeAdjusted() == "critical" { + return percent - a.Adjustment() + } + return percent +} +func (a *vulnImageAssessmentHtml) HighVulnPercent() float32 { + if a.HighVulnerabilities == 0 { + return magicBarChartAdjustment + } + percent := (float32(a.HighVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 + if a.SeverityToBeAdjusted() == "high" { + return percent - a.Adjustment() + } + return percent +} +func (a *vulnImageAssessmentHtml) MediumVulnPercent() float32 { + if a.MediumVulnerabilities == 0 { + return magicBarChartAdjustment + } + percent := (float32(a.MediumVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 + if a.SeverityToBeAdjusted() == "medium" { + return percent - a.Adjustment() + } + return percent +} +func (a *vulnImageAssessmentHtml) LowVulnPercent() float32 { + if a.LowVulnerabilities == 0 { + return magicBarChartAdjustment + } + percent := (float32(a.LowVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 + if a.SeverityToBeAdjusted() == "low" { + return percent - a.Adjustment() + } + return percent +} +func (a *vulnImageAssessmentHtml) InfoVulnPercent() float32 { + if a.InfoVulnerabilities == 0 { + return magicBarChartAdjustment + } + percent := (float32(a.InfoVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 + if a.SeverityToBeAdjusted() == "info" { + return percent - a.Adjustment() + } + return percent +} +func (a *vulnImageAssessmentHtml) Adjustment() float32 { + var x float32 + if a.CriticalVulnerabilities == 0 { + x += magicBarChartAdjustment + } + if a.HighVulnerabilities == 0 { + x += magicBarChartAdjustment + } + if a.MediumVulnerabilities == 0 { + x += magicBarChartAdjustment + } + if a.LowVulnerabilities == 0 { + x += magicBarChartAdjustment + } + if a.InfoVulnerabilities == 0 { + x += magicBarChartAdjustment + } + return x +} +func (a *vulnImageAssessmentHtml) SeverityToBeAdjusted() string { + severity := "critical" + highest := a.CriticalVulnerabilities + + if highest < a.HighVulnerabilities { + severity = "high" + highest = a.HighVulnerabilities + } + if highest < a.MediumVulnerabilities { + severity = "medium" + highest = a.MediumVulnerabilities + } + if highest < a.LowVulnerabilities { + severity = "low" + highest = a.LowVulnerabilities + } + if highest < a.InfoVulnerabilities { + severity = "info" + } + + return severity +} + +func calcHtmlBarChartWidth(severity string, htmlData vulnImageAssessmentHtml) string { + switch severity { + case "critical": + return fmt.Sprintf("%f%%", htmlData.CriticalVulnPercent()) + case "high": + return fmt.Sprintf("%f%%", htmlData.HighVulnPercent()) + case "medium": + return fmt.Sprintf("%f%%", htmlData.MediumVulnPercent()) + case "low": + return fmt.Sprintf("%f%%", htmlData.LowVulnPercent()) + case "info": + return fmt.Sprintf("%f%%", htmlData.InfoVulnPercent()) + default: + return "0" + } +} + +func calcHtmlBarChartX(severity string, htmlData vulnImageAssessmentHtml) string { + var x float32 + switch severity { + case "critical": + x = 0 + case "high": + x = htmlData.CriticalVulnPercent() + case "medium": + x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + case "low": + x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + + htmlData.MediumVulnPercent() + case "info": + x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + + htmlData.MediumVulnPercent() + htmlData.LowVulnPercent() + } + return fmt.Sprintf("%f%%", x) +} + +func isLayerInstruction(inst string) bool { + for _, mInst := range magicLayerInstructions { + if mInst == inst { + return true + } + } + return false +} +func htmlLayerInstruction(layer string) string { + if len(layer) == 0 { + return "" + } + + words := strings.Split(layer, " ") + if isLayerInstruction(words[0]) { + return words[0] + } + + return "RUN" +} + +func htmlLayerPrint(layer string) string { + if len(layer) == 0 { + return "" + } + + words := strings.Split(layer, " ") + if isLayerInstruction(words[0]) { + return strings.Join(words[1:], " ") + } + + return layer +} + +func calcHtmlBarChartTextX(severity string, htmlData vulnImageAssessmentHtml) string { + var x float32 + switch severity { + case "critical": + x = htmlData.CriticalVulnPercent() / 2 + case "high": + x = htmlData.CriticalVulnPercent() + (htmlData.HighVulnPercent() / 2) + case "medium": + x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + + (htmlData.MediumVulnPercent() / 2) + case "low": + x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + + htmlData.MediumVulnPercent() + (htmlData.LowVulnPercent() / 2) + case "info": + x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + + htmlData.MediumVulnPercent() + htmlData.LowVulnPercent() + (htmlData.InfoVulnPercent() / 2) + } + return fmt.Sprintf("%f%%", x) +} + +func generateVulnAssessmentHTML(response api.VulnerabilitiesContainersResponse) error { + assessment := response.Data + htmlTemplate, ok := databox.Get("vuln_assessment.html") + if !ok { + return errors.New( + "html template not found, this is most likely a mistake on us, please report it to support.lacework.com.", + ) + } + + headerInfo := assessment[0] + + var ( + buff = &bytes.Buffer{} + funcMap = template.FuncMap{ + "calcBarChartWidth": calcHtmlBarChartWidth, + "calcBarChartX": calcHtmlBarChartX, + "calcBarChartTextX": calcHtmlBarChartTextX, + "layerInstruction": htmlLayerInstruction, + "layerPrint": htmlLayerPrint, + } + tmpl = template.Must(template.New("vuln_assessment").Funcs(funcMap).Parse(string(htmlTemplate))) + htmlData = vulnImageAssessmentHtml{ + Account: cli.Account, + Repository: headerInfo.EvalCtx.ImageInfo.Repo, + ID: headerInfo.EvalCtx.ImageInfo.ID, + Digest: headerInfo.EvalCtx.ImageInfo.Digest, + CreatedTime: time.UnixMilli(headerInfo.EvalCtx.ImageInfo.CreatedTime).Format(time.RFC3339), + Size: byteCountBinary(headerInfo.EvalCtx.ImageInfo.Size), + Tags: headerInfo.EvalCtx.ImageInfo.Tags, + + TotalVulnerabilities: int32(response.TotalVulnerabilities()), + CriticalVulnerabilities: response.CriticalVulnerabilities(), + HighVulnerabilities: response.HighVulnerabilities(), + MediumVulnerabilities: response.MediumVulnerabilities(), + LowVulnerabilities: response.LowVulnerabilities(), + InfoVulnerabilities: response.InfoVulnerabilities(), + FixableVulnerabilities: response.TotalFixableVulnerabilities(), + + TableHeight: magicTableInitialHeight + (int32(response.TotalVulnerabilities()) * magicTableHeightMultiplier), + Vulnerabilities: vulContainerImageLayersToHTML(assessment), + } + outputHTML = fmt.Sprintf("%s-%s.html", + strings.ReplaceAll(htmlData.Repository, "/", "-"), + htmlData.Digest) + ) + + if err := tmpl.Execute(buff, htmlData); err != nil { + return errors.Wrap(err, "unable to execute template") + } + + if err := os.WriteFile(outputHTML, buff.Bytes(), os.ModePerm); err != nil { + return errors.Wrap(err, "unable to write html file") + } + + cli.OutputHuman("The container vulnerability assessment was stored at '%s'\n", outputHTML) + return nil +} + +func vulContainerImageLayersToHTML(image []api.VulnerabilityContainer) []htmlVuln { + var vulns = []htmlVuln{} + for _, i := range image { + space := regexp.MustCompile(`\s+`) + layerCreatedBy := space.ReplaceAllString(i.FeatureProps.IntroducedIn, " ") + + newHtmlVuln := htmlVuln{ + CVE: i.VulnID, + Severity: cases.Title(language.English).String(i.Severity), + SeverityHTMLClass: strings.ToLower(i.Severity), + PkgName: i.FeatureKey.Name, + PkgVersion: i.FeatureKey.Version, + PkgFixed: i.FixInfo.FixedVersion, + Layer: layerCreatedBy, + } + + // Todo(v2): CVSSv3Score does not exist in v2 container response + // if score := vul.CVSSv3Score(); score != 0 { + // // CVSSv3 + // newHtmlVuln.V3Score = score + // newHtmlVuln.UseV3Score = true + // } else if score = vul.CVSSv2Score(); score != 0 { + // // CVSSv2 + // newHtmlVuln.V2Score = score + // newHtmlVuln.UseV2Score = true + // } else { + // // N/A + newHtmlVuln.UseNoScore = true + // } + + vulns = append(vulns, newHtmlVuln) + } + + // order by severity + sort.Slice(vulns, func(i, j int) bool { + return api.SeverityOrder(vulns[i].Severity) < api.SeverityOrder(vulns[j].Severity) + }) + + // add the row height after ordering + for row := range vulns { + vulns[row].RowHeight = (int32(row) * magicTableHeightMultiplier) + magicTableHeightMultiplier + } + + return vulns +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability.go new file mode 100644 index 000000000..fb3117196 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability.go @@ -0,0 +1,362 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "strconv" + "strings" + + "github.com/lacework/go-sdk/lwseverity" + "github.com/pkg/errors" + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" +) + +var ( + vulCmdState = struct { + // enable poll mechanism for scans status + Poll bool + + // store the vulnerability assessment in HTML format on disk + Html bool + + // output vulnerability assessment in CSV format + Csv bool + + // DEPRECATED + // when enabled we tread the provided sha256 hash as image id + ImageID bool + + // display extended details about a vulnerability assessment + Details bool + + // Filter the vulnerability assessment table by severity + Severity string + + // Exit the CLI application with an error given a specified severity is met + FailOnSeverity string + + // Exit the CLI application with an error given fixable vulnerabilities + FailOnFixable bool + + // display only fixable vulnerabilities + Fixable bool + + // show a list of packages by number of CVEs + Packages bool + + // start time for listing assessments + Start string + + // end time for listing assessments + End string + + // natural time range for listing assessments + Range string + + // active flag to filter container vulnerability assessments and + // show only assessments of containers actively running + Active bool + + // show only hosts that are online + Online bool + + // show only hosts that are offline + Offline bool + + // filter assessments for specific repositories + Repositories []string + + // filter assessments for specific registries + Registries []string + + // filter assessments by a single CVE + Cve string + + // filter assessments by collector type(Agent/Agentless) + CollectorType string + }{ + // Default collector_type is agentless + CollectorType: vulnHostCollectorTypeAgentless} + + // vulnerability represents the vulnerability command that holds both, the host + // and container sub-commands + vulnerabilityCmd = &cobra.Command{ + Use: "vulnerability", + Aliases: []string{"vuln", "vul"}, + Short: "Container and host vulnerability assessments", + Long: "Container and host vulnerability assessments.", + } + + // vulContainerCmd represents the vulnerability container command + vulContainerCmd = &cobra.Command{ + Use: "container", + Aliases: []string{"ctr"}, + Short: "Vulnerability assessment for containers", + Long: `Request on-demand container vulnerability scans and show previous assessments +from published images. + +**PREREQUISITE:** Your Lacework account should already be configured +with a Container Registry Integration of the container images you are +trying to scan or show. + +To create a new integration use the following command: + + lacework container-registry create + +If you prefer to configure the integration via the WebUI, log in to your account at: + + https://.lacework.net + +Then navigate to Settings > Integrations > Container Registry.`, + } + + // vulHostCmd represents the vulnerability host command + vulHostCmd = &cobra.Command{ + Use: "host", + Short: "Vulnerability assessment for hosts", + Long: `Request on-demand host vulnerability scans and show previous assessments +from hosts with the Lacework datacollector agent installed. +`, + } +) + +const ( + vulnHostCollectorTypeAgent string = "Agent" + vulnHostCollectorTypeAgentless string = "Agentless" +) + +func init() { + // add the vulnerability command + rootCmd.AddCommand(vulnerabilityCmd) + + // add sub-commands to the vulnerability command + vulnerabilityCmd.AddCommand(vulContainerCmd) + vulnerabilityCmd.AddCommand(vulHostCmd) +} + +func setPackagesFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.BoolVar(&vulCmdState.Packages, "packages", false, + "show a list of packages with CVE count", + ) + } + } +} + +func setFixableFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.BoolVar(&vulCmdState.Fixable, "fixable", false, + "only show fixable vulnerabilities", + ) + } + } +} + +func setHtmlFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.BoolVar(&vulCmdState.Html, "html", false, + "generate a vulnerability assessment in HTML format", + ) + } + } +} + +func setCsvFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.BoolVar(&vulCmdState.Csv, "csv", false, + "output vulnerability assessment in CSV format", + ) + } + } +} + +func setDetailsFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.BoolVar(&vulCmdState.Details, "details", false, + "increase details of a vulnerability assessment", + ) + } + } +} + +func setFailOnSeverityFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.StringVar(&vulCmdState.FailOnSeverity, "fail_on_severity", "", + fmt.Sprintf("specify a severity threshold to fail if vulnerabilities are found (%s)", + lwseverity.ValidSeverities.String()), + ) + } + } +} + +func setFailOnFixableFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.BoolVar(&vulCmdState.FailOnFixable, "fail_on_fixable", false, + "fail if the assessed container has fixable vulnerabilities", + ) + } + } +} + +func setSeverityFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.StringVar(&vulCmdState.Severity, "severity", "", + fmt.Sprintf("filter vulnerability assessment by severity threshold (%s)", + lwseverity.ValidSeverities.String()), + ) + } + } +} + +func setActiveFlag(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + cmd.BoolVar(&vulCmdState.Active, "active", false, + "only show vulnerabilities of packages actively running in your environment", + ) + } + } +} + +func setTimeRangeFlags(cmds ...*flag.FlagSet) { + for _, cmd := range cmds { + if cmd != nil { + + cmd.StringVar(&vulCmdState.Start, + "start", "-24h", "start of the time range", + ) + cmd.StringVar(&vulCmdState.End, + "end", "now", "end of the time range", + ) + cmd.StringVar(&vulCmdState.Range, + "range", "", "natural time range for query", + ) + } + } +} + +func buildVulnContainerAssessmentReportTable(summary string, details string) string { + report := &strings.Builder{} + + report.WriteString(summary) + if vulCmdState.Details || vulCmdState.Packages || vulFiltersEnabled() { + report.WriteString(details) + } else { + if !vulCmdState.Html { + report.WriteString( + "Try adding '--details' to increase details shown about the vulnerability assessment.\n", + ) + } + } + + return report.String() +} + +func byteCountBinary(b int) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp]) +} + +func stringToInt(s string) int { + i, err := strconv.Atoi(s) + if err != nil { + cli.Log.Debugw("unable to convert string to int", + "string", s, "error", err.Error(), "func", "stringToInt", + ) + return 0 + } + return i +} + +func validateSeverityFlags() error { + if vulCmdState.Severity != "" { + if !lwseverity.IsValid(vulCmdState.Severity) { + return errors.Errorf("the severity %s is not valid, use one of %s", + vulCmdState.Severity, lwseverity.ValidSeverities.String(), + ) + } + } + + if vulCmdState.FailOnSeverity != "" { + if !lwseverity.IsValid(vulCmdState.FailOnSeverity) { + return errors.Errorf("the severity %s is not valid, use one of %s", + vulCmdState.FailOnSeverity, lwseverity.ValidSeverities.String(), + ) + } + } + + return nil +} + +func vulFailureFlagsEnabled() bool { + return vulCmdState.FailOnSeverity != "" || vulCmdState.FailOnFixable +} + +func vulFiltersEnabled() bool { + return vulCmdState.Severity != "" || vulCmdState.Fixable +} + +func filterSeverity(severity string, threshold string) bool { + thresholdValue, _ := lwseverity.Normalize(threshold) + severityValue, _ := lwseverity.Normalize(severity) + return severityValue > thresholdValue +} + +// Used to store data for --package output vuln ctr/host +type packageTable struct { + cveCount int + severity string + packageName string + packageNamespace string + currentVersion string + fixVersion string + packageStatus string + hostCount int + fixes int +} + +type filteredPackageTable struct { + packages []packageTable + totalPackages int + totalUnfiltered int +} + +func (pt *packageTable) equals(p packageTable) bool { + return pt.packageName == p.packageName && + pt.packageNamespace == p.packageNamespace && + pt.currentVersion == p.currentVersion +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_container.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_container.go new file mode 100644 index 000000000..46e692137 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_container.go @@ -0,0 +1,188 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createContainerVulnerabilityException() (string, error) { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "description", + Prompt: &survey.Input{Message: "Description: "}, + Validate: survey.Required, + }, + { + Name: "reason", + Prompt: &survey.Select{ + Message: "Exception Reason: ", + Options: []string{"Accepted Risk", "False Positive", "Compensating Controls", "Fix Pending", "Other"}, + }, + Validate: survey.Required, + }, + { + Name: "includeCriteria", + Prompt: &survey.MultiSelect{ + Message: "Select Vulnerability Criteria to set: ", + Options: []string{"CVEs", "Packages", "Severities", "Fixable"}}, + Validate: survey.MinItems(1), + }, + } + + answers := struct { + Name string + Description string `survey:"description"` + Reason string `survey:"reason"` + Severities []string `survey:"severities"` + Cves string `survey:"cves"` + Packages string `survey:"packages"` + Fixable bool `survey:"fixable"` + ImageID string `survey:"imageId"` + ImageTag string `survey:"imageTag"` + Registry string `survey:"registry"` + Repository string `survey:"repository"` + Namespace string `survey:"namespace"` + IncludeCriteria []string `survey:"includeCriteria"` + }{} + + err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)) + if err != nil { + return "", err + } + + err = askVulnerabilityExceptionCriteria(&answers, answers.IncludeCriteria) + if err != nil { + return "", err + } + + fixable := answers.Fixable + vulnExCfg := api.VulnerabilityExceptionConfig{ + Description: answers.Description, + Type: api.VulnerabilityExceptionTypeContainer, + ExceptionReason: api.NewVulnerabilityExceptionReason(answers.Reason), + Severities: api.NewVulnerabilityExceptionSeverities(answers.Severities), + Cve: strings.Split(answers.Cves, "\n"), + Package: transformVulnerabilityExceptionPackages(answers.Packages), + Fixable: &fixable, + } + + // ask the user if they would like to configure a Resource Scope, or many + scope := false + err = survey.AskOne(&survey.Confirm{ + Message: "Configure one or more Resource Scope(s)?", + }, &scope) + if err != nil { + return "", err + } + + if scope { + err = askVulnerabilityExceptionContainerResourceScope(&answers) + if err != nil { + return "", err + } + vulnExCfg.ResourceScope = api.VulnerabilityExceptionContainerResourceScope{ + ImageID: strings.Split(answers.ImageID, "\n"), + ImageTag: strings.Split(answers.ImageTag, "\n"), + Registry: strings.Split(answers.Registry, "\n"), + Repository: strings.Split(answers.Repository, "\n"), + Namespace: strings.Split(answers.Namespace, "\n"), + } + } + + vuln := api.NewVulnerabilityException(answers.Name, vulnExCfg) + + cli.StartProgress("Creating container vulnerability exception...") + vulnResp, err := cli.LwApi.V2.VulnerabilityExceptions.CreateVulnerabilityExceptionsHost(vuln) + cli.StopProgress() + return vulnResp.Data.Guid, err +} + +func askVulnerabilityExceptionContainerResourceScope(answers interface{}) error { + criteria := struct { + Scope []string `survey:"scope"` + }{} + err := survey.Ask([]*survey.Question{ + { + Name: "scope", + Prompt: &survey.MultiSelect{ + Message: "Select Resource Scope criteria to set: ", + Options: []string{"Namespaces", "Image IDs", "Image Tags", "Registries", "Repositories"}}, + Validate: survey.MinItems(1), + }, + }, &criteria, survey.WithIcons(promptIconsFunc)) + if err != nil { + return err + } + + var questions []*survey.Question + for _, c := range criteria.Scope { + if c == "Image IDs" { + questions = append(questions, + &survey.Question{Name: "imageId", + Prompt: &survey.Multiline{Message: "List of Image IDs:"}, + }) + continue + } + + if c == "Image Tags" { + questions = append(questions, + &survey.Question{Name: "imageTag", + Prompt: &survey.Multiline{Message: "List of Image Tags:"}, + }) + continue + } + + if c == "Registries" { + questions = append(questions, + &survey.Question{Name: "registry", + Prompt: &survey.Multiline{Message: "List of Registries:"}}) + continue + } + + if c == "Repositories" { + questions = append(questions, + &survey.Question{ + Name: "repository", + Prompt: &survey.Multiline{Message: "List of Repositories:"}, + }) + continue + } + + if c == "Namespaces" { + questions = append(questions, + &survey.Question{ + Name: "namespace", + Prompt: &survey.Multiline{Message: "List of Namespaces:"}, + }) + continue + } + } + + return survey.Ask(questions, answers, survey.WithIcons(promptIconsFunc)) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_host.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_host.go new file mode 100644 index 000000000..4011fbc5c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_host.go @@ -0,0 +1,177 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "strings" + + "github.com/AlecAivazis/survey/v2" + + "github.com/lacework/go-sdk/api" +) + +func createHostVulnerabilityException() (string, error) { + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name: "}, + Validate: survey.Required, + }, + { + Name: "description", + Prompt: &survey.Input{Message: "Description: "}, + Validate: survey.Required, + }, + { + Name: "reason", + Prompt: &survey.Select{ + Message: "Exception Reason: ", + Options: []string{"Accepted Risk", "False Positive", "Compensating Controls", "Fix Pending", "Other"}, + }, + Validate: survey.Required, + }, + { + Name: "includeCriteria", + Prompt: &survey.MultiSelect{ + Message: "Select Vulnerability Criteria to set: ", + Options: []string{"CVEs", "Packages", "Severities", "Fixable"}}, + Validate: survey.MinItems(1), + }, + } + + answers := struct { + Name string + Description string `survey:"description"` + Reason string `survey:"reason"` + Severities []string `survey:"severities"` + Cves string `survey:"cves"` + Packages string `survey:"packages"` + Fixable bool `survey:"fixable"` + Hostname string `survey:"hostname"` + ExternalIP string `survey:"externalIp"` + ClusterName string `survey:"clusterName"` + Namespace string `survey:"namespace"` + IncludeCriteria []string `survey:"includeCriteria"` + }{} + + err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)) + if err != nil { + return "", err + } + + err = askVulnerabilityExceptionCriteria(&answers, answers.IncludeCriteria) + if err != nil { + return "", err + } + + fixable := answers.Fixable + vulnExCfg := api.VulnerabilityExceptionConfig{ + Description: answers.Description, + Type: api.VulnerabilityExceptionTypeHost, + ExceptionReason: api.NewVulnerabilityExceptionReason(answers.Reason), + Severities: api.NewVulnerabilityExceptionSeverities(answers.Severities), + Cve: strings.Split(answers.Cves, "\n"), + Package: transformVulnerabilityExceptionPackages(answers.Packages), + Fixable: &fixable, + } + + // ask the user if they would like to configure a Resource Scope, or many + scope := false + err = survey.AskOne(&survey.Confirm{ + Message: "Configure one or more Resource Scope(s)?", + }, &scope) + if err != nil { + return "", err + } + + if scope { + err = askVulnerabilityExceptionHostResourceScope(&answers) + if err != nil { + return "", err + } + vulnExCfg.ResourceScope = api.VulnerabilityExceptionHostResourceScope{ + Hostname: strings.Split(answers.Hostname, "\n"), + ExternalIP: strings.Split(answers.ExternalIP, "\n"), + ClusterName: strings.Split(answers.ClusterName, "\n"), + Namespace: strings.Split(answers.Namespace, "\n"), + } + } + + vuln := api.NewVulnerabilityException(answers.Name, vulnExCfg) + + cli.StartProgress("Creating host vulnerability exception...") + vulnResp, err := cli.LwApi.V2.VulnerabilityExceptions.CreateVulnerabilityExceptionsHost(vuln) + cli.StopProgress() + return vulnResp.Data.Guid, err +} + +func askVulnerabilityExceptionHostResourceScope(answers interface{}) error { + criteria := struct { + Scope []string `survey:"scope"` + }{} + err := survey.Ask([]*survey.Question{ + { + Name: "scope", + Prompt: &survey.MultiSelect{ + Message: "Select Resource Scope criteria to set: ", + Options: []string{"Namespaces", "Hostnames", "External IPs", "Cluster Names"}}, + Validate: survey.MinItems(1), + }, + }, &criteria, survey.WithIcons(promptIconsFunc)) + if err != nil { + return err + } + + var questions []*survey.Question + for _, c := range criteria.Scope { + if c == "Namespaces" { + questions = append(questions, + &survey.Question{Name: "namespace", + Prompt: &survey.Multiline{Message: "List of Namespaces:"}, + }) + continue + } + + if c == "Hostnames" { + questions = append(questions, + &survey.Question{Name: "hostname", + Prompt: &survey.Multiline{Message: "List of Hostnames:"}, + }) + continue + } + + if c == "External IPs" { + questions = append(questions, + &survey.Question{Name: "externalIp", + Prompt: &survey.Multiline{Message: "List of External IPs:"}}) + continue + } + + if c == "Cluster Names" { + questions = append(questions, + &survey.Question{ + Name: "clusterName", + Prompt: &survey.Multiline{Message: "List of ClusterNames:"}, + }) + continue + } + } + + return survey.Ask(questions, answers, survey.WithIcons(promptIconsFunc)) +} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerabilty_exceptions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerabilty_exceptions.go new file mode 100644 index 000000000..5ac4a8b1f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerabilty_exceptions.go @@ -0,0 +1,391 @@ +// +// Author:: Darren Murray() +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cmd + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/AlecAivazis/survey/v2/core" + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/lwseverity" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +const CveRegex = `(?i)CVE-\d{4}-\d{4,7}` +const AlasRegex = `(?i)ALAS(2?)-\d{4}-\d{3,7}` + +var ( + // vulnerability-exceptions command is used to manage lacework vulnerability exceptions + vulnerabilityExceptionCommand = &cobra.Command{ + Use: "vulnerability-exception", + Aliases: []string{"vulnerability-exceptions", "ve", "vuln-exception", "vuln-exceptions"}, + Short: "Manage vulnerability exceptions", + Long: "Manage vulnerability exceptions to control and customize your alert profile for hosts and containers.", + } + + // list command is used to list all lacework vulnerability exceptions + vulnerabilityExceptionListCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all vulnerability exceptions", + Long: "List all vulnerability exceptions configured in your Lacework account.", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + vulnerabilityExceptions, err := cli.LwApi.V2.VulnerabilityExceptions.List() + if err != nil { + return errors.Wrap(err, "unable to get vulnerability exceptions") + } + if len(vulnerabilityExceptions.Data) == 0 { + msg := `There are no vulnerability exceptions configured in your account. + +Get started by integrating your vulnerability exceptions to manage alerting using the command: + + lacework vulnerability-exception create + +If you prefer to configure vulnerability exceptions via the WebUI, log in to your account at: + + https://%s.lacework.net + +Then navigate to Vulnerabilities > Exceptions. +` + cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) + return nil + } + + if cli.JSONOutput() { + return cli.OutputJSON(vulnerabilityExceptions) + } + + var rows [][]string + for _, vuln := range vulnerabilityExceptions.Data { + rows = append(rows, []string{vuln.Guid, vuln.ExceptionName, vuln.ExceptionType, vuln.Status()}) + } + + cli.OutputHuman(renderCustomTable([]string{"GUID", "NAME", "TYPE", "STATE"}, rows, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }))) + return nil + }, + } + // show command is used to retrieve a lacework vulnerability exception by id + vulnerabilityExceptionShowCommand = &cobra.Command{ + Use: "show ", + Short: "Get vulnerability exception by ID", + Long: "Get a single vulnerability exception by it's vulnerability exception ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var response api.VulnerabilityExceptionResponse + err := cli.LwApi.V2.VulnerabilityExceptions.Get(args[0], &response) + if err != nil { + return errors.Wrap(err, "unable to get vulnerability exception") + } + vuln := response.Data + + if cli.JSONOutput() { + return cli.OutputJSON(vuln) + } + + var groupCommon [][]string + groupCommon = append(groupCommon, []string{vuln.Guid, vuln.ExceptionName, vuln.ExceptionType, vuln.Status()}) + + cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "TYPE", "STATUS"}, groupCommon)) + cli.OutputHuman("\n") + cli.OutputHuman(buildVulnerabilityExceptionsPropsTable(vuln)) + return nil + }, + } + + // delete command is used to remove a lacework vulnerability exception by id + vulnerabilityExceptionDeleteCommand = &cobra.Command{ + Use: "delete ", + Short: "Delete a vulnerability exception", + Long: "Delete a single vulnerability exception by it's vulnerability exception ID.", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + err := cli.LwApi.V2.VulnerabilityExceptions.Delete(args[0]) + if err != nil { + return errors.Wrap(err, "unable to delete vulnerability exception") + } + return nil + }, + } + + // create command is used to create a new lacework vulnerability exception + vulnerabilityExceptionCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new vulnerability exception", + Long: "Creates a new single vulnerability exception.", + RunE: func(_ *cobra.Command, args []string) error { + if !cli.InteractiveMode() { + return errors.New("interactive mode is disabled") + } + + vulnID, err := promptCreateVulnerabilityException() + if err != nil { + return errors.Wrap(err, "unable to create vulnerability exception") + } + + cli.OutputHuman("The vulnerability exception created with GUID %s.\n", vulnID) + return nil + }, + } +) + +func init() { + // add the vulnerability-exception command + rootCmd.AddCommand(vulnerabilityExceptionCommand) + + // add sub-commands to the vulnerability-exception command + vulnerabilityExceptionCommand.AddCommand(vulnerabilityExceptionListCommand) + vulnerabilityExceptionCommand.AddCommand(vulnerabilityExceptionShowCommand) + vulnerabilityExceptionCommand.AddCommand(vulnerabilityExceptionCreateCommand) + vulnerabilityExceptionCommand.AddCommand(vulnerabilityExceptionDeleteCommand) +} + +func buildVulnerabilityExceptionsPropsTable(vuln api.VulnerabilityException) string { + var sb strings.Builder + props := setProps(vuln) + + sb.WriteString(renderOneLineCustomTable("VULNERABILITY EXCEPTION PROPS", + renderCustomTable([]string{}, props, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + t.SetAlignment(tablewriter.ALIGN_LEFT) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetAutoWrapText(false) + }), + )) + if vuln.VulnerabilityCriteria.Package != nil { + sb.WriteString(buildPackagesTable(vuln.VulnerabilityCriteria.Package)) + } + return sb.String() +} + +func buildPackagesTable(packages []map[string][]string) string { + var ( + details [][]string + ) + for _, p := range packages { + for k, v := range p { + details = append(details, []string{k, strings.Join(v, ", ")}) + } + } + + return renderOneLineCustomTable("PACKAGES", + renderCustomTable( + []string{"NAME", "VERSIONS"}, + details, + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + }), + ), + tableFunc(func(t *tablewriter.Table) { + t.SetBorder(false) + t.SetColumnSeparator(" ") + t.SetAutoWrapText(false) + }), + ) +} + +func setProps(vuln api.VulnerabilityException) [][]string { + var details [][]string + details = append(details, []string{"DESCRIPTION", vuln.Props.Description}) + details = append(details, []string{"UPDATED BY", vuln.Props.UpdatedBy}) + details = append(details, []string{"LAST UPDATED", vuln.UpdatedTime}) + details = append(details, []string{"CREATED", vuln.CreatedTime}) + details = append(details, []string{"REASON", vuln.ExceptionReason}) + + if vuln.ResourceScope != nil { + if vuln.ExceptionType == api.VulnerabilityExceptionTypeHost.String() { + details = append(details, []string{"NAMESPACES", strings.Join(vuln.ResourceScope.Namespace, ", ")}) + details = append(details, []string{"HOSTNAMES", strings.Join(vuln.ResourceScope.Hostname, ", ")}) + details = append(details, []string{"EXTERNAL IPS", strings.Join(vuln.ResourceScope.ExternalIP, ",")}) + details = append(details, []string{"CLUSTER NAMES", strings.Join(vuln.ResourceScope.ClusterName, ", ")}) + } else if vuln.ExceptionType == api.VulnerabilityExceptionTypeContainer.String() { + details = append(details, []string{"NAMESPACES", strings.Join(vuln.ResourceScope.Namespace, ", ")}) + details = append(details, []string{"IMAGE IDS", strings.Join(vuln.ResourceScope.ImageID, ", ")}) + details = append(details, []string{"IMAGE TAGS", strings.Join(vuln.ResourceScope.ImageTag, ", ")}) + details = append(details, []string{"REGISTRIES", strings.Join(vuln.ResourceScope.Registry, ", ")}) + details = append(details, []string{"REPOSITORIES", strings.Join(vuln.ResourceScope.Repository, ", ")}) + } + } + + details = append(details, []string{"FIXABLE", + vulnerabilityExceptionFixableEnabled(vuln.VulnerabilityCriteria.Fixable)}) + details = append(details, []string{"CVES", strings.Join(vuln.VulnerabilityCriteria.Cve, ", ")}) + details = append(details, []string{"SEVERITIES", strings.Join(vuln.VulnerabilityCriteria.Severity, ", ")}) + + return details +} + +func promptCreateVulnerabilityException() (string, error) { + var ( + group = "" + prompt = &survey.Select{ + Message: "Choose a vulnerability exception type to create: ", + Options: []string{ + "Host", + "Container", + }, + } + err = survey.AskOne(prompt, &group) + ) + if err != nil { + return "", err + } + + switch group { + case "Host": + return createHostVulnerabilityException() + case "Container": + return createContainerVulnerabilityException() + default: + return "", errors.New("unknown vulnerability exception type") + } +} + +func vulnerabilityExceptionFixableEnabled(fixable []int) string { + if len(fixable) == 0 { + return "false" + } + return strconv.FormatBool(fixable[0] == 1) +} + +func askVulnerabilityExceptionCriteria(answers interface{}, criteria []string) error { + var questions []*survey.Question + for _, c := range criteria { + if c == "CVEs" { + questions = append(questions, + &survey.Question{ + Name: "cves", + Prompt: &survey.Multiline{Message: "List of CVE IDs:"}, + Validate: validateCveFormat(), + }) + continue + } + + if c == "Severities" { + questions = append(questions, + &survey.Question{ + Name: "severities", + Prompt: &survey.MultiSelect{ + Message: "Select severities:", + Options: []string{"Critical", "High", "Medium", "Low", "Info"}, + }, + Validate: validateSeverities(), + }) + continue + } + if c == "Packages" { + questions = append(questions, + &survey.Question{ + Name: "packages", + Prompt: &survey.Multiline{Message: "List of 'package:version' packages to include:"}, + }) + continue + } + + if c == "Packages" { + questions = append(questions, + &survey.Question{ + Name: "fixable", + Prompt: &survey.Confirm{Message: "Include Fixable:"}, + }) + continue + } + } + + err := survey.Ask(questions, answers, survey.WithIcons(promptIconsFunc)) + if err != nil { + return err + } + return nil +} + +func transformVulnerabilityExceptionPackages(packages string) []api.VulnerabilityExceptionPackage { + if packages == "" { + return []api.VulnerabilityExceptionPackage{} + } + var vulnPackages []api.VulnerabilityExceptionPackage + packageList := strings.Split(packages, "\n") + for _, pack := range packageList { + vulnPackage := strings.Split(pack, ":") + vulnPackages = append(vulnPackages, + api.VulnerabilityExceptionPackage{Name: vulnPackage[0], Version: vulnPackage[1]}, + ) + } + return vulnPackages +} + +func validateCveFormat() survey.Validator { + return func(val interface{}) error { + cveRegEx, _ := regexp.Compile(CveRegex) + alasRegEx, _ := regexp.Compile(AlasRegex) + if list, ok := val.([]core.OptionAnswer); ok { + for _, i := range list { + if !cveRegEx.MatchString(i.Value) && !alasRegEx.MatchString(i.Value) { + return fmt.Errorf("CVE format is invalid. Please format corretly eg: CVE-2014-0001, ALAS2-2022-1788") + } + } + } else { + value := val.(string) + if !cveRegEx.MatchString(value) && !alasRegEx.MatchString(value) { + return fmt.Errorf("CVE format is invalid. Please format corretly eg: CVE-2014-0001, ALAS2-2022-1788") + } + } + return nil + } +} + +func validateSeverities() survey.Validator { + return func(val interface{}) error { + if list, ok := val.([]core.OptionAnswer); ok { + for _, i := range list { + match := strings.Contains(lwseverity.ValidSeverities.String(), strings.ToLower(i.Value)) + if !match { + return fmt.Errorf( + "severity '%s' is invalid. Must be one of 'Critical', 'High', 'Medium', 'Low', 'Info'", i.Value, + ) + } + } + } else { + value := val.(core.OptionAnswer).Value + match := strings.Contains(lwseverity.ValidSeverities.String(), strings.ToLower(value)) + if !match { + return fmt.Errorf("severity '%s' is invalid. Must be one of 'Critical', 'High', 'Medium', 'Low', 'Info'", value) + } + } + return nil + } +} diff --git a/vendor/github.com/lacework/go-sdk/internal/archive/detect.go b/vendor/github.com/lacework/go-sdk/internal/archive/detect.go new file mode 100644 index 000000000..e60c3b56c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/archive/detect.go @@ -0,0 +1,89 @@ +package archive + +import ( + "os" + "path/filepath" + "strings" + + "github.com/gabriel-vasile/mimetype" +) + +func detectFileType(file string) (mimeType string, err error) { + + fDescriptor, err := os.Open(file) + if err != nil { + return + } + defer fDescriptor.Close() + // We only have to pass the file header = first 261 bytes + head := make([]byte, 261) + + _, err = fDescriptor.Read(head) + if err != nil { + return + } + + mtype := mimetype.Detect(head) + mimeType = mtype.String() + + return +} + +func FileIsGZ(file string) (isGZ bool, err error) { + mtype, err := detectFileType(file) + if err != nil { + return + } + isGZ = mtype == "application/gzip" + return +} + +func FileIsTar(file string) (isTar bool, err error) { + mtype, err := detectFileType(file) + if err != nil { + return + } + isTar = mtype == "application/x-tar" + return +} + +func DetectTGZAndUnpack(filePath string, targetDir string) (err error) { + // detect if file is a tar gz and extract to targetDir + fileName := filepath.Base(filePath) + baseFileName := strings.ReplaceAll(fileName, ".tar.gz", "") + baseFileName = strings.ReplaceAll(baseFileName, ".tgz", "") + unpackDir, err := os.MkdirTemp("", "temp-cdk-unpack-archive-") + if err != nil { + return + } + defer os.RemoveAll(unpackDir) + + tarFile := filepath.Join(unpackDir, baseFileName+".tar") + isGZ, err := FileIsGZ(filePath) + if err != nil { + return + } + + if isGZ { + if err := Gunzip(filePath, tarFile); err != nil { + return err + } + } else { + + return + } + + isTar, err := FileIsTar(tarFile) + if err != nil { + return + } + + if isTar { + if err = UnTar(tarFile, targetDir); err != nil { + return + } + } else { + return + } + return +} diff --git a/vendor/github.com/lacework/go-sdk/internal/archive/gz.go b/vendor/github.com/lacework/go-sdk/internal/archive/gz.go new file mode 100644 index 000000000..177a76844 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/archive/gz.go @@ -0,0 +1,34 @@ +package archive + +import ( + "compress/gzip" + "io" + "os" +) + +// Inflate GZip file. +// +// Writes decompressed data to target path. +func Gunzip(source string, target string) (err error) { + reader, err := os.Open(source) + if err != nil { + return + } + defer reader.Close() + + archive, err := gzip.NewReader(reader) + if err != nil { + return + } + defer archive.Close() + + writer, err := os.Create(target) + if err != nil { + return + } + defer writer.Close() + + _, err = io.Copy(writer, archive) + + return +} diff --git a/vendor/github.com/lacework/go-sdk/internal/archive/tar.go b/vendor/github.com/lacework/go-sdk/internal/archive/tar.go new file mode 100644 index 000000000..cf5e84793 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/archive/tar.go @@ -0,0 +1,64 @@ +package archive + +import ( + "archive/tar" + "io" + "os" + "path/filepath" +) + +// Extract tarball to dir +func UnTar(tarball string, dir string) (err error) { + reader, err := os.Open(tarball) + if err != nil { + return + } + defer reader.Close() + + tarReader := tar.NewReader(reader) + + for { + hdr, err := tarReader.Next() + if err == io.EOF { + break + } else if err != nil { + return err + } + path := filepath.Join(dir, hdr.Name) + mode := hdr.FileInfo().Mode() + switch hdr.Typeflag { + case tar.TypeReg: + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(file, tarReader) + if err != nil { + return err + } + case tar.TypeDir: + err = os.MkdirAll(path, mode) + if err != nil { + return err + } + case tar.TypeLink: + err = os.Link(filepath.Join(dir, filepath.Clean(hdr.Linkname)), path) + if err != nil { + return err + } + case tar.TypeSymlink: + err = os.Symlink(filepath.Clean(hdr.Linkname), path) + if err != nil { + return err + } + case tar.TypeXGlobalHeader, tar.TypeXHeader: + continue + + } + + } + + return +} diff --git a/vendor/github.com/lacework/go-sdk/internal/array/contains.go b/vendor/github.com/lacework/go-sdk/internal/array/contains.go new file mode 100644 index 000000000..1869c0b1b --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/array/contains.go @@ -0,0 +1,66 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package array + +import "strings" + +func ContainsStr(array []string, expected string) bool { + for _, value := range array { + if expected == value { + return true + } + } + return false +} + +func ContainsStrCaseInsensitive(array []string, expected string) bool { + for _, value := range array { + if strings.EqualFold(expected, value) { + return true + } + } + return false +} + +func ContainsPartialStr(array []string, expected string) bool { + for _, value := range array { + if strings.Contains(expected, value) { + return true + } + } + return false +} + +func ContainsInt(array []int, expected int) bool { + for _, value := range array { + if expected == value { + return true + } + } + return false +} + +func ContainsBool(array []bool, expected bool) bool { + for _, value := range array { + if expected == value { + return true + } + } + return false +} diff --git a/vendor/github.com/lacework/go-sdk/internal/array/join.go b/vendor/github.com/lacework/go-sdk/internal/array/join.go new file mode 100644 index 000000000..136f62d65 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/array/join.go @@ -0,0 +1,31 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package array + +import ( + "fmt" + "strings" +) + +func JoinInt32(array []int32, delim string) string { + return strings.Trim( + strings.Replace(fmt.Sprint(array), " ", delim, -1), + "[]", + ) +} diff --git a/vendor/github.com/lacework/go-sdk/internal/array/sort.go b/vendor/github.com/lacework/go-sdk/internal/array/sort.go new file mode 100644 index 000000000..670bc0232 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/array/sort.go @@ -0,0 +1,37 @@ +package array + +import ( + "sort" + "strings" +) + +// Sort2D can be used 2d Arrays used for Table Outputs to sort headers +func Sort2D(slice [][]string) { + for range slice { + sort.Slice(slice[:], func(i, j int) bool { + elem := slice[i][0] + next := slice[j][0] + switch strings.Compare(elem, next) { + case -1: + return true + case 1: + return false + default: + // When equal compare next element + for x := 1; x < len(slice[i]); x++ { + secondaryElem := slice[i][x] + nextSecondaryElem := slice[j][x] + switch strings.Compare(secondaryElem, nextSecondaryElem) { + case -1: + return true + case 1: + return false + default: + continue + } + } + return false + } + }) + } +} diff --git a/vendor/github.com/lacework/go-sdk/internal/array/unique.go b/vendor/github.com/lacework/go-sdk/internal/array/unique.go new file mode 100644 index 000000000..013c332e1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/array/unique.go @@ -0,0 +1,33 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package array + +// Unique removes duplicates from a slice and returns the list with only unique values +// accepts a slice of string or int +func Unique[T string | int](sliceList []T) []T { + var list []T + allKeys := make(map[T]bool) + for _, item := range sliceList { + if _, value := allKeys[item]; !value { + allKeys[item] = true + list = append(list, item) + } + } + return list +} diff --git a/vendor/github.com/lacework/go-sdk/internal/cache/cache.go b/vendor/github.com/lacework/go-sdk/internal/cache/cache.go new file mode 100644 index 000000000..9407f00b0 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/cache/cache.go @@ -0,0 +1,34 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package cache + +import ( + "path" + + homedir "github.com/mitchellh/go-homedir" +) + +func CacheDir() (string, error) { + home, err := homedir.Dir() + if err != nil { + return "", err + } + + return path.Join(home, ".config", "lacework"), nil +} diff --git a/vendor/github.com/lacework/go-sdk/internal/capturer/capture_output.go b/vendor/github.com/lacework/go-sdk/internal/capturer/capture_output.go new file mode 100644 index 000000000..5e4f48b7e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/capturer/capture_output.go @@ -0,0 +1,62 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package capturer + +import ( + "bytes" + "io" + "os" + + "github.com/fatih/color" +) + +// captureOutput executes a function and captures the STDOUT and STDERR, +// useful to test logging messages or human readable output +func CaptureOutput(f func()) string { + r, w, err := os.Pipe() + if err != nil { + panic(err) + } + + stdout := os.Stdout + os.Stdout = w + defer func() { + os.Stdout = stdout + }() + + colorOut := color.Output + color.Output = w + defer func() { + color.Output = colorOut + }() + + stderr := os.Stderr + os.Stderr = w + defer func() { + os.Stderr = stderr + }() + + f() + w.Close() + + var buf bytes.Buffer + io.Copy(&buf, r) //nolint + + return buf.String() +} diff --git a/vendor/github.com/lacework/go-sdk/internal/databox/blob.go b/vendor/github.com/lacework/go-sdk/internal/databox/blob.go new file mode 100644 index 000000000..c0c9e5677 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/databox/blob.go @@ -0,0 +1,34 @@ +// Code generated by: internal/databox/generator/main.go +// +// <<< DO NOT EDIT >>> +// + +package databox + +func init() { + box.Add("/reports/aws/cis.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 52, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 52, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 52, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 52, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 53, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 53, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 82, 68, 83, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 69, 76, 65, 83, 84, 73, 67, 83, 69, 65, 82, 67, 72, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 69, 76, 65, 83, 84, 73, 67, 83, 69, 65, 82, 67, 72, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 69, 76, 65, 83, 84, 73, 67, 83, 69, 65, 82, 67, 72, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 69, 76, 65, 83, 84, 73, 67, 83, 69, 65, 82, 67, 72, 95, 52, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) + box.Add("/reports/azure/cis.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 56, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 56, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 56, 95, 51, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) + box.Add("/reports/azure/cis_131.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 76, 87, 95, 73, 65, 77, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 76, 87, 95, 73, 65, 77, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 76, 87, 95, 73, 65, 77, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 49, 49, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) + box.Add("/reports/gcp/cis.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 53, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 53, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 53, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 54, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 54, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 54, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 54, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 56, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) + box.Add("/reports/gcp/cis_12.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 53, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 53, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 55, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 55, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 55, 95, 51, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) + box.Add("/scaffoldings/golang/.gitignore", []byte{35, 32, 77, 97, 99, 32, 117, 115, 101, 114, 115, 10, 46, 68, 83, 95, 83, 116, 111, 114, 101, 10, 10, 35, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 67, 68, 75, 32, 100, 101, 118, 45, 109, 111, 100, 101, 10, 46, 100, 101, 118, 10, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 10, 10, 35, 32, 118, 105, 109, 10, 42, 46, 115, 119, 112, 10, 10, 35, 32, 71, 111, 32, 116, 101, 115, 116, 32, 99, 111, 118, 101, 114, 97, 103, 101, 10, 99, 111, 118, 101, 114, 97, 103, 101, 46, 111, 117, 116, 10, 99, 111, 118, 101, 114, 97, 103, 101, 46, 104, 116, 109, 108, 10, 10, 35, 32, 98, 105, 110, 97, 114, 105, 101, 115, 32, 102, 111, 108, 100, 101, 114, 10, 47, 98, 105, 110, 10, 10, 35, 32, 118, 115, 99, 111, 100, 101, 32, 100, 101, 98, 117, 103, 46, 116, 101, 115, 116, 10, 42, 42, 47, 100, 101, 98, 117, 103, 46, 116, 101, 115, 116, 10, 46, 118, 115, 99, 111, 100, 101, 47, 42, 10}) + box.Add("/scaffoldings/golang/LICENSE", []byte{10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 44, 32, 74, 97, 110, 117, 97, 114, 121, 32, 50, 48, 48, 52, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 10, 10, 32, 32, 32, 84, 69, 82, 77, 83, 32, 65, 78, 68, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 70, 79, 82, 32, 85, 83, 69, 44, 32, 82, 69, 80, 82, 79, 68, 85, 67, 84, 73, 79, 78, 44, 32, 65, 78, 68, 32, 68, 73, 83, 84, 82, 73, 66, 85, 84, 73, 79, 78, 10, 10, 32, 32, 32, 49, 46, 32, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 115, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 102, 111, 114, 32, 117, 115, 101, 44, 32, 114, 101, 112, 114, 111, 100, 117, 99, 116, 105, 111, 110, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 100, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 97, 115, 32, 100, 101, 102, 105, 110, 101, 100, 32, 98, 121, 32, 83, 101, 99, 116, 105, 111, 110, 115, 32, 49, 32, 116, 104, 114, 111, 117, 103, 104, 32, 57, 32, 111, 102, 32, 116, 104, 105, 115, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 76, 105, 99, 101, 110, 115, 111, 114, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 32, 111, 114, 32, 101, 110, 116, 105, 116, 121, 32, 97, 117, 116, 104, 111, 114, 105, 122, 101, 100, 32, 98, 121, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 32, 116, 104, 97, 116, 32, 105, 115, 32, 103, 114, 97, 110, 116, 105, 110, 103, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 76, 101, 103, 97, 108, 32, 69, 110, 116, 105, 116, 121, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 117, 110, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 32, 97, 99, 116, 105, 110, 103, 32, 101, 110, 116, 105, 116, 121, 32, 97, 110, 100, 32, 97, 108, 108, 10, 32, 32, 32, 32, 32, 32, 111, 116, 104, 101, 114, 32, 101, 110, 116, 105, 116, 105, 101, 115, 32, 116, 104, 97, 116, 32, 99, 111, 110, 116, 114, 111, 108, 44, 32, 97, 114, 101, 32, 99, 111, 110, 116, 114, 111, 108, 108, 101, 100, 32, 98, 121, 44, 32, 111, 114, 32, 97, 114, 101, 32, 117, 110, 100, 101, 114, 32, 99, 111, 109, 109, 111, 110, 10, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 114, 111, 108, 32, 119, 105, 116, 104, 32, 116, 104, 97, 116, 32, 101, 110, 116, 105, 116, 121, 46, 32, 70, 111, 114, 32, 116, 104, 101, 32, 112, 117, 114, 112, 111, 115, 101, 115, 32, 111, 102, 32, 116, 104, 105, 115, 32, 100, 101, 102, 105, 110, 105, 116, 105, 111, 110, 44, 10, 32, 32, 32, 32, 32, 32, 34, 99, 111, 110, 116, 114, 111, 108, 34, 32, 109, 101, 97, 110, 115, 32, 40, 105, 41, 32, 116, 104, 101, 32, 112, 111, 119, 101, 114, 44, 32, 100, 105, 114, 101, 99, 116, 32, 111, 114, 32, 105, 110, 100, 105, 114, 101, 99, 116, 44, 32, 116, 111, 32, 99, 97, 117, 115, 101, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 100, 105, 114, 101, 99, 116, 105, 111, 110, 32, 111, 114, 32, 109, 97, 110, 97, 103, 101, 109, 101, 110, 116, 32, 111, 102, 32, 115, 117, 99, 104, 32, 101, 110, 116, 105, 116, 121, 44, 32, 119, 104, 101, 116, 104, 101, 114, 32, 98, 121, 32, 99, 111, 110, 116, 114, 97, 99, 116, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 44, 32, 111, 114, 32, 40, 105, 105, 41, 32, 111, 119, 110, 101, 114, 115, 104, 105, 112, 32, 111, 102, 32, 102, 105, 102, 116, 121, 32, 112, 101, 114, 99, 101, 110, 116, 32, 40, 53, 48, 37, 41, 32, 111, 114, 32, 109, 111, 114, 101, 32, 111, 102, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 111, 117, 116, 115, 116, 97, 110, 100, 105, 110, 103, 32, 115, 104, 97, 114, 101, 115, 44, 32, 111, 114, 32, 40, 105, 105, 105, 41, 32, 98, 101, 110, 101, 102, 105, 99, 105, 97, 108, 32, 111, 119, 110, 101, 114, 115, 104, 105, 112, 32, 111, 102, 32, 115, 117, 99, 104, 32, 101, 110, 116, 105, 116, 121, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 89, 111, 117, 34, 32, 40, 111, 114, 32, 34, 89, 111, 117, 114, 34, 41, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 97, 110, 32, 105, 110, 100, 105, 118, 105, 100, 117, 97, 108, 32, 111, 114, 32, 76, 101, 103, 97, 108, 32, 69, 110, 116, 105, 116, 121, 10, 32, 32, 32, 32, 32, 32, 101, 120, 101, 114, 99, 105, 115, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 103, 114, 97, 110, 116, 101, 100, 32, 98, 121, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 83, 111, 117, 114, 99, 101, 34, 32, 102, 111, 114, 109, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 112, 114, 101, 102, 101, 114, 114, 101, 100, 32, 102, 111, 114, 109, 32, 102, 111, 114, 32, 109, 97, 107, 105, 110, 103, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 44, 10, 32, 32, 32, 32, 32, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 98, 117, 116, 32, 110, 111, 116, 32, 108, 105, 109, 105, 116, 101, 100, 32, 116, 111, 32, 115, 111, 102, 116, 119, 97, 114, 101, 32, 115, 111, 117, 114, 99, 101, 32, 99, 111, 100, 101, 44, 32, 100, 111, 99, 117, 109, 101, 110, 116, 97, 116, 105, 111, 110, 10, 32, 32, 32, 32, 32, 32, 115, 111, 117, 114, 99, 101, 44, 32, 97, 110, 100, 32, 99, 111, 110, 102, 105, 103, 117, 114, 97, 116, 105, 111, 110, 32, 102, 105, 108, 101, 115, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 79, 98, 106, 101, 99, 116, 34, 32, 102, 111, 114, 109, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 97, 110, 121, 32, 102, 111, 114, 109, 32, 114, 101, 115, 117, 108, 116, 105, 110, 103, 32, 102, 114, 111, 109, 32, 109, 101, 99, 104, 97, 110, 105, 99, 97, 108, 10, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 111, 114, 32, 116, 114, 97, 110, 115, 108, 97, 116, 105, 111, 110, 32, 111, 102, 32, 97, 32, 83, 111, 117, 114, 99, 101, 32, 102, 111, 114, 109, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 98, 117, 116, 10, 32, 32, 32, 32, 32, 32, 110, 111, 116, 32, 108, 105, 109, 105, 116, 101, 100, 32, 116, 111, 32, 99, 111, 109, 112, 105, 108, 101, 100, 32, 111, 98, 106, 101, 99, 116, 32, 99, 111, 100, 101, 44, 32, 103, 101, 110, 101, 114, 97, 116, 101, 100, 32, 100, 111, 99, 117, 109, 101, 110, 116, 97, 116, 105, 111, 110, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 100, 32, 99, 111, 110, 118, 101, 114, 115, 105, 111, 110, 115, 32, 116, 111, 32, 111, 116, 104, 101, 114, 32, 109, 101, 100, 105, 97, 32, 116, 121, 112, 101, 115, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 87, 111, 114, 107, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 119, 111, 114, 107, 32, 111, 102, 32, 97, 117, 116, 104, 111, 114, 115, 104, 105, 112, 44, 32, 119, 104, 101, 116, 104, 101, 114, 32, 105, 110, 32, 83, 111, 117, 114, 99, 101, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 79, 98, 106, 101, 99, 116, 32, 102, 111, 114, 109, 44, 32, 109, 97, 100, 101, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 97, 115, 32, 105, 110, 100, 105, 99, 97, 116, 101, 100, 32, 98, 121, 32, 97, 10, 32, 32, 32, 32, 32, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 110, 111, 116, 105, 99, 101, 32, 116, 104, 97, 116, 32, 105, 115, 32, 105, 110, 99, 108, 117, 100, 101, 100, 32, 105, 110, 32, 111, 114, 32, 97, 116, 116, 97, 99, 104, 101, 100, 32, 116, 111, 32, 116, 104, 101, 32, 119, 111, 114, 107, 10, 32, 32, 32, 32, 32, 32, 40, 97, 110, 32, 101, 120, 97, 109, 112, 108, 101, 32, 105, 115, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 105, 110, 32, 116, 104, 101, 32, 65, 112, 112, 101, 110, 100, 105, 120, 32, 98, 101, 108, 111, 119, 41, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 97, 110, 121, 32, 119, 111, 114, 107, 44, 32, 119, 104, 101, 116, 104, 101, 114, 32, 105, 110, 32, 83, 111, 117, 114, 99, 101, 32, 111, 114, 32, 79, 98, 106, 101, 99, 116, 10, 32, 32, 32, 32, 32, 32, 102, 111, 114, 109, 44, 32, 116, 104, 97, 116, 32, 105, 115, 32, 98, 97, 115, 101, 100, 32, 111, 110, 32, 40, 111, 114, 32, 100, 101, 114, 105, 118, 101, 100, 32, 102, 114, 111, 109, 41, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 102, 111, 114, 32, 119, 104, 105, 99, 104, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 101, 100, 105, 116, 111, 114, 105, 97, 108, 32, 114, 101, 118, 105, 115, 105, 111, 110, 115, 44, 32, 97, 110, 110, 111, 116, 97, 116, 105, 111, 110, 115, 44, 32, 101, 108, 97, 98, 111, 114, 97, 116, 105, 111, 110, 115, 44, 32, 111, 114, 32, 111, 116, 104, 101, 114, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 10, 32, 32, 32, 32, 32, 32, 114, 101, 112, 114, 101, 115, 101, 110, 116, 44, 32, 97, 115, 32, 97, 32, 119, 104, 111, 108, 101, 44, 32, 97, 110, 32, 111, 114, 105, 103, 105, 110, 97, 108, 32, 119, 111, 114, 107, 32, 111, 102, 32, 97, 117, 116, 104, 111, 114, 115, 104, 105, 112, 46, 32, 70, 111, 114, 32, 116, 104, 101, 32, 112, 117, 114, 112, 111, 115, 101, 115, 10, 32, 32, 32, 32, 32, 32, 111, 102, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 115, 104, 97, 108, 108, 32, 110, 111, 116, 32, 105, 110, 99, 108, 117, 100, 101, 32, 119, 111, 114, 107, 115, 32, 116, 104, 97, 116, 32, 114, 101, 109, 97, 105, 110, 10, 32, 32, 32, 32, 32, 32, 115, 101, 112, 97, 114, 97, 98, 108, 101, 32, 102, 114, 111, 109, 44, 32, 111, 114, 32, 109, 101, 114, 101, 108, 121, 32, 108, 105, 110, 107, 32, 40, 111, 114, 32, 98, 105, 110, 100, 32, 98, 121, 32, 110, 97, 109, 101, 41, 32, 116, 111, 32, 116, 104, 101, 32, 105, 110, 116, 101, 114, 102, 97, 99, 101, 115, 32, 111, 102, 44, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 101, 114, 101, 111, 102, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 97, 110, 121, 32, 119, 111, 114, 107, 32, 111, 102, 32, 97, 117, 116, 104, 111, 114, 115, 104, 105, 112, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 111, 114, 105, 103, 105, 110, 97, 108, 32, 118, 101, 114, 115, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 97, 110, 121, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 32, 111, 114, 32, 97, 100, 100, 105, 116, 105, 111, 110, 115, 10, 32, 32, 32, 32, 32, 32, 116, 111, 32, 116, 104, 97, 116, 32, 87, 111, 114, 107, 32, 111, 114, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 101, 114, 101, 111, 102, 44, 32, 116, 104, 97, 116, 32, 105, 115, 32, 105, 110, 116, 101, 110, 116, 105, 111, 110, 97, 108, 108, 121, 10, 32, 32, 32, 32, 32, 32, 115, 117, 98, 109, 105, 116, 116, 101, 100, 32, 116, 111, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 102, 111, 114, 32, 105, 110, 99, 108, 117, 115, 105, 111, 110, 32, 105, 110, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 98, 121, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 10, 32, 32, 32, 32, 32, 32, 111, 114, 32, 98, 121, 32, 97, 110, 32, 105, 110, 100, 105, 118, 105, 100, 117, 97, 108, 32, 111, 114, 32, 76, 101, 103, 97, 108, 32, 69, 110, 116, 105, 116, 121, 32, 97, 117, 116, 104, 111, 114, 105, 122, 101, 100, 32, 116, 111, 32, 115, 117, 98, 109, 105, 116, 32, 111, 110, 32, 98, 101, 104, 97, 108, 102, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 46, 32, 70, 111, 114, 32, 116, 104, 101, 32, 112, 117, 114, 112, 111, 115, 101, 115, 32, 111, 102, 32, 116, 104, 105, 115, 32, 100, 101, 102, 105, 110, 105, 116, 105, 111, 110, 44, 32, 34, 115, 117, 98, 109, 105, 116, 116, 101, 100, 34, 10, 32, 32, 32, 32, 32, 32, 109, 101, 97, 110, 115, 32, 97, 110, 121, 32, 102, 111, 114, 109, 32, 111, 102, 32, 101, 108, 101, 99, 116, 114, 111, 110, 105, 99, 44, 32, 118, 101, 114, 98, 97, 108, 44, 32, 111, 114, 32, 119, 114, 105, 116, 116, 101, 110, 32, 99, 111, 109, 109, 117, 110, 105, 99, 97, 116, 105, 111, 110, 32, 115, 101, 110, 116, 10, 32, 32, 32, 32, 32, 32, 116, 111, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 111, 114, 32, 105, 116, 115, 32, 114, 101, 112, 114, 101, 115, 101, 110, 116, 97, 116, 105, 118, 101, 115, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 98, 117, 116, 32, 110, 111, 116, 32, 108, 105, 109, 105, 116, 101, 100, 32, 116, 111, 10, 32, 32, 32, 32, 32, 32, 99, 111, 109, 109, 117, 110, 105, 99, 97, 116, 105, 111, 110, 32, 111, 110, 32, 101, 108, 101, 99, 116, 114, 111, 110, 105, 99, 32, 109, 97, 105, 108, 105, 110, 103, 32, 108, 105, 115, 116, 115, 44, 32, 115, 111, 117, 114, 99, 101, 32, 99, 111, 100, 101, 32, 99, 111, 110, 116, 114, 111, 108, 32, 115, 121, 115, 116, 101, 109, 115, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 100, 32, 105, 115, 115, 117, 101, 32, 116, 114, 97, 99, 107, 105, 110, 103, 32, 115, 121, 115, 116, 101, 109, 115, 32, 116, 104, 97, 116, 32, 97, 114, 101, 32, 109, 97, 110, 97, 103, 101, 100, 32, 98, 121, 44, 32, 111, 114, 32, 111, 110, 32, 98, 101, 104, 97, 108, 102, 32, 111, 102, 44, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 102, 111, 114, 32, 116, 104, 101, 32, 112, 117, 114, 112, 111, 115, 101, 32, 111, 102, 32, 100, 105, 115, 99, 117, 115, 115, 105, 110, 103, 32, 97, 110, 100, 32, 105, 109, 112, 114, 111, 118, 105, 110, 103, 32, 116, 104, 101, 32, 87, 111, 114, 107, 44, 32, 98, 117, 116, 10, 32, 32, 32, 32, 32, 32, 101, 120, 99, 108, 117, 100, 105, 110, 103, 32, 99, 111, 109, 109, 117, 110, 105, 99, 97, 116, 105, 111, 110, 32, 116, 104, 97, 116, 32, 105, 115, 32, 99, 111, 110, 115, 112, 105, 99, 117, 111, 117, 115, 108, 121, 32, 109, 97, 114, 107, 101, 100, 32, 111, 114, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 10, 32, 32, 32, 32, 32, 32, 100, 101, 115, 105, 103, 110, 97, 116, 101, 100, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 32, 98, 121, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 32, 97, 115, 32, 34, 78, 111, 116, 32, 97, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 46, 34, 10, 10, 32, 32, 32, 32, 32, 32, 34, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 97, 110, 100, 32, 97, 110, 121, 32, 105, 110, 100, 105, 118, 105, 100, 117, 97, 108, 32, 111, 114, 32, 76, 101, 103, 97, 108, 32, 69, 110, 116, 105, 116, 121, 10, 32, 32, 32, 32, 32, 32, 111, 110, 32, 98, 101, 104, 97, 108, 102, 32, 111, 102, 32, 119, 104, 111, 109, 32, 97, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 104, 97, 115, 32, 98, 101, 101, 110, 32, 114, 101, 99, 101, 105, 118, 101, 100, 32, 98, 121, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 115, 117, 98, 115, 101, 113, 117, 101, 110, 116, 108, 121, 32, 105, 110, 99, 111, 114, 112, 111, 114, 97, 116, 101, 100, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 101, 32, 87, 111, 114, 107, 46, 10, 10, 32, 32, 32, 50, 46, 32, 71, 114, 97, 110, 116, 32, 111, 102, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 76, 105, 99, 101, 110, 115, 101, 46, 32, 83, 117, 98, 106, 101, 99, 116, 32, 116, 111, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 101, 97, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 104, 101, 114, 101, 98, 121, 32, 103, 114, 97, 110, 116, 115, 32, 116, 111, 32, 89, 111, 117, 32, 97, 32, 112, 101, 114, 112, 101, 116, 117, 97, 108, 44, 10, 32, 32, 32, 32, 32, 32, 119, 111, 114, 108, 100, 119, 105, 100, 101, 44, 32, 110, 111, 110, 45, 101, 120, 99, 108, 117, 115, 105, 118, 101, 44, 32, 110, 111, 45, 99, 104, 97, 114, 103, 101, 44, 32, 114, 111, 121, 97, 108, 116, 121, 45, 102, 114, 101, 101, 44, 32, 105, 114, 114, 101, 118, 111, 99, 97, 98, 108, 101, 10, 32, 32, 32, 32, 32, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 108, 105, 99, 101, 110, 115, 101, 32, 116, 111, 32, 114, 101, 112, 114, 111, 100, 117, 99, 101, 44, 32, 112, 114, 101, 112, 97, 114, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 111, 102, 44, 10, 32, 32, 32, 32, 32, 32, 112, 117, 98, 108, 105, 99, 108, 121, 32, 100, 105, 115, 112, 108, 97, 121, 44, 32, 112, 117, 98, 108, 105, 99, 108, 121, 32, 112, 101, 114, 102, 111, 114, 109, 44, 32, 115, 117, 98, 108, 105, 99, 101, 110, 115, 101, 44, 32, 97, 110, 100, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 115, 117, 99, 104, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 105, 110, 32, 83, 111, 117, 114, 99, 101, 32, 111, 114, 32, 79, 98, 106, 101, 99, 116, 32, 102, 111, 114, 109, 46, 10, 10, 32, 32, 32, 51, 46, 32, 71, 114, 97, 110, 116, 32, 111, 102, 32, 80, 97, 116, 101, 110, 116, 32, 76, 105, 99, 101, 110, 115, 101, 46, 32, 83, 117, 98, 106, 101, 99, 116, 32, 116, 111, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 101, 97, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 104, 101, 114, 101, 98, 121, 32, 103, 114, 97, 110, 116, 115, 32, 116, 111, 32, 89, 111, 117, 32, 97, 32, 112, 101, 114, 112, 101, 116, 117, 97, 108, 44, 10, 32, 32, 32, 32, 32, 32, 119, 111, 114, 108, 100, 119, 105, 100, 101, 44, 32, 110, 111, 110, 45, 101, 120, 99, 108, 117, 115, 105, 118, 101, 44, 32, 110, 111, 45, 99, 104, 97, 114, 103, 101, 44, 32, 114, 111, 121, 97, 108, 116, 121, 45, 102, 114, 101, 101, 44, 32, 105, 114, 114, 101, 118, 111, 99, 97, 98, 108, 101, 10, 32, 32, 32, 32, 32, 32, 40, 101, 120, 99, 101, 112, 116, 32, 97, 115, 32, 115, 116, 97, 116, 101, 100, 32, 105, 110, 32, 116, 104, 105, 115, 32, 115, 101, 99, 116, 105, 111, 110, 41, 32, 112, 97, 116, 101, 110, 116, 32, 108, 105, 99, 101, 110, 115, 101, 32, 116, 111, 32, 109, 97, 107, 101, 44, 32, 104, 97, 118, 101, 32, 109, 97, 100, 101, 44, 10, 32, 32, 32, 32, 32, 32, 117, 115, 101, 44, 32, 111, 102, 102, 101, 114, 32, 116, 111, 32, 115, 101, 108, 108, 44, 32, 115, 101, 108, 108, 44, 32, 105, 109, 112, 111, 114, 116, 44, 32, 97, 110, 100, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 32, 116, 114, 97, 110, 115, 102, 101, 114, 32, 116, 104, 101, 32, 87, 111, 114, 107, 44, 10, 32, 32, 32, 32, 32, 32, 119, 104, 101, 114, 101, 32, 115, 117, 99, 104, 32, 108, 105, 99, 101, 110, 115, 101, 32, 97, 112, 112, 108, 105, 101, 115, 32, 111, 110, 108, 121, 32, 116, 111, 32, 116, 104, 111, 115, 101, 32, 112, 97, 116, 101, 110, 116, 32, 99, 108, 97, 105, 109, 115, 32, 108, 105, 99, 101, 110, 115, 97, 98, 108, 101, 10, 32, 32, 32, 32, 32, 32, 98, 121, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 116, 104, 97, 116, 32, 97, 114, 101, 32, 110, 101, 99, 101, 115, 115, 97, 114, 105, 108, 121, 32, 105, 110, 102, 114, 105, 110, 103, 101, 100, 32, 98, 121, 32, 116, 104, 101, 105, 114, 10, 32, 32, 32, 32, 32, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 40, 115, 41, 32, 97, 108, 111, 110, 101, 32, 111, 114, 32, 98, 121, 32, 99, 111, 109, 98, 105, 110, 97, 116, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 105, 114, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 40, 115, 41, 10, 32, 32, 32, 32, 32, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 116, 111, 32, 119, 104, 105, 99, 104, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 40, 115, 41, 32, 119, 97, 115, 32, 115, 117, 98, 109, 105, 116, 116, 101, 100, 46, 32, 73, 102, 32, 89, 111, 117, 10, 32, 32, 32, 32, 32, 32, 105, 110, 115, 116, 105, 116, 117, 116, 101, 32, 112, 97, 116, 101, 110, 116, 32, 108, 105, 116, 105, 103, 97, 116, 105, 111, 110, 32, 97, 103, 97, 105, 110, 115, 116, 32, 97, 110, 121, 32, 101, 110, 116, 105, 116, 121, 32, 40, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 97, 10, 32, 32, 32, 32, 32, 32, 99, 114, 111, 115, 115, 45, 99, 108, 97, 105, 109, 32, 111, 114, 32, 99, 111, 117, 110, 116, 101, 114, 99, 108, 97, 105, 109, 32, 105, 110, 32, 97, 32, 108, 97, 119, 115, 117, 105, 116, 41, 32, 97, 108, 108, 101, 103, 105, 110, 103, 32, 116, 104, 97, 116, 32, 116, 104, 101, 32, 87, 111, 114, 107, 10, 32, 32, 32, 32, 32, 32, 111, 114, 32, 97, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 105, 110, 99, 111, 114, 112, 111, 114, 97, 116, 101, 100, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 99, 111, 110, 115, 116, 105, 116, 117, 116, 101, 115, 32, 100, 105, 114, 101, 99, 116, 10, 32, 32, 32, 32, 32, 32, 111, 114, 32, 99, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 121, 32, 112, 97, 116, 101, 110, 116, 32, 105, 110, 102, 114, 105, 110, 103, 101, 109, 101, 110, 116, 44, 32, 116, 104, 101, 110, 32, 97, 110, 121, 32, 112, 97, 116, 101, 110, 116, 32, 108, 105, 99, 101, 110, 115, 101, 115, 10, 32, 32, 32, 32, 32, 32, 103, 114, 97, 110, 116, 101, 100, 32, 116, 111, 32, 89, 111, 117, 32, 117, 110, 100, 101, 114, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 97, 116, 32, 87, 111, 114, 107, 32, 115, 104, 97, 108, 108, 32, 116, 101, 114, 109, 105, 110, 97, 116, 101, 10, 32, 32, 32, 32, 32, 32, 97, 115, 32, 111, 102, 32, 116, 104, 101, 32, 100, 97, 116, 101, 32, 115, 117, 99, 104, 32, 108, 105, 116, 105, 103, 97, 116, 105, 111, 110, 32, 105, 115, 32, 102, 105, 108, 101, 100, 46, 10, 10, 32, 32, 32, 52, 46, 32, 82, 101, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 46, 32, 89, 111, 117, 32, 109, 97, 121, 32, 114, 101, 112, 114, 111, 100, 117, 99, 101, 32, 97, 110, 100, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 32, 99, 111, 112, 105, 101, 115, 32, 111, 102, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 87, 111, 114, 107, 32, 111, 114, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 101, 114, 101, 111, 102, 32, 105, 110, 32, 97, 110, 121, 32, 109, 101, 100, 105, 117, 109, 44, 32, 119, 105, 116, 104, 32, 111, 114, 32, 119, 105, 116, 104, 111, 117, 116, 10, 32, 32, 32, 32, 32, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 44, 32, 97, 110, 100, 32, 105, 110, 32, 83, 111, 117, 114, 99, 101, 32, 111, 114, 32, 79, 98, 106, 101, 99, 116, 32, 102, 111, 114, 109, 44, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 116, 104, 97, 116, 32, 89, 111, 117, 10, 32, 32, 32, 32, 32, 32, 109, 101, 101, 116, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 58, 10, 10, 32, 32, 32, 32, 32, 32, 40, 97, 41, 32, 89, 111, 117, 32, 109, 117, 115, 116, 32, 103, 105, 118, 101, 32, 97, 110, 121, 32, 111, 116, 104, 101, 114, 32, 114, 101, 99, 105, 112, 105, 101, 110, 116, 115, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 59, 32, 97, 110, 100, 10, 10, 32, 32, 32, 32, 32, 32, 40, 98, 41, 32, 89, 111, 117, 32, 109, 117, 115, 116, 32, 99, 97, 117, 115, 101, 32, 97, 110, 121, 32, 109, 111, 100, 105, 102, 105, 101, 100, 32, 102, 105, 108, 101, 115, 32, 116, 111, 32, 99, 97, 114, 114, 121, 32, 112, 114, 111, 109, 105, 110, 101, 110, 116, 32, 110, 111, 116, 105, 99, 101, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 116, 97, 116, 105, 110, 103, 32, 116, 104, 97, 116, 32, 89, 111, 117, 32, 99, 104, 97, 110, 103, 101, 100, 32, 116, 104, 101, 32, 102, 105, 108, 101, 115, 59, 32, 97, 110, 100, 10, 10, 32, 32, 32, 32, 32, 32, 40, 99, 41, 32, 89, 111, 117, 32, 109, 117, 115, 116, 32, 114, 101, 116, 97, 105, 110, 44, 32, 105, 110, 32, 116, 104, 101, 32, 83, 111, 117, 114, 99, 101, 32, 102, 111, 114, 109, 32, 111, 102, 32, 97, 110, 121, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 104, 97, 116, 32, 89, 111, 117, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 44, 32, 97, 108, 108, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 44, 32, 112, 97, 116, 101, 110, 116, 44, 32, 116, 114, 97, 100, 101, 109, 97, 114, 107, 44, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 116, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 110, 111, 116, 105, 99, 101, 115, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 83, 111, 117, 114, 99, 101, 32, 102, 111, 114, 109, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 101, 120, 99, 108, 117, 100, 105, 110, 103, 32, 116, 104, 111, 115, 101, 32, 110, 111, 116, 105, 99, 101, 115, 32, 116, 104, 97, 116, 32, 100, 111, 32, 110, 111, 116, 32, 112, 101, 114, 116, 97, 105, 110, 32, 116, 111, 32, 97, 110, 121, 32, 112, 97, 114, 116, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 59, 32, 97, 110, 100, 10, 10, 32, 32, 32, 32, 32, 32, 40, 100, 41, 32, 73, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 105, 110, 99, 108, 117, 100, 101, 115, 32, 97, 32, 34, 78, 79, 84, 73, 67, 69, 34, 32, 116, 101, 120, 116, 32, 102, 105, 108, 101, 32, 97, 115, 32, 112, 97, 114, 116, 32, 111, 102, 32, 105, 116, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 44, 32, 116, 104, 101, 110, 32, 97, 110, 121, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 97, 116, 32, 89, 111, 117, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 32, 109, 117, 115, 116, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 110, 99, 108, 117, 100, 101, 32, 97, 32, 114, 101, 97, 100, 97, 98, 108, 101, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 97, 116, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 110, 111, 116, 105, 99, 101, 115, 32, 99, 111, 110, 116, 97, 105, 110, 101, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 116, 104, 105, 110, 32, 115, 117, 99, 104, 32, 78, 79, 84, 73, 67, 69, 32, 102, 105, 108, 101, 44, 32, 101, 120, 99, 108, 117, 100, 105, 110, 103, 32, 116, 104, 111, 115, 101, 32, 110, 111, 116, 105, 99, 101, 115, 32, 116, 104, 97, 116, 32, 100, 111, 32, 110, 111, 116, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 101, 114, 116, 97, 105, 110, 32, 116, 111, 32, 97, 110, 121, 32, 112, 97, 114, 116, 32, 111, 102, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 44, 32, 105, 110, 32, 97, 116, 32, 108, 101, 97, 115, 116, 32, 111, 110, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 102, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 32, 112, 108, 97, 99, 101, 115, 58, 32, 119, 105, 116, 104, 105, 110, 32, 97, 32, 78, 79, 84, 73, 67, 69, 32, 116, 101, 120, 116, 32, 102, 105, 108, 101, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 115, 32, 112, 97, 114, 116, 32, 111, 102, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 59, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 101, 32, 83, 111, 117, 114, 99, 101, 32, 102, 111, 114, 109, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 99, 117, 109, 101, 110, 116, 97, 116, 105, 111, 110, 44, 32, 105, 102, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 97, 108, 111, 110, 103, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 59, 32, 111, 114, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 116, 104, 105, 110, 32, 97, 32, 100, 105, 115, 112, 108, 97, 121, 32, 103, 101, 110, 101, 114, 97, 116, 101, 100, 32, 98, 121, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 44, 32, 105, 102, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 101, 114, 101, 118, 101, 114, 32, 115, 117, 99, 104, 32, 116, 104, 105, 114, 100, 45, 112, 97, 114, 116, 121, 32, 110, 111, 116, 105, 99, 101, 115, 32, 110, 111, 114, 109, 97, 108, 108, 121, 32, 97, 112, 112, 101, 97, 114, 46, 32, 84, 104, 101, 32, 99, 111, 110, 116, 101, 110, 116, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 102, 32, 116, 104, 101, 32, 78, 79, 84, 73, 67, 69, 32, 102, 105, 108, 101, 32, 97, 114, 101, 32, 102, 111, 114, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 97, 108, 32, 112, 117, 114, 112, 111, 115, 101, 115, 32, 111, 110, 108, 121, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 32, 110, 111, 116, 32, 109, 111, 100, 105, 102, 121, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 32, 89, 111, 117, 32, 109, 97, 121, 32, 97, 100, 100, 32, 89, 111, 117, 114, 32, 111, 119, 110, 32, 97, 116, 116, 114, 105, 98, 117, 116, 105, 111, 110, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 110, 111, 116, 105, 99, 101, 115, 32, 119, 105, 116, 104, 105, 110, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 97, 116, 32, 89, 111, 117, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 44, 32, 97, 108, 111, 110, 103, 115, 105, 100, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 32, 97, 115, 32, 97, 110, 32, 97, 100, 100, 101, 110, 100, 117, 109, 32, 116, 111, 32, 116, 104, 101, 32, 78, 79, 84, 73, 67, 69, 32, 116, 101, 120, 116, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 87, 111, 114, 107, 44, 32, 112, 114, 111, 118, 105, 100, 101, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 104, 97, 116, 32, 115, 117, 99, 104, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 97, 116, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 110, 111, 116, 105, 99, 101, 115, 32, 99, 97, 110, 110, 111, 116, 32, 98, 101, 32, 99, 111, 110, 115, 116, 114, 117, 101, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 115, 32, 109, 111, 100, 105, 102, 121, 105, 110, 103, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 32, 32, 32, 89, 111, 117, 32, 109, 97, 121, 32, 97, 100, 100, 32, 89, 111, 117, 114, 32, 111, 119, 110, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 115, 116, 97, 116, 101, 109, 101, 110, 116, 32, 116, 111, 32, 89, 111, 117, 114, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 109, 97, 121, 32, 112, 114, 111, 118, 105, 100, 101, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 111, 114, 32, 100, 105, 102, 102, 101, 114, 101, 110, 116, 32, 108, 105, 99, 101, 110, 115, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 10, 32, 32, 32, 32, 32, 32, 102, 111, 114, 32, 117, 115, 101, 44, 32, 114, 101, 112, 114, 111, 100, 117, 99, 116, 105, 111, 110, 44, 32, 111, 114, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 111, 102, 32, 89, 111, 117, 114, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 44, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 102, 111, 114, 32, 97, 110, 121, 32, 115, 117, 99, 104, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 97, 115, 32, 97, 32, 119, 104, 111, 108, 101, 44, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 89, 111, 117, 114, 32, 117, 115, 101, 44, 10, 32, 32, 32, 32, 32, 32, 114, 101, 112, 114, 111, 100, 117, 99, 116, 105, 111, 110, 44, 32, 97, 110, 100, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 32, 99, 111, 109, 112, 108, 105, 101, 115, 32, 119, 105, 116, 104, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 115, 116, 97, 116, 101, 100, 32, 105, 110, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 53, 46, 32, 83, 117, 98, 109, 105, 115, 115, 105, 111, 110, 32, 111, 102, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 115, 46, 32, 85, 110, 108, 101, 115, 115, 32, 89, 111, 117, 32, 101, 120, 112, 108, 105, 99, 105, 116, 108, 121, 32, 115, 116, 97, 116, 101, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 121, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 105, 110, 116, 101, 110, 116, 105, 111, 110, 97, 108, 108, 121, 32, 115, 117, 98, 109, 105, 116, 116, 101, 100, 32, 102, 111, 114, 32, 105, 110, 99, 108, 117, 115, 105, 111, 110, 32, 105, 110, 32, 116, 104, 101, 32, 87, 111, 114, 107, 10, 32, 32, 32, 32, 32, 32, 98, 121, 32, 89, 111, 117, 32, 116, 111, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 115, 104, 97, 108, 108, 32, 98, 101, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 119, 105, 116, 104, 111, 117, 116, 32, 97, 110, 121, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 116, 101, 114, 109, 115, 32, 111, 114, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 46, 10, 32, 32, 32, 32, 32, 32, 78, 111, 116, 119, 105, 116, 104, 115, 116, 97, 110, 100, 105, 110, 103, 32, 116, 104, 101, 32, 97, 98, 111, 118, 101, 44, 32, 110, 111, 116, 104, 105, 110, 103, 32, 104, 101, 114, 101, 105, 110, 32, 115, 104, 97, 108, 108, 32, 115, 117, 112, 101, 114, 115, 101, 100, 101, 32, 111, 114, 32, 109, 111, 100, 105, 102, 121, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 111, 102, 32, 97, 110, 121, 32, 115, 101, 112, 97, 114, 97, 116, 101, 32, 108, 105, 99, 101, 110, 115, 101, 32, 97, 103, 114, 101, 101, 109, 101, 110, 116, 32, 121, 111, 117, 32, 109, 97, 121, 32, 104, 97, 118, 101, 32, 101, 120, 101, 99, 117, 116, 101, 100, 10, 32, 32, 32, 32, 32, 32, 119, 105, 116, 104, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 114, 101, 103, 97, 114, 100, 105, 110, 103, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 115, 46, 10, 10, 32, 32, 32, 54, 46, 32, 84, 114, 97, 100, 101, 109, 97, 114, 107, 115, 46, 32, 84, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 32, 100, 111, 101, 115, 32, 110, 111, 116, 32, 103, 114, 97, 110, 116, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 32, 116, 111, 32, 117, 115, 101, 32, 116, 104, 101, 32, 116, 114, 97, 100, 101, 10, 32, 32, 32, 32, 32, 32, 110, 97, 109, 101, 115, 44, 32, 116, 114, 97, 100, 101, 109, 97, 114, 107, 115, 44, 32, 115, 101, 114, 118, 105, 99, 101, 32, 109, 97, 114, 107, 115, 44, 32, 111, 114, 32, 112, 114, 111, 100, 117, 99, 116, 32, 110, 97, 109, 101, 115, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 111, 114, 44, 10, 32, 32, 32, 32, 32, 32, 101, 120, 99, 101, 112, 116, 32, 97, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 102, 111, 114, 32, 114, 101, 97, 115, 111, 110, 97, 98, 108, 101, 32, 97, 110, 100, 32, 99, 117, 115, 116, 111, 109, 97, 114, 121, 32, 117, 115, 101, 32, 105, 110, 32, 100, 101, 115, 99, 114, 105, 98, 105, 110, 103, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 111, 114, 105, 103, 105, 110, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 114, 101, 112, 114, 111, 100, 117, 99, 105, 110, 103, 32, 116, 104, 101, 32, 99, 111, 110, 116, 101, 110, 116, 32, 111, 102, 32, 116, 104, 101, 32, 78, 79, 84, 73, 67, 69, 32, 102, 105, 108, 101, 46, 10, 10, 32, 32, 32, 55, 46, 32, 68, 105, 115, 99, 108, 97, 105, 109, 101, 114, 32, 111, 102, 32, 87, 97, 114, 114, 97, 110, 116, 121, 46, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 112, 114, 111, 118, 105, 100, 101, 115, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 40, 97, 110, 100, 32, 101, 97, 99, 104, 10, 32, 32, 32, 32, 32, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 112, 114, 111, 118, 105, 100, 101, 115, 32, 105, 116, 115, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 115, 41, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 32, 32, 32, 32, 32, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 105, 109, 112, 108, 105, 101, 100, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 44, 32, 119, 105, 116, 104, 111, 117, 116, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 44, 32, 97, 110, 121, 32, 119, 97, 114, 114, 97, 110, 116, 105, 101, 115, 32, 111, 114, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 10, 32, 32, 32, 32, 32, 32, 111, 102, 32, 84, 73, 84, 76, 69, 44, 32, 78, 79, 78, 45, 73, 78, 70, 82, 73, 78, 71, 69, 77, 69, 78, 84, 44, 32, 77, 69, 82, 67, 72, 65, 78, 84, 65, 66, 73, 76, 73, 84, 89, 44, 32, 111, 114, 32, 70, 73, 84, 78, 69, 83, 83, 32, 70, 79, 82, 32, 65, 10, 32, 32, 32, 32, 32, 32, 80, 65, 82, 84, 73, 67, 85, 76, 65, 82, 32, 80, 85, 82, 80, 79, 83, 69, 46, 32, 89, 111, 117, 32, 97, 114, 101, 32, 115, 111, 108, 101, 108, 121, 32, 114, 101, 115, 112, 111, 110, 115, 105, 98, 108, 101, 32, 102, 111, 114, 32, 100, 101, 116, 101, 114, 109, 105, 110, 105, 110, 103, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 97, 112, 112, 114, 111, 112, 114, 105, 97, 116, 101, 110, 101, 115, 115, 32, 111, 102, 32, 117, 115, 105, 110, 103, 32, 111, 114, 32, 114, 101, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 110, 103, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 97, 115, 115, 117, 109, 101, 32, 97, 110, 121, 10, 32, 32, 32, 32, 32, 32, 114, 105, 115, 107, 115, 32, 97, 115, 115, 111, 99, 105, 97, 116, 101, 100, 32, 119, 105, 116, 104, 32, 89, 111, 117, 114, 32, 101, 120, 101, 114, 99, 105, 115, 101, 32, 111, 102, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 56, 46, 32, 76, 105, 109, 105, 116, 97, 116, 105, 111, 110, 32, 111, 102, 32, 76, 105, 97, 98, 105, 108, 105, 116, 121, 46, 32, 73, 110, 32, 110, 111, 32, 101, 118, 101, 110, 116, 32, 97, 110, 100, 32, 117, 110, 100, 101, 114, 32, 110, 111, 32, 108, 101, 103, 97, 108, 32, 116, 104, 101, 111, 114, 121, 44, 10, 32, 32, 32, 32, 32, 32, 119, 104, 101, 116, 104, 101, 114, 32, 105, 110, 32, 116, 111, 114, 116, 32, 40, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 110, 101, 103, 108, 105, 103, 101, 110, 99, 101, 41, 44, 32, 99, 111, 110, 116, 114, 97, 99, 116, 44, 32, 111, 114, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 44, 10, 32, 32, 32, 32, 32, 32, 117, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 40, 115, 117, 99, 104, 32, 97, 115, 32, 100, 101, 108, 105, 98, 101, 114, 97, 116, 101, 32, 97, 110, 100, 32, 103, 114, 111, 115, 115, 108, 121, 10, 32, 32, 32, 32, 32, 32, 110, 101, 103, 108, 105, 103, 101, 110, 116, 32, 97, 99, 116, 115, 41, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 104, 97, 108, 108, 32, 97, 110, 121, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 98, 101, 10, 32, 32, 32, 32, 32, 32, 108, 105, 97, 98, 108, 101, 32, 116, 111, 32, 89, 111, 117, 32, 102, 111, 114, 32, 100, 97, 109, 97, 103, 101, 115, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 97, 110, 121, 32, 100, 105, 114, 101, 99, 116, 44, 32, 105, 110, 100, 105, 114, 101, 99, 116, 44, 32, 115, 112, 101, 99, 105, 97, 108, 44, 10, 32, 32, 32, 32, 32, 32, 105, 110, 99, 105, 100, 101, 110, 116, 97, 108, 44, 32, 111, 114, 32, 99, 111, 110, 115, 101, 113, 117, 101, 110, 116, 105, 97, 108, 32, 100, 97, 109, 97, 103, 101, 115, 32, 111, 102, 32, 97, 110, 121, 32, 99, 104, 97, 114, 97, 99, 116, 101, 114, 32, 97, 114, 105, 115, 105, 110, 103, 32, 97, 115, 32, 97, 10, 32, 32, 32, 32, 32, 32, 114, 101, 115, 117, 108, 116, 32, 111, 102, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 32, 111, 114, 32, 111, 117, 116, 32, 111, 102, 32, 116, 104, 101, 32, 117, 115, 101, 32, 111, 114, 32, 105, 110, 97, 98, 105, 108, 105, 116, 121, 32, 116, 111, 32, 117, 115, 101, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 87, 111, 114, 107, 32, 40, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 98, 117, 116, 32, 110, 111, 116, 32, 108, 105, 109, 105, 116, 101, 100, 32, 116, 111, 32, 100, 97, 109, 97, 103, 101, 115, 32, 102, 111, 114, 32, 108, 111, 115, 115, 32, 111, 102, 32, 103, 111, 111, 100, 119, 105, 108, 108, 44, 10, 32, 32, 32, 32, 32, 32, 119, 111, 114, 107, 32, 115, 116, 111, 112, 112, 97, 103, 101, 44, 32, 99, 111, 109, 112, 117, 116, 101, 114, 32, 102, 97, 105, 108, 117, 114, 101, 32, 111, 114, 32, 109, 97, 108, 102, 117, 110, 99, 116, 105, 111, 110, 44, 32, 111, 114, 32, 97, 110, 121, 32, 97, 110, 100, 32, 97, 108, 108, 10, 32, 32, 32, 32, 32, 32, 111, 116, 104, 101, 114, 32, 99, 111, 109, 109, 101, 114, 99, 105, 97, 108, 32, 100, 97, 109, 97, 103, 101, 115, 32, 111, 114, 32, 108, 111, 115, 115, 101, 115, 41, 44, 32, 101, 118, 101, 110, 32, 105, 102, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 10, 32, 32, 32, 32, 32, 32, 104, 97, 115, 32, 98, 101, 101, 110, 32, 97, 100, 118, 105, 115, 101, 100, 32, 111, 102, 32, 116, 104, 101, 32, 112, 111, 115, 115, 105, 98, 105, 108, 105, 116, 121, 32, 111, 102, 32, 115, 117, 99, 104, 32, 100, 97, 109, 97, 103, 101, 115, 46, 10, 10, 32, 32, 32, 57, 46, 32, 65, 99, 99, 101, 112, 116, 105, 110, 103, 32, 87, 97, 114, 114, 97, 110, 116, 121, 32, 111, 114, 32, 65, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 76, 105, 97, 98, 105, 108, 105, 116, 121, 46, 32, 87, 104, 105, 108, 101, 32, 114, 101, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 110, 103, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 111, 114, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 101, 114, 101, 111, 102, 44, 32, 89, 111, 117, 32, 109, 97, 121, 32, 99, 104, 111, 111, 115, 101, 32, 116, 111, 32, 111, 102, 102, 101, 114, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 100, 32, 99, 104, 97, 114, 103, 101, 32, 97, 32, 102, 101, 101, 32, 102, 111, 114, 44, 32, 97, 99, 99, 101, 112, 116, 97, 110, 99, 101, 32, 111, 102, 32, 115, 117, 112, 112, 111, 114, 116, 44, 32, 119, 97, 114, 114, 97, 110, 116, 121, 44, 32, 105, 110, 100, 101, 109, 110, 105, 116, 121, 44, 10, 32, 32, 32, 32, 32, 32, 111, 114, 32, 111, 116, 104, 101, 114, 32, 108, 105, 97, 98, 105, 108, 105, 116, 121, 32, 111, 98, 108, 105, 103, 97, 116, 105, 111, 110, 115, 32, 97, 110, 100, 47, 111, 114, 32, 114, 105, 103, 104, 116, 115, 32, 99, 111, 110, 115, 105, 115, 116, 101, 110, 116, 32, 119, 105, 116, 104, 32, 116, 104, 105, 115, 10, 32, 32, 32, 32, 32, 32, 76, 105, 99, 101, 110, 115, 101, 46, 32, 72, 111, 119, 101, 118, 101, 114, 44, 32, 105, 110, 32, 97, 99, 99, 101, 112, 116, 105, 110, 103, 32, 115, 117, 99, 104, 32, 111, 98, 108, 105, 103, 97, 116, 105, 111, 110, 115, 44, 32, 89, 111, 117, 32, 109, 97, 121, 32, 97, 99, 116, 32, 111, 110, 108, 121, 10, 32, 32, 32, 32, 32, 32, 111, 110, 32, 89, 111, 117, 114, 32, 111, 119, 110, 32, 98, 101, 104, 97, 108, 102, 32, 97, 110, 100, 32, 111, 110, 32, 89, 111, 117, 114, 32, 115, 111, 108, 101, 32, 114, 101, 115, 112, 111, 110, 115, 105, 98, 105, 108, 105, 116, 121, 44, 32, 110, 111, 116, 32, 111, 110, 32, 98, 101, 104, 97, 108, 102, 10, 32, 32, 32, 32, 32, 32, 111, 102, 32, 97, 110, 121, 32, 111, 116, 104, 101, 114, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 44, 32, 97, 110, 100, 32, 111, 110, 108, 121, 32, 105, 102, 32, 89, 111, 117, 32, 97, 103, 114, 101, 101, 32, 116, 111, 32, 105, 110, 100, 101, 109, 110, 105, 102, 121, 44, 10, 32, 32, 32, 32, 32, 32, 100, 101, 102, 101, 110, 100, 44, 32, 97, 110, 100, 32, 104, 111, 108, 100, 32, 101, 97, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 104, 97, 114, 109, 108, 101, 115, 115, 32, 102, 111, 114, 32, 97, 110, 121, 32, 108, 105, 97, 98, 105, 108, 105, 116, 121, 10, 32, 32, 32, 32, 32, 32, 105, 110, 99, 117, 114, 114, 101, 100, 32, 98, 121, 44, 32, 111, 114, 32, 99, 108, 97, 105, 109, 115, 32, 97, 115, 115, 101, 114, 116, 101, 100, 32, 97, 103, 97, 105, 110, 115, 116, 44, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 98, 121, 32, 114, 101, 97, 115, 111, 110, 10, 32, 32, 32, 32, 32, 32, 111, 102, 32, 121, 111, 117, 114, 32, 97, 99, 99, 101, 112, 116, 105, 110, 103, 32, 97, 110, 121, 32, 115, 117, 99, 104, 32, 119, 97, 114, 114, 97, 110, 116, 121, 32, 111, 114, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 108, 105, 97, 98, 105, 108, 105, 116, 121, 46, 10, 10, 32, 32, 32, 69, 78, 68, 32, 79, 70, 32, 84, 69, 82, 77, 83, 32, 65, 78, 68, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 10, 10, 32, 32, 32, 65, 80, 80, 69, 78, 68, 73, 88, 58, 32, 72, 111, 119, 32, 116, 111, 32, 97, 112, 112, 108, 121, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 116, 111, 32, 121, 111, 117, 114, 32, 119, 111, 114, 107, 46, 10, 10, 32, 32, 32, 32, 32, 32, 84, 111, 32, 97, 112, 112, 108, 121, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 116, 111, 32, 121, 111, 117, 114, 32, 119, 111, 114, 107, 44, 32, 97, 116, 116, 97, 99, 104, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 10, 32, 32, 32, 32, 32, 32, 98, 111, 105, 108, 101, 114, 112, 108, 97, 116, 101, 32, 110, 111, 116, 105, 99, 101, 44, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 102, 105, 101, 108, 100, 115, 32, 101, 110, 99, 108, 111, 115, 101, 100, 32, 98, 121, 32, 98, 114, 97, 99, 107, 101, 116, 115, 32, 34, 91, 93, 34, 10, 32, 32, 32, 32, 32, 32, 114, 101, 112, 108, 97, 99, 101, 100, 32, 119, 105, 116, 104, 32, 121, 111, 117, 114, 32, 111, 119, 110, 32, 105, 100, 101, 110, 116, 105, 102, 121, 105, 110, 103, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 46, 32, 40, 68, 111, 110, 39, 116, 32, 105, 110, 99, 108, 117, 100, 101, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 98, 114, 97, 99, 107, 101, 116, 115, 33, 41, 32, 32, 84, 104, 101, 32, 116, 101, 120, 116, 32, 115, 104, 111, 117, 108, 100, 32, 98, 101, 32, 101, 110, 99, 108, 111, 115, 101, 100, 32, 105, 110, 32, 116, 104, 101, 32, 97, 112, 112, 114, 111, 112, 114, 105, 97, 116, 101, 10, 32, 32, 32, 32, 32, 32, 99, 111, 109, 109, 101, 110, 116, 32, 115, 121, 110, 116, 97, 120, 32, 102, 111, 114, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 102, 111, 114, 109, 97, 116, 46, 32, 87, 101, 32, 97, 108, 115, 111, 32, 114, 101, 99, 111, 109, 109, 101, 110, 100, 32, 116, 104, 97, 116, 32, 97, 10, 32, 32, 32, 32, 32, 32, 102, 105, 108, 101, 32, 111, 114, 32, 99, 108, 97, 115, 115, 32, 110, 97, 109, 101, 32, 97, 110, 100, 32, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 32, 111, 102, 32, 112, 117, 114, 112, 111, 115, 101, 32, 98, 101, 32, 105, 110, 99, 108, 117, 100, 101, 100, 32, 111, 110, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 115, 97, 109, 101, 32, 34, 112, 114, 105, 110, 116, 101, 100, 32, 112, 97, 103, 101, 34, 32, 97, 115, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 110, 111, 116, 105, 99, 101, 32, 102, 111, 114, 32, 101, 97, 115, 105, 101, 114, 10, 32, 32, 32, 32, 32, 32, 105, 100, 101, 110, 116, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 105, 114, 100, 45, 112, 97, 114, 116, 121, 32, 97, 114, 99, 104, 105, 118, 101, 115, 46, 10, 10, 32, 32, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 91, 121, 121, 121, 121, 93, 32, 91, 110, 97, 109, 101, 32, 111, 102, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 93, 10, 10, 32, 32, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 32, 32, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 32, 32, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 10, 32, 32, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 10, 32, 32, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 32, 32, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 32, 32, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 32, 32, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 32, 32, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10}) + box.Add("/scaffoldings/golang/Makefile", []byte{71, 79, 76, 65, 78, 71, 67, 73, 76, 73, 78, 84, 86, 69, 82, 83, 73, 79, 78, 63, 61, 49, 46, 53, 48, 46, 48, 10, 71, 79, 73, 77, 80, 79, 82, 84, 83, 86, 69, 82, 83, 73, 79, 78, 63, 61, 118, 48, 46, 49, 46, 49, 50, 10, 71, 79, 88, 86, 69, 82, 83, 73, 79, 78, 63, 61, 118, 49, 46, 48, 46, 49, 10, 71, 79, 84, 69, 83, 84, 83, 85, 77, 86, 69, 82, 83, 73, 79, 78, 63, 61, 118, 49, 46, 56, 46, 50, 10, 67, 79, 86, 69, 82, 65, 71, 69, 79, 85, 84, 63, 61, 99, 111, 118, 101, 114, 97, 103, 101, 46, 111, 117, 116, 10, 67, 79, 86, 69, 82, 65, 71, 69, 72, 84, 77, 76, 63, 61, 99, 111, 118, 101, 114, 97, 103, 101, 46, 104, 116, 109, 108, 10, 71, 79, 74, 85, 78, 73, 84, 79, 85, 84, 63, 61, 103, 111, 45, 106, 117, 110, 105, 116, 46, 120, 109, 108, 10, 80, 65, 67, 75, 65, 71, 69, 78, 65, 77, 69, 63, 61, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 10, 67, 76, 73, 78, 65, 77, 69, 63, 61, 108, 97, 99, 101, 119, 111, 114, 107, 10, 71, 79, 95, 76, 68, 70, 76, 65, 71, 83, 61, 34, 45, 88, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 118, 101, 114, 115, 105, 111, 110, 46, 86, 101, 114, 115, 105, 111, 110, 61, 36, 40, 115, 104, 101, 108, 108, 32, 99, 97, 116, 32, 86, 69, 82, 83, 73, 79, 78, 41, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 88, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 118, 101, 114, 115, 105, 111, 110, 46, 71, 105, 116, 83, 72, 65, 61, 36, 40, 115, 104, 101, 108, 108, 32, 103, 105, 116, 32, 114, 101, 118, 45, 112, 97, 114, 115, 101, 32, 72, 69, 65, 68, 41, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 88, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 118, 101, 114, 115, 105, 111, 110, 46, 46, 66, 117, 105, 108, 100, 84, 105, 109, 101, 61, 36, 40, 115, 104, 101, 108, 108, 32, 100, 97, 116, 101, 32, 43, 37, 89, 37, 109, 37, 100, 37, 72, 37, 77, 37, 83, 41, 34, 10, 71, 79, 70, 76, 65, 71, 83, 61, 45, 109, 111, 100, 61, 118, 101, 110, 100, 111, 114, 10, 67, 71, 79, 95, 69, 78, 65, 66, 76, 69, 68, 63, 61, 48, 10, 101, 120, 112, 111, 114, 116, 32, 71, 79, 70, 76, 65, 71, 83, 32, 71, 79, 95, 76, 68, 70, 76, 65, 71, 83, 32, 67, 71, 79, 95, 69, 78, 65, 66, 76, 69, 68, 10, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 104, 101, 108, 112, 10, 104, 101, 108, 112, 58, 10, 9, 64, 101, 99, 104, 111, 32, 34, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 34, 10, 9, 64, 101, 99, 104, 111, 32, 34, 32, 77, 97, 107, 101, 102, 105, 108, 101, 32, 104, 101, 108, 112, 101, 114, 58, 34, 10, 9, 64, 101, 99, 104, 111, 32, 34, 34, 10, 9, 64, 103, 114, 101, 112, 32, 45, 70, 104, 32, 34, 35, 35, 34, 32, 36, 40, 77, 65, 75, 69, 70, 73, 76, 69, 95, 76, 73, 83, 84, 41, 32, 124, 32, 103, 114, 101, 112, 32, 45, 118, 32, 103, 114, 101, 112, 32, 124, 32, 115, 101, 100, 32, 45, 101, 32, 39, 115, 47, 92, 92, 36, 36, 47, 47, 39, 32, 124, 32, 115, 101, 100, 32, 45, 69, 32, 39, 115, 47, 94, 40, 91, 94, 58, 93, 42, 41, 58, 46, 42, 35, 35, 40, 46, 42, 41, 47, 32, 92, 49, 32, 45, 92, 50, 47, 39, 10, 9, 64, 101, 99, 104, 111, 32, 34, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 34, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 112, 114, 101, 112, 97, 114, 101, 10, 112, 114, 101, 112, 97, 114, 101, 58, 32, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 32, 103, 111, 45, 118, 101, 110, 100, 111, 114, 32, 35, 35, 32, 73, 110, 105, 116, 105, 97, 108, 105, 122, 101, 32, 116, 104, 101, 32, 103, 111, 32, 101, 110, 118, 105, 114, 111, 110, 109, 101, 110, 116, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 103, 111, 45, 118, 101, 110, 100, 111, 114, 10, 103, 111, 45, 118, 101, 110, 100, 111, 114, 58, 32, 35, 35, 32, 82, 117, 110, 115, 32, 103, 111, 32, 109, 111, 100, 32, 116, 105, 100, 121, 44, 32, 118, 101, 110, 100, 111, 114, 32, 97, 110, 100, 32, 118, 101, 114, 105, 102, 121, 32, 116, 111, 32, 99, 108, 101, 97, 110, 117, 112, 44, 32, 99, 111, 112, 121, 32, 97, 110, 100, 32, 118, 101, 114, 105, 102, 121, 32, 100, 101, 112, 101, 110, 100, 101, 110, 99, 105, 101, 115, 10, 9, 103, 111, 32, 109, 111, 100, 32, 116, 105, 100, 121, 10, 9, 103, 111, 32, 109, 111, 100, 32, 118, 101, 110, 100, 111, 114, 10, 9, 103, 111, 32, 109, 111, 100, 32, 118, 101, 114, 105, 102, 121, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 116, 101, 115, 116, 10, 116, 101, 115, 116, 58, 32, 112, 114, 101, 112, 97, 114, 101, 32, 35, 35, 32, 82, 117, 110, 32, 117, 110, 105, 116, 32, 116, 101, 115, 116, 115, 10, 9, 103, 111, 116, 101, 115, 116, 115, 117, 109, 32, 45, 102, 32, 116, 101, 115, 116, 110, 97, 109, 101, 32, 45, 45, 32, 45, 118, 32, 45, 99, 111, 118, 101, 114, 32, 45, 114, 117, 110, 61, 36, 40, 114, 101, 103, 101, 120, 41, 32, 45, 99, 111, 118, 101, 114, 112, 114, 111, 102, 105, 108, 101, 61, 36, 40, 67, 79, 86, 69, 82, 65, 71, 69, 79, 85, 84, 41, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 46, 47, 46, 46, 46, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 105, 109, 112, 111, 114, 116, 115, 45, 99, 104, 101, 99, 107, 10, 105, 109, 112, 111, 114, 116, 115, 45, 99, 104, 101, 99, 107, 58, 32, 35, 35, 32, 76, 105, 115, 116, 115, 32, 105, 109, 112, 111, 114, 116, 115, 32, 105, 115, 115, 117, 101, 115, 10, 9, 64, 116, 101, 115, 116, 32, 45, 122, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 105, 109, 112, 111, 114, 116, 115, 32, 45, 108, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 45, 102, 32, 123, 123, 46, 68, 105, 114, 125, 125, 32, 46, 47, 46, 46, 46, 41, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 108, 105, 110, 116, 10, 108, 105, 110, 116, 58, 32, 35, 35, 32, 82, 117, 110, 115, 32, 103, 111, 32, 108, 105, 110, 116, 101, 114, 10, 9, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 32, 114, 117, 110, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 102, 109, 116, 10, 102, 109, 116, 58, 32, 35, 35, 32, 82, 117, 110, 115, 32, 97, 110, 100, 32, 97, 112, 112, 108, 105, 101, 115, 32, 103, 111, 32, 102, 111, 114, 109, 97, 116, 116, 105, 110, 103, 32, 99, 104, 97, 110, 103, 101, 115, 10, 9, 64, 103, 111, 102, 109, 116, 32, 45, 119, 32, 45, 108, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 45, 102, 32, 123, 123, 46, 68, 105, 114, 125, 125, 32, 46, 47, 46, 46, 46, 41, 10, 9, 64, 103, 111, 105, 109, 112, 111, 114, 116, 115, 32, 45, 119, 32, 45, 108, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 45, 102, 32, 123, 123, 46, 68, 105, 114, 125, 125, 32, 46, 47, 46, 46, 46, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 102, 109, 116, 45, 99, 104, 101, 99, 107, 10, 102, 109, 116, 45, 99, 104, 101, 99, 107, 58, 32, 35, 35, 32, 76, 105, 115, 116, 115, 32, 102, 111, 114, 109, 97, 116, 116, 105, 110, 103, 32, 105, 115, 115, 117, 101, 115, 10, 9, 64, 116, 101, 115, 116, 32, 45, 122, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 102, 109, 116, 32, 45, 108, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 45, 102, 32, 123, 123, 46, 68, 105, 114, 125, 125, 32, 46, 47, 46, 46, 46, 41, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 98, 117, 105, 108, 100, 10, 98, 117, 105, 108, 100, 58, 32, 35, 35, 32, 67, 111, 109, 112, 105, 108, 101, 115, 32, 98, 105, 110, 97, 114, 121, 32, 102, 111, 114, 32, 116, 104, 101, 32, 114, 117, 110, 110, 105, 110, 103, 32, 119, 111, 114, 107, 115, 116, 97, 116, 105, 111, 110, 32, 40, 67, 68, 75, 32, 115, 117, 112, 112, 111, 114, 116, 41, 10, 9, 103, 111, 32, 98, 117, 105, 108, 100, 32, 46, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 98, 117, 105, 108, 100, 45, 99, 114, 111, 115, 115, 45, 112, 108, 97, 116, 102, 111, 114, 109, 10, 98, 117, 105, 108, 100, 45, 99, 114, 111, 115, 115, 45, 112, 108, 97, 116, 102, 111, 114, 109, 58, 32, 35, 35, 32, 67, 111, 109, 112, 105, 108, 101, 115, 32, 98, 105, 110, 97, 114, 105, 101, 115, 32, 102, 111, 114, 32, 97, 108, 108, 32, 115, 117, 112, 112, 111, 114, 116, 101, 100, 32, 112, 108, 97, 116, 102, 111, 114, 109, 115, 10, 9, 103, 111, 120, 32, 45, 111, 117, 116, 112, 117, 116, 61, 34, 98, 105, 110, 47, 36, 40, 80, 65, 67, 75, 65, 71, 69, 78, 65, 77, 69, 41, 45, 123, 123, 46, 79, 83, 125, 125, 45, 123, 123, 46, 65, 114, 99, 104, 125, 125, 34, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 111, 115, 61, 34, 108, 105, 110, 117, 120, 32, 119, 105, 110, 100, 111, 119, 115, 34, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 97, 114, 99, 104, 61, 34, 97, 109, 100, 54, 52, 32, 51, 56, 54, 34, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 111, 115, 97, 114, 99, 104, 61, 34, 100, 97, 114, 119, 105, 110, 47, 97, 109, 100, 54, 52, 32, 100, 97, 114, 119, 105, 110, 47, 97, 114, 109, 54, 52, 32, 108, 105, 110, 117, 120, 47, 97, 114, 109, 32, 108, 105, 110, 117, 120, 47, 97, 114, 109, 54, 52, 34, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 108, 100, 102, 108, 97, 103, 115, 61, 36, 40, 71, 79, 95, 76, 68, 70, 76, 65, 71, 83, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 10, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 58, 32, 35, 35, 32, 73, 110, 115, 116, 97, 108, 108, 32, 103, 111, 32, 105, 110, 100, 105, 114, 101, 99, 116, 32, 100, 101, 112, 101, 110, 100, 101, 110, 99, 105, 101, 115, 10, 105, 102, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 41, 41, 10, 9, 99, 117, 114, 108, 32, 45, 115, 83, 102, 76, 32, 104, 116, 116, 112, 115, 58, 47, 47, 114, 97, 119, 46, 103, 105, 116, 104, 117, 98, 117, 115, 101, 114, 99, 111, 110, 116, 101, 110, 116, 46, 99, 111, 109, 47, 103, 111, 108, 97, 110, 103, 99, 105, 47, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 47, 109, 97, 115, 116, 101, 114, 47, 105, 110, 115, 116, 97, 108, 108, 46, 115, 104, 32, 124, 32, 115, 104, 32, 45, 115, 32, 45, 45, 32, 45, 98, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 32, 118, 36, 40, 71, 79, 76, 65, 78, 71, 67, 73, 76, 73, 78, 84, 86, 69, 82, 83, 73, 79, 78, 41, 10, 101, 110, 100, 105, 102, 10, 105, 102, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 105, 109, 112, 111, 114, 116, 115, 41, 41, 10, 9, 71, 79, 70, 76, 65, 71, 83, 61, 45, 109, 111, 100, 61, 114, 101, 97, 100, 111, 110, 108, 121, 32, 103, 111, 32, 105, 110, 115, 116, 97, 108, 108, 32, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 120, 47, 116, 111, 111, 108, 115, 47, 99, 109, 100, 47, 103, 111, 105, 109, 112, 111, 114, 116, 115, 64, 36, 40, 71, 79, 73, 77, 80, 79, 82, 84, 83, 86, 69, 82, 83, 73, 79, 78, 41, 10, 101, 110, 100, 105, 102, 10, 105, 102, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 120, 41, 41, 10, 9, 71, 79, 70, 76, 65, 71, 83, 61, 45, 109, 111, 100, 61, 114, 101, 97, 100, 111, 110, 108, 121, 32, 103, 111, 32, 105, 110, 115, 116, 97, 108, 108, 32, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 109, 105, 116, 99, 104, 101, 108, 108, 104, 47, 103, 111, 120, 64, 36, 40, 71, 79, 88, 86, 69, 82, 83, 73, 79, 78, 41, 10, 101, 110, 100, 105, 102, 10, 105, 102, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 116, 101, 115, 116, 115, 117, 109, 41, 41, 10, 9, 71, 79, 70, 76, 65, 71, 83, 61, 45, 109, 111, 100, 61, 114, 101, 97, 100, 111, 110, 108, 121, 32, 103, 111, 32, 105, 110, 115, 116, 97, 108, 108, 32, 103, 111, 116, 101, 115, 116, 46, 116, 111, 111, 108, 115, 47, 103, 111, 116, 101, 115, 116, 115, 117, 109, 64, 36, 40, 71, 79, 84, 69, 83, 84, 83, 85, 77, 86, 69, 82, 83, 73, 79, 78, 41, 10, 101, 110, 100, 105, 102, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 117, 110, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 10, 117, 110, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 58, 32, 35, 35, 32, 85, 110, 105, 110, 115, 116, 97, 108, 108, 32, 103, 111, 32, 105, 110, 100, 105, 114, 101, 99, 116, 32, 100, 101, 112, 101, 110, 100, 101, 110, 99, 105, 101, 115, 10, 105, 102, 110, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 41, 41, 10, 9, 114, 109, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 47, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 10, 101, 110, 100, 105, 102, 10, 105, 102, 110, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 105, 109, 112, 111, 114, 116, 115, 41, 41, 10, 9, 114, 109, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 47, 103, 111, 105, 109, 112, 111, 114, 116, 115, 10, 101, 110, 100, 105, 102, 10, 105, 102, 110, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 120, 41, 41, 10, 9, 114, 109, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 47, 103, 111, 120, 10, 101, 110, 100, 105, 102, 10, 105, 102, 110, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 116, 101, 115, 116, 115, 117, 109, 41, 41, 10, 9, 114, 109, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 47, 103, 111, 116, 101, 115, 116, 115, 117, 109, 10, 101, 110, 100, 105, 102, 10}) + box.Add("/scaffoldings/golang/VERSION", []byte{48, 46, 48, 46, 49, 45, 100, 101, 118, 10}) + box.Add("/scaffoldings/golang/bin/.gitkeeper", []byte{}) + box.Add("/scaffoldings/golang/cmd/README.md", []byte{35, 32, 96, 47, 99, 109, 100, 96, 10, 10, 77, 97, 105, 110, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 115, 32, 102, 111, 114, 32, 116, 104, 105, 115, 32, 112, 114, 111, 106, 101, 99, 116, 46}) + box.Add("/scaffoldings/golang/cmd/cdk.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 99, 109, 100, 10, 10, 105, 109, 112, 111, 114, 116, 32, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 111, 98, 114, 97, 34, 10, 10, 118, 97, 114, 32, 40, 10, 9, 99, 100, 107, 73, 110, 105, 116, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 9, 85, 115, 101, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 99, 100, 107, 45, 105, 110, 105, 116, 34, 44, 10, 9, 9, 83, 104, 111, 114, 116, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 105, 110, 116, 101, 114, 110, 97, 108, 32, 117, 115, 101, 32, 111, 110, 108, 121, 34, 44, 10, 9, 9, 76, 111, 110, 103, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 96, 73, 110, 116, 101, 114, 110, 97, 108, 32, 99, 111, 109, 109, 97, 110, 100, 32, 116, 104, 97, 116, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 101, 115, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 116, 111, 32, 98, 101, 32, 117, 115, 101, 100, 32, 98, 121, 32, 116, 104, 101, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 67, 76, 73, 32, 97, 115, 32, 97, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 46, 96, 44, 10, 9, 9, 72, 105, 100, 100, 101, 110, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 68, 105, 115, 97, 98, 108, 101, 65, 117, 116, 111, 71, 101, 110, 84, 97, 103, 58, 32, 116, 114, 117, 101, 44, 10, 9, 9, 83, 105, 108, 101, 110, 99, 101, 69, 114, 114, 111, 114, 115, 58, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 82, 117, 110, 69, 58, 32, 102, 117, 110, 99, 40, 95, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 95, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 9, 9, 47, 47, 32, 84, 104, 105, 115, 32, 101, 118, 101, 110, 116, 32, 105, 115, 32, 111, 110, 108, 121, 32, 101, 120, 101, 99, 117, 116, 101, 100, 32, 97, 102, 116, 101, 114, 32, 116, 104, 105, 115, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 105, 115, 32, 105, 110, 115, 116, 97, 108, 108, 101, 100, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 85, 115, 101, 32, 116, 104, 105, 115, 32, 101, 118, 101, 110, 116, 32, 116, 111, 32, 100, 101, 112, 108, 111, 121, 32, 97, 110, 121, 32, 110, 101, 99, 101, 115, 115, 97, 114, 121, 32, 102, 105, 108, 101, 44, 32, 99, 97, 99, 104, 101, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 44, 32, 116, 111, 32, 105, 110, 115, 116, 97, 108, 108, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 10, 9, 9, 9, 47, 47, 32, 108, 105, 98, 114, 97, 114, 105, 101, 115, 32, 112, 97, 99, 107, 97, 103, 101, 100, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 101, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 46, 32, 65, 110, 121, 116, 104, 105, 110, 103, 32, 116, 104, 97, 116, 32, 121, 111, 117, 32, 110, 101, 101, 100, 32, 116, 111, 32, 100, 111, 32, 98, 101, 102, 111, 114, 101, 32, 116, 104, 101, 32, 117, 115, 101, 114, 10, 9, 9, 9, 47, 47, 32, 115, 116, 97, 114, 116, 115, 32, 101, 120, 101, 99, 117, 116, 105, 110, 103, 32, 99, 111, 109, 109, 97, 110, 100, 115, 46, 10, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 77, 111, 114, 101, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 97, 116, 58, 10, 9, 9, 9, 47, 47, 32, 61, 62, 32, 104, 116, 116, 112, 115, 58, 47, 47, 108, 97, 99, 101, 119, 111, 114, 107, 46, 97, 116, 108, 97, 115, 115, 105, 97, 110, 46, 110, 101, 116, 47, 108, 47, 99, 112, 47, 66, 109, 48, 90, 114, 109, 82, 71, 10, 9, 9, 9, 114, 101, 116, 117, 114, 110, 32, 110, 105, 108, 10, 9, 9, 125, 44, 10, 9, 125, 10, 10, 9, 99, 100, 107, 82, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 9, 85, 115, 101, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 99, 100, 107, 45, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 32, 60, 99, 117, 114, 114, 101, 110, 116, 95, 118, 101, 114, 115, 105, 111, 110, 62, 32, 60, 110, 101, 119, 95, 111, 114, 95, 111, 108, 100, 101, 114, 95, 118, 101, 114, 115, 105, 111, 110, 62, 34, 44, 10, 9, 9, 83, 104, 111, 114, 116, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 105, 110, 116, 101, 114, 110, 97, 108, 32, 117, 115, 101, 32, 111, 110, 108, 121, 34, 44, 10, 9, 9, 76, 111, 110, 103, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 96, 73, 110, 116, 101, 114, 110, 97, 108, 32, 99, 111, 109, 109, 97, 110, 100, 32, 116, 104, 97, 116, 32, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 115, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 119, 104, 101, 110, 32, 105, 116, 32, 105, 115, 32, 117, 112, 100, 97, 116, 101, 100, 32, 111, 114, 32, 100, 111, 119, 110, 103, 114, 97, 100, 101, 100, 46, 96, 44, 10, 9, 9, 72, 105, 100, 100, 101, 110, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 68, 105, 115, 97, 98, 108, 101, 65, 117, 116, 111, 71, 101, 110, 84, 97, 103, 58, 32, 116, 114, 117, 101, 44, 10, 9, 9, 83, 105, 108, 101, 110, 99, 101, 69, 114, 114, 111, 114, 115, 58, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 82, 117, 110, 69, 58, 32, 102, 117, 110, 99, 40, 95, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 95, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 9, 9, 47, 47, 32, 84, 104, 101, 114, 101, 32, 97, 114, 101, 32, 116, 119, 111, 32, 112, 108, 97, 99, 101, 115, 32, 119, 104, 101, 114, 101, 32, 116, 104, 105, 115, 32, 101, 118, 101, 110, 116, 32, 105, 115, 32, 117, 115, 101, 100, 59, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 32, 49, 41, 32, 87, 104, 101, 110, 32, 97, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 105, 115, 32, 117, 112, 100, 97, 116, 101, 100, 32, 97, 110, 100, 44, 10, 9, 9, 9, 47, 47, 32, 32, 50, 41, 32, 87, 104, 101, 110, 32, 97, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 105, 115, 32, 100, 111, 119, 110, 103, 114, 97, 100, 101, 100, 32, 116, 111, 32, 97, 110, 32, 111, 108, 100, 101, 114, 32, 118, 101, 114, 115, 105, 111, 110, 32, 40, 114, 111, 108, 108, 98, 97, 99, 107, 41, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 82, 111, 108, 108, 98, 97, 99, 107, 115, 32, 97, 114, 101, 32, 110, 101, 101, 100, 101, 100, 32, 109, 111, 115, 116, 108, 121, 32, 102, 111, 114, 32, 119, 104, 101, 110, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 115, 32, 97, 114, 101, 32, 117, 112, 100, 97, 116, 101, 100, 32, 116, 111, 32, 97, 32, 118, 101, 114, 115, 105, 111, 110, 32, 119, 105, 116, 104, 32, 97, 32, 98, 117, 103, 32, 111, 114, 10, 9, 9, 9, 47, 47, 32, 115, 111, 109, 101, 116, 104, 105, 110, 103, 32, 116, 104, 97, 116, 32, 112, 114, 101, 118, 101, 110, 116, 115, 32, 116, 104, 101, 32, 117, 115, 101, 114, 32, 116, 111, 32, 99, 111, 110, 116, 105, 110, 117, 101, 32, 117, 115, 105, 110, 103, 32, 116, 104, 101, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 112, 114, 111, 100, 117, 99, 116, 105, 118, 101, 108, 121, 46, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 78, 111, 116, 101, 32, 116, 104, 97, 116, 32, 119, 104, 101, 110, 32, 116, 104, 105, 115, 32, 108, 105, 102, 101, 99, 121, 99, 108, 101, 32, 101, 118, 101, 110, 116, 32, 105, 115, 32, 101, 120, 101, 99, 117, 116, 101, 100, 44, 32, 105, 116, 32, 114, 101, 99, 101, 105, 118, 101, 115, 32, 116, 119, 111, 32, 97, 114, 103, 117, 109, 101, 110, 116, 115, 44, 32, 116, 104, 101, 32, 99, 117, 114, 114, 101, 110, 116, 10, 9, 9, 9, 47, 47, 32, 118, 101, 114, 115, 105, 111, 110, 32, 97, 110, 100, 32, 116, 104, 101, 32, 118, 101, 114, 115, 105, 111, 110, 32, 116, 111, 32, 117, 112, 100, 97, 116, 101, 32, 111, 114, 32, 114, 111, 108, 108, 98, 97, 99, 107, 32, 116, 104, 101, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 46, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 77, 111, 114, 101, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 97, 116, 58, 10, 9, 9, 9, 47, 47, 32, 61, 62, 32, 104, 116, 116, 112, 115, 58, 47, 47, 108, 97, 99, 101, 119, 111, 114, 107, 46, 97, 116, 108, 97, 115, 115, 105, 97, 110, 46, 110, 101, 116, 47, 108, 47, 99, 112, 47, 66, 109, 48, 90, 114, 109, 82, 71, 10, 9, 9, 9, 114, 101, 116, 117, 114, 110, 32, 110, 105, 108, 10, 9, 9, 125, 44, 10, 9, 125, 10, 10, 9, 99, 100, 107, 67, 108, 101, 97, 110, 117, 112, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 9, 85, 115, 101, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 99, 100, 107, 45, 99, 108, 101, 97, 110, 117, 112, 34, 44, 10, 9, 9, 83, 104, 111, 114, 116, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 105, 110, 116, 101, 114, 110, 97, 108, 32, 117, 115, 101, 32, 111, 110, 108, 121, 34, 44, 10, 9, 9, 76, 111, 110, 103, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 96, 73, 110, 116, 101, 114, 110, 97, 108, 32, 99, 111, 109, 109, 97, 110, 100, 32, 116, 104, 97, 116, 32, 99, 108, 101, 97, 110, 115, 32, 97, 110, 121, 116, 104, 105, 110, 103, 32, 116, 104, 97, 116, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 100, 101, 112, 108, 111, 121, 101, 100, 32, 100, 117, 114, 105, 110, 103, 32, 99, 100, 107, 45, 105, 110, 105, 116, 32, 111, 114, 32, 99, 100, 107, 45, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 32, 101, 118, 101, 110, 116, 115, 46, 96, 44, 10, 9, 9, 72, 105, 100, 100, 101, 110, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 68, 105, 115, 97, 98, 108, 101, 65, 117, 116, 111, 71, 101, 110, 84, 97, 103, 58, 32, 116, 114, 117, 101, 44, 10, 9, 9, 83, 105, 108, 101, 110, 99, 101, 69, 114, 114, 111, 114, 115, 58, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 82, 117, 110, 69, 58, 32, 102, 117, 110, 99, 40, 95, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 95, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 9, 9, 47, 47, 32, 66, 101, 102, 111, 114, 101, 32, 116, 104, 105, 115, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 105, 115, 32, 114, 101, 109, 111, 118, 101, 100, 32, 40, 117, 110, 105, 110, 115, 116, 97, 108, 108, 101, 100, 41, 44, 32, 116, 104, 105, 115, 32, 108, 105, 102, 101, 99, 121, 99, 108, 101, 32, 101, 118, 101, 110, 116, 32, 105, 115, 32, 101, 120, 101, 99, 117, 116, 101, 100, 46, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 72, 101, 114, 101, 32, 105, 115, 32, 119, 104, 101, 114, 101, 32, 121, 111, 117, 32, 115, 104, 111, 117, 108, 100, 32, 112, 101, 114, 102, 111, 114, 109, 32, 97, 32, 99, 108, 101, 97, 110, 117, 112, 32, 111, 102, 32, 97, 110, 121, 116, 104, 105, 110, 103, 32, 116, 104, 97, 116, 32, 119, 97, 115, 32, 100, 101, 112, 108, 111, 121, 101, 100, 32, 100, 117, 114, 105, 110, 103, 32, 116, 104, 101, 10, 9, 9, 9, 47, 47, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 32, 40, 99, 100, 107, 45, 105, 110, 105, 116, 41, 32, 97, 110, 100, 47, 111, 114, 32, 116, 104, 101, 32, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 97, 116, 105, 111, 110, 32, 40, 99, 100, 107, 45, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 41, 32, 101, 118, 101, 110, 116, 115, 46, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 77, 111, 114, 101, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 97, 116, 58, 10, 9, 9, 9, 47, 47, 32, 61, 62, 32, 104, 116, 116, 112, 115, 58, 47, 47, 108, 97, 99, 101, 119, 111, 114, 107, 46, 97, 116, 108, 97, 115, 115, 105, 97, 110, 46, 110, 101, 116, 47, 108, 47, 99, 112, 47, 66, 109, 48, 90, 114, 109, 82, 71, 10, 9, 9, 9, 114, 101, 116, 117, 114, 110, 32, 110, 105, 108, 10, 9, 9, 125, 44, 10, 9, 125, 10, 41, 10, 10, 102, 117, 110, 99, 32, 105, 110, 105, 116, 40, 41, 32, 123, 10, 9, 47, 47, 32, 97, 100, 100, 32, 116, 104, 101, 32, 99, 111, 109, 109, 97, 110, 100, 115, 32, 102, 111, 114, 32, 116, 104, 101, 32, 67, 68, 75, 32, 108, 105, 102, 101, 99, 121, 99, 108, 101, 32, 101, 118, 101, 110, 116, 115, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 65, 100, 100, 67, 111, 109, 109, 97, 110, 100, 40, 99, 100, 107, 73, 110, 105, 116, 67, 109, 100, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 65, 100, 100, 67, 111, 109, 109, 97, 110, 100, 40, 99, 100, 107, 82, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 67, 109, 100, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 65, 100, 100, 67, 111, 109, 109, 97, 110, 100, 40, 99, 100, 107, 67, 108, 101, 97, 110, 117, 112, 67, 109, 100, 41, 10, 125, 10}) + box.Add("/scaffoldings/golang/cmd/placeholder.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 99, 109, 100, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 102, 109, 116, 34, 10, 10, 9, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 111, 98, 114, 97, 34, 10, 41, 10, 10, 118, 97, 114, 32, 40, 10, 9, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 9, 85, 115, 101, 58, 32, 32, 32, 34, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 34, 44, 10, 9, 9, 83, 104, 111, 114, 116, 58, 32, 34, 65, 32, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 32, 99, 111, 109, 109, 97, 110, 100, 34, 44, 10, 9, 9, 76, 111, 110, 103, 58, 32, 32, 96, 84, 104, 105, 115, 32, 105, 115, 32, 106, 117, 115, 116, 32, 97, 110, 32, 101, 120, 97, 109, 112, 108, 101, 32, 111, 102, 32, 97, 32, 99, 111, 109, 109, 97, 110, 100, 46, 96, 44, 10, 9, 9, 82, 117, 110, 69, 58, 32, 102, 117, 110, 99, 40, 95, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 95, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 9, 9, 102, 109, 116, 46, 80, 114, 105, 110, 116, 108, 110, 40, 34, 89, 111, 117, 114, 32, 110, 101, 119, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 39, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 39, 32, 105, 115, 32, 114, 101, 97, 100, 121, 33, 34, 41, 10, 9, 9, 9, 114, 101, 116, 117, 114, 110, 32, 110, 105, 108, 10, 9, 9, 125, 44, 10, 9, 125, 10, 41, 10, 10, 102, 117, 110, 99, 32, 105, 110, 105, 116, 40, 41, 32, 123, 10, 9, 47, 47, 32, 97, 100, 100, 32, 116, 104, 101, 32, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 32, 99, 111, 109, 109, 97, 110, 100, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 65, 100, 100, 67, 111, 109, 109, 97, 110, 100, 40, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 67, 109, 100, 41, 10, 125, 10}) + box.Add("/scaffoldings/golang/cmd/root.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 99, 109, 100, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 102, 109, 116, 34, 10, 9, 34, 111, 115, 34, 10, 10, 9, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 111, 98, 114, 97, 34, 10, 9, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 118, 105, 112, 101, 114, 34, 10, 41, 10, 10, 47, 47, 32, 114, 111, 111, 116, 67, 109, 100, 32, 114, 101, 112, 114, 101, 115, 101, 110, 116, 115, 32, 116, 104, 101, 32, 98, 97, 115, 101, 32, 99, 111, 109, 109, 97, 110, 100, 32, 119, 104, 101, 110, 32, 99, 97, 108, 108, 101, 100, 32, 119, 105, 116, 104, 111, 117, 116, 32, 97, 110, 121, 32, 115, 117, 98, 99, 111, 109, 109, 97, 110, 100, 115, 10, 118, 97, 114, 32, 114, 111, 111, 116, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 85, 115, 101, 58, 32, 32, 32, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 34, 44, 10, 9, 83, 104, 111, 114, 116, 58, 32, 34, 65, 32, 98, 114, 105, 101, 102, 32, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 32, 111, 102, 32, 121, 111, 117, 114, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 34, 44, 10, 9, 76, 111, 110, 103, 58, 32, 96, 65, 32, 108, 111, 110, 103, 101, 114, 32, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 32, 116, 104, 97, 116, 32, 115, 112, 97, 110, 115, 32, 109, 117, 108, 116, 105, 112, 108, 101, 32, 108, 105, 110, 101, 115, 32, 97, 110, 100, 32, 108, 105, 107, 101, 108, 121, 32, 99, 111, 110, 116, 97, 105, 110, 115, 10, 101, 120, 97, 109, 112, 108, 101, 115, 32, 97, 110, 100, 32, 117, 115, 97, 103, 101, 32, 111, 102, 32, 117, 115, 105, 110, 103, 32, 121, 111, 117, 114, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 46, 32, 70, 111, 114, 32, 101, 120, 97, 109, 112, 108, 101, 58, 10, 10, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 105, 115, 32, 97, 32, 115, 99, 97, 102, 102, 111, 108, 100, 105, 110, 103, 32, 102, 111, 114, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 67, 68, 75, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 115, 46, 32, 73, 116, 32, 104, 101, 108, 112, 115, 32, 100, 101, 118, 101, 108, 111, 112, 101, 114, 115, 10, 99, 114, 101, 97, 116, 101, 32, 110, 101, 119, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 115, 32, 102, 97, 115, 116, 32, 98, 121, 32, 97, 117, 116, 111, 103, 101, 110, 101, 114, 97, 116, 105, 110, 103, 32, 97, 32, 115, 107, 101, 108, 101, 116, 111, 110, 32, 119, 105, 116, 104, 32, 98, 111, 105, 108, 101, 114, 112, 108, 97, 116, 101, 32, 99, 111, 100, 101, 46, 10, 10, 84, 111, 32, 113, 117, 105, 99, 107, 108, 121, 32, 103, 101, 116, 32, 103, 111, 105, 110, 103, 32, 119, 105, 116, 104, 32, 116, 104, 105, 115, 32, 110, 101, 119, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 44, 32, 115, 116, 97, 114, 116, 32, 109, 111, 100, 105, 102, 121, 105, 110, 103, 32, 116, 104, 101, 32, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 32, 99, 111, 109, 109, 97, 110, 100, 58, 10, 10, 32, 32, 32, 32, 108, 97, 99, 101, 119, 111, 114, 107, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 10, 96, 44, 10, 10, 9, 47, 47, 32, 78, 79, 84, 69, 58, 32, 73, 102, 32, 121, 111, 117, 114, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 100, 111, 101, 115, 32, 78, 79, 84, 32, 110, 101, 101, 100, 32, 109, 117, 108, 116, 105, 112, 108, 101, 32, 99, 111, 109, 109, 97, 110, 100, 115, 44, 32, 117, 110, 99, 111, 109, 109, 101, 110, 116, 10, 9, 47, 47, 32, 32, 32, 32, 32, 32, 32, 116, 104, 105, 115, 32, 108, 105, 110, 101, 32, 97, 110, 100, 32, 114, 101, 109, 111, 118, 101, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 39, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 46, 103, 111, 39, 10, 9, 47, 47, 32, 82, 117, 110, 58, 32, 102, 117, 110, 99, 40, 99, 109, 100, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 97, 114, 103, 115, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 123, 125, 44, 10, 125, 10, 10, 47, 47, 32, 69, 120, 101, 99, 117, 116, 101, 32, 97, 100, 100, 115, 32, 97, 108, 108, 32, 99, 104, 105, 108, 100, 32, 99, 111, 109, 109, 97, 110, 100, 115, 32, 116, 111, 32, 116, 104, 101, 32, 114, 111, 111, 116, 32, 99, 111, 109, 109, 97, 110, 100, 32, 97, 110, 100, 32, 115, 101, 116, 115, 32, 102, 108, 97, 103, 115, 32, 97, 112, 112, 114, 111, 112, 114, 105, 97, 116, 101, 108, 121, 46, 10, 47, 47, 32, 84, 104, 105, 115, 32, 105, 115, 32, 99, 97, 108, 108, 101, 100, 32, 98, 121, 32, 109, 97, 105, 110, 46, 109, 97, 105, 110, 40, 41, 46, 32, 73, 116, 32, 111, 110, 108, 121, 32, 110, 101, 101, 100, 115, 32, 116, 111, 32, 104, 97, 112, 112, 101, 110, 32, 111, 110, 99, 101, 32, 116, 111, 32, 116, 104, 101, 32, 114, 111, 111, 116, 67, 109, 100, 46, 10, 102, 117, 110, 99, 32, 69, 120, 101, 99, 117, 116, 101, 40, 41, 32, 123, 10, 9, 101, 114, 114, 32, 58, 61, 32, 114, 111, 111, 116, 67, 109, 100, 46, 69, 120, 101, 99, 117, 116, 101, 40, 41, 10, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 111, 115, 46, 69, 120, 105, 116, 40, 49, 41, 10, 9, 125, 10, 125, 10, 10, 102, 117, 110, 99, 32, 105, 110, 105, 116, 40, 41, 32, 123, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 100, 101, 98, 117, 103, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 116, 117, 114, 110, 32, 111, 110, 32, 100, 101, 98, 117, 103, 32, 108, 111, 103, 103, 105, 110, 103, 34, 44, 10, 9, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 110, 111, 99, 111, 108, 111, 114, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 116, 117, 114, 110, 32, 111, 102, 102, 32, 99, 111, 108, 111, 114, 115, 34, 44, 10, 9, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 110, 111, 99, 97, 99, 104, 101, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 116, 117, 114, 110, 32, 111, 102, 102, 32, 99, 97, 99, 104, 105, 110, 103, 34, 44, 10, 9, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 110, 111, 110, 105, 110, 116, 101, 114, 97, 99, 116, 105, 118, 101, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 116, 117, 114, 110, 32, 111, 102, 102, 32, 105, 110, 116, 101, 114, 97, 99, 116, 105, 118, 101, 32, 109, 111, 100, 101, 32, 40, 100, 105, 115, 97, 98, 108, 101, 32, 115, 112, 105, 110, 110, 101, 114, 115, 44, 32, 112, 114, 111, 109, 112, 116, 115, 44, 32, 101, 116, 99, 46, 41, 34, 44, 10, 9, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 106, 115, 111, 110, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 115, 119, 105, 116, 99, 104, 32, 99, 111, 109, 109, 97, 110, 100, 115, 32, 111, 117, 116, 112, 117, 116, 32, 102, 114, 111, 109, 32, 104, 117, 109, 97, 110, 45, 114, 101, 97, 100, 97, 98, 108, 101, 32, 116, 111, 32, 106, 115, 111, 110, 32, 102, 111, 114, 109, 97, 116, 34, 44, 10, 9, 41, 10, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 100, 101, 98, 117, 103, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 100, 101, 98, 117, 103, 34, 41, 41, 41, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 110, 111, 99, 111, 108, 111, 114, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 110, 111, 99, 111, 108, 111, 114, 34, 41, 41, 41, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 110, 111, 99, 97, 99, 104, 101, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 110, 111, 99, 97, 99, 104, 101, 34, 41, 41, 41, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 110, 111, 110, 105, 110, 116, 101, 114, 97, 99, 116, 105, 118, 101, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 110, 111, 110, 105, 110, 116, 101, 114, 97, 99, 116, 105, 118, 101, 34, 41, 41, 41, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 106, 115, 111, 110, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 106, 115, 111, 110, 34, 41, 41, 41, 10, 10, 9, 47, 47, 32, 72, 101, 114, 101, 32, 121, 111, 117, 32, 119, 105, 108, 108, 32, 100, 101, 102, 105, 110, 101, 32, 121, 111, 117, 114, 32, 102, 108, 97, 103, 115, 32, 97, 110, 100, 32, 99, 111, 110, 102, 105, 103, 117, 114, 97, 116, 105, 111, 110, 32, 115, 101, 116, 116, 105, 110, 103, 115, 46, 10, 125, 10, 10, 47, 47, 32, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 32, 105, 115, 32, 97, 32, 115, 105, 109, 112, 108, 101, 32, 109, 97, 99, 114, 111, 32, 116, 111, 32, 99, 104, 101, 99, 107, 32, 71, 111, 108, 97, 110, 103, 32, 101, 114, 114, 111, 114, 115, 44, 32, 105, 102, 32, 116, 104, 101, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 101, 114, 114, 111, 114, 10, 47, 47, 32, 105, 115, 32, 110, 105, 108, 44, 32, 105, 116, 32, 100, 111, 101, 115, 110, 39, 116, 32, 100, 111, 32, 97, 110, 121, 116, 104, 105, 110, 103, 44, 32, 98, 117, 116, 32, 105, 102, 32, 116, 104, 101, 32, 101, 114, 114, 111, 114, 32, 104, 97, 115, 32, 115, 111, 109, 101, 116, 104, 105, 110, 103, 44, 32, 105, 116, 32, 112, 114, 105, 110, 116, 115, 32, 97, 10, 47, 47, 32, 87, 65, 82, 78, 73, 78, 71, 32, 109, 101, 115, 115, 97, 103, 101, 32, 116, 111, 32, 116, 104, 101, 32, 117, 115, 101, 114, 44, 32, 117, 115, 101, 102, 117, 108, 32, 102, 111, 114, 32, 116, 104, 111, 115, 101, 32, 99, 97, 115, 101, 115, 32, 119, 104, 101, 114, 101, 32, 119, 101, 32, 107, 110, 111, 119, 32, 116, 104, 101, 114, 101, 32, 119, 111, 110, 39, 116, 10, 47, 47, 32, 98, 101, 32, 97, 32, 112, 114, 111, 98, 108, 101, 109, 32, 98, 117, 116, 32, 116, 104, 101, 32, 108, 105, 110, 116, 101, 114, 32, 115, 116, 105, 108, 108, 32, 97, 115, 107, 115, 32, 116, 111, 32, 99, 104, 101, 99, 107, 32, 97, 108, 108, 32, 101, 114, 114, 111, 114, 115, 10, 102, 117, 110, 99, 32, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 102, 109, 116, 46, 70, 112, 114, 105, 110, 116, 102, 40, 111, 115, 46, 83, 116, 100, 101, 114, 114, 44, 32, 34, 87, 65, 82, 78, 32, 37, 115, 92, 110, 34, 44, 32, 101, 114, 114, 41, 10, 9, 125, 10, 125, 10}) + box.Add("/scaffoldings/golang/go.mod", []byte{109, 111, 100, 117, 108, 101, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 10, 10, 103, 111, 32, 49, 46, 50, 48, 10, 10, 114, 101, 113, 117, 105, 114, 101, 32, 40, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 111, 98, 114, 97, 32, 118, 49, 46, 54, 46, 49, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 118, 105, 112, 101, 114, 32, 118, 49, 46, 49, 51, 46, 48, 10, 41, 10, 10, 114, 101, 113, 117, 105, 114, 101, 32, 40, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 102, 115, 110, 111, 116, 105, 102, 121, 47, 102, 115, 110, 111, 116, 105, 102, 121, 32, 118, 49, 46, 53, 46, 52, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 104, 97, 115, 104, 105, 99, 111, 114, 112, 47, 104, 99, 108, 32, 118, 49, 46, 48, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 105, 110, 99, 111, 110, 115, 104, 114, 101, 118, 101, 97, 98, 108, 101, 47, 109, 111, 117, 115, 101, 116, 114, 97, 112, 32, 118, 49, 46, 48, 46, 49, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 109, 97, 103, 105, 99, 111, 110, 97, 105, 114, 47, 112, 114, 111, 112, 101, 114, 116, 105, 101, 115, 32, 118, 49, 46, 56, 46, 54, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 109, 105, 116, 99, 104, 101, 108, 108, 104, 47, 109, 97, 112, 115, 116, 114, 117, 99, 116, 117, 114, 101, 32, 118, 49, 46, 53, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 112, 101, 108, 108, 101, 116, 105, 101, 114, 47, 103, 111, 45, 116, 111, 109, 108, 32, 118, 49, 46, 57, 46, 53, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 112, 101, 108, 108, 101, 116, 105, 101, 114, 47, 103, 111, 45, 116, 111, 109, 108, 47, 118, 50, 32, 118, 50, 46, 48, 46, 53, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 97, 102, 101, 114, 111, 32, 118, 49, 46, 56, 46, 50, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 97, 115, 116, 32, 118, 49, 46, 53, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 106, 119, 97, 108, 116, 101, 114, 119, 101, 97, 116, 104, 101, 114, 109, 97, 110, 32, 118, 49, 46, 49, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 112, 102, 108, 97, 103, 32, 118, 49, 46, 48, 46, 53, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 117, 98, 111, 115, 105, 116, 111, 47, 103, 111, 116, 101, 110, 118, 32, 118, 49, 46, 52, 46, 49, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 120, 47, 115, 121, 115, 32, 118, 48, 46, 48, 46, 48, 45, 50, 48, 50, 50, 48, 53, 50, 48, 49, 53, 49, 51, 48, 50, 45, 98, 99, 50, 99, 56, 53, 97, 100, 97, 49, 48, 97, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 120, 47, 116, 101, 120, 116, 32, 118, 48, 46, 51, 46, 55, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 112, 107, 103, 46, 105, 110, 47, 105, 110, 105, 46, 118, 49, 32, 118, 49, 46, 54, 55, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 112, 107, 103, 46, 105, 110, 47, 121, 97, 109, 108, 46, 118, 50, 32, 118, 50, 46, 52, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 112, 107, 103, 46, 105, 110, 47, 121, 97, 109, 108, 46, 118, 51, 32, 118, 51, 46, 48, 46, 49, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 41, 10}) + box.Add("/scaffoldings/golang/internal/README.md", []byte{35, 32, 96, 47, 105, 110, 116, 101, 114, 110, 97, 108, 96, 10, 10, 80, 114, 105, 118, 97, 116, 101, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 32, 97, 110, 100, 32, 108, 105, 98, 114, 97, 114, 121, 32, 99, 111, 100, 101, 46, 32, 84, 104, 105, 115, 32, 105, 115, 32, 116, 104, 101, 32, 99, 111, 100, 101, 32, 121, 111, 117, 32, 100, 111, 110, 39, 116, 32, 119, 97, 110, 116, 32, 111, 116, 104, 101, 114, 115, 32, 105, 109, 112, 111, 114, 116, 105, 110, 103, 32, 105, 110, 32, 116, 104, 101, 105, 114, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 115, 32, 111, 114, 32, 108, 105, 98, 114, 97, 114, 105, 101, 115, 46}) + box.Add("/scaffoldings/golang/internal/logger/logger.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 108, 111, 103, 103, 101, 114, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 111, 115, 34, 10, 10, 9, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 47, 103, 111, 45, 115, 100, 107, 47, 108, 119, 108, 111, 103, 103, 101, 114, 34, 10, 41, 10, 10, 47, 47, 32, 76, 111, 103, 32, 97, 108, 108, 111, 119, 115, 32, 116, 104, 105, 115, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 116, 111, 32, 108, 101, 118, 101, 114, 97, 103, 101, 32, 111, 117, 114, 32, 71, 111, 45, 83, 68, 75, 32, 108, 119, 108, 111, 103, 103, 101, 114, 10, 47, 47, 10, 47, 47, 32, 69, 120, 97, 109, 112, 108, 101, 58, 10, 47, 47, 10, 47, 47, 9, 105, 109, 112, 111, 114, 116, 32, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 108, 111, 103, 103, 101, 114, 34, 10, 47, 47, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 73, 110, 102, 111, 40, 34, 97, 110, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 97, 108, 32, 109, 101, 115, 115, 97, 103, 101, 34, 41, 10, 47, 47, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 68, 101, 98, 117, 103, 40, 34, 97, 32, 100, 101, 98, 117, 103, 32, 109, 101, 115, 115, 97, 103, 101, 34, 41, 10, 47, 47, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 73, 110, 102, 111, 119, 40, 34, 105, 110, 102, 111, 32, 109, 101, 115, 115, 97, 103, 101, 32, 119, 105, 116, 104, 32, 118, 97, 114, 105, 97, 98, 108, 101, 115, 34, 44, 32, 34, 102, 111, 111, 34, 44, 32, 34, 98, 97, 114, 34, 41, 10, 118, 97, 114, 32, 76, 111, 103, 32, 61, 32, 108, 119, 108, 111, 103, 103, 101, 114, 46, 78, 101, 119, 40, 111, 115, 46, 71, 101, 116, 101, 110, 118, 40, 34, 76, 87, 95, 76, 79, 71, 34, 41, 41, 46, 83, 117, 103, 97, 114, 40, 41, 10}) + box.Add("/scaffoldings/golang/internal/metric/metric.go", []byte{112, 97, 99, 107, 97, 103, 101, 32, 109, 101, 116, 114, 105, 99, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 99, 111, 110, 116, 101, 120, 116, 34, 10, 9, 34, 111, 115, 34, 10, 10, 9, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 108, 111, 103, 103, 101, 114, 34, 10, 9, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 118, 101, 114, 115, 105, 111, 110, 34, 10, 10, 9, 99, 100, 107, 32, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 47, 103, 111, 45, 115, 100, 107, 47, 99, 108, 105, 47, 99, 100, 107, 47, 103, 111, 47, 112, 114, 111, 116, 111, 47, 118, 49, 34, 10, 9, 34, 103, 111, 111, 103, 108, 101, 46, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 103, 114, 112, 99, 34, 10, 9, 34, 103, 111, 111, 103, 108, 101, 46, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 103, 114, 112, 99, 47, 99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 115, 47, 105, 110, 115, 101, 99, 117, 114, 101, 34, 10, 41, 10, 10, 118, 97, 114, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 32, 99, 100, 107, 46, 67, 111, 114, 101, 67, 108, 105, 101, 110, 116, 10, 10, 102, 117, 110, 99, 32, 105, 110, 105, 116, 40, 41, 32, 123, 10, 9, 47, 47, 32, 83, 101, 116, 32, 117, 112, 32, 97, 32, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 32, 116, 111, 32, 116, 104, 101, 32, 67, 68, 75, 32, 115, 101, 114, 118, 101, 114, 10, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 73, 110, 102, 111, 119, 40, 34, 99, 111, 110, 110, 101, 99, 116, 105, 110, 103, 32, 116, 111, 32, 103, 82, 80, 67, 32, 115, 101, 114, 118, 101, 114, 34, 44, 32, 34, 97, 100, 100, 114, 101, 115, 115, 34, 44, 32, 111, 115, 46, 71, 101, 116, 101, 110, 118, 40, 34, 76, 87, 95, 67, 68, 75, 95, 84, 65, 82, 71, 69, 84, 34, 41, 41, 10, 9, 99, 111, 110, 110, 44, 32, 101, 114, 114, 32, 58, 61, 32, 103, 114, 112, 99, 46, 68, 105, 97, 108, 40, 111, 115, 46, 71, 101, 116, 101, 110, 118, 40, 34, 76, 87, 95, 67, 68, 75, 95, 84, 65, 82, 71, 69, 84, 34, 41, 44, 10, 9, 9, 47, 47, 32, 119, 101, 32, 97, 108, 108, 111, 119, 32, 105, 110, 115, 101, 99, 117, 114, 101, 32, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 115, 32, 115, 105, 110, 99, 101, 32, 119, 101, 32, 97, 114, 101, 32, 99, 111, 110, 110, 101, 99, 116, 105, 110, 103, 32, 116, 111, 32, 39, 108, 111, 99, 97, 108, 104, 111, 115, 116, 39, 10, 9, 9, 103, 114, 112, 99, 46, 87, 105, 116, 104, 84, 114, 97, 110, 115, 112, 111, 114, 116, 67, 114, 101, 100, 101, 110, 116, 105, 97, 108, 115, 40, 105, 110, 115, 101, 99, 117, 114, 101, 46, 78, 101, 119, 67, 114, 101, 100, 101, 110, 116, 105, 97, 108, 115, 40, 41, 41, 41, 10, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 67, 97, 110, 110, 111, 116, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 101, 32, 67, 68, 75, 32, 99, 108, 105, 101, 110, 116, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 32, 101, 114, 114, 46, 69, 114, 114, 111, 114, 40, 41, 41, 10, 9, 125, 32, 101, 108, 115, 101, 32, 123, 10, 9, 9, 99, 100, 107, 67, 108, 105, 101, 110, 116, 32, 61, 32, 99, 100, 107, 46, 78, 101, 119, 67, 111, 114, 101, 67, 108, 105, 101, 110, 116, 40, 99, 111, 110, 110, 41, 10, 9, 125, 10, 125, 10, 10, 102, 117, 110, 99, 32, 83, 101, 110, 100, 77, 101, 116, 114, 105, 99, 68, 97, 116, 97, 40, 102, 101, 97, 116, 117, 114, 101, 32, 115, 116, 114, 105, 110, 103, 44, 32, 100, 97, 116, 97, 32, 109, 97, 112, 91, 115, 116, 114, 105, 110, 103, 93, 115, 116, 114, 105, 110, 103, 41, 32, 123, 10, 9, 105, 102, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 32, 61, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 115, 101, 110, 100, 32, 116, 101, 108, 101, 109, 101, 116, 114, 121, 34, 44, 10, 9, 9, 9, 34, 116, 121, 112, 101, 34, 44, 32, 34, 100, 97, 116, 97, 34, 44, 10, 9, 9, 9, 34, 101, 114, 114, 111, 114, 34, 44, 32, 34, 99, 108, 105, 101, 110, 116, 32, 110, 111, 116, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 101, 100, 34, 44, 10, 9, 9, 41, 10, 9, 9, 114, 101, 116, 117, 114, 110, 10, 9, 125, 10, 10, 9, 47, 47, 32, 97, 100, 100, 32, 118, 101, 114, 115, 105, 111, 110, 10, 9, 100, 97, 116, 97, 91, 34, 118, 101, 114, 115, 105, 111, 110, 34, 93, 32, 61, 32, 118, 101, 114, 115, 105, 111, 110, 46, 86, 101, 114, 115, 105, 111, 110, 10, 10, 9, 103, 111, 32, 102, 117, 110, 99, 40, 41, 32, 123, 10, 9, 9, 95, 44, 32, 101, 114, 114, 32, 58, 61, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 46, 72, 111, 110, 101, 121, 118, 101, 110, 116, 40, 99, 111, 110, 116, 101, 120, 116, 46, 66, 97, 99, 107, 103, 114, 111, 117, 110, 100, 40, 41, 44, 32, 38, 99, 100, 107, 46, 72, 111, 110, 101, 121, 118, 101, 110, 116, 82, 101, 113, 117, 101, 115, 116, 123, 10, 9, 9, 9, 70, 101, 97, 116, 117, 114, 101, 58, 32, 102, 101, 97, 116, 117, 114, 101, 44, 32, 70, 101, 97, 116, 117, 114, 101, 68, 97, 116, 97, 58, 32, 100, 97, 116, 97, 44, 10, 9, 9, 125, 41, 10, 9, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 115, 101, 110, 100, 32, 116, 101, 108, 101, 109, 101, 116, 114, 121, 34, 44, 10, 9, 9, 9, 9, 34, 116, 121, 112, 101, 34, 44, 32, 34, 100, 97, 116, 97, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 32, 101, 114, 114, 46, 69, 114, 114, 111, 114, 40, 41, 44, 10, 9, 9, 9, 41, 10, 9, 9, 125, 10, 9, 125, 40, 41, 10, 125, 10, 10, 102, 117, 110, 99, 32, 83, 101, 110, 100, 77, 101, 116, 114, 105, 99, 69, 114, 114, 111, 114, 40, 101, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 105, 102, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 32, 61, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 115, 101, 110, 100, 32, 116, 101, 108, 101, 109, 101, 116, 114, 121, 34, 44, 10, 9, 9, 9, 34, 116, 121, 112, 101, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 10, 9, 9, 9, 34, 101, 114, 114, 111, 114, 34, 44, 32, 34, 99, 108, 105, 101, 110, 116, 32, 110, 111, 116, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 101, 100, 34, 44, 10, 9, 9, 41, 10, 9, 9, 114, 101, 116, 117, 114, 110, 10, 9, 125, 10, 10, 9, 103, 111, 32, 102, 117, 110, 99, 40, 41, 32, 123, 10, 9, 9, 95, 44, 32, 101, 114, 114, 32, 58, 61, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 46, 72, 111, 110, 101, 121, 118, 101, 110, 116, 40, 99, 111, 110, 116, 101, 120, 116, 46, 66, 97, 99, 107, 103, 114, 111, 117, 110, 100, 40, 41, 44, 32, 38, 99, 100, 107, 46, 72, 111, 110, 101, 121, 118, 101, 110, 116, 82, 101, 113, 117, 101, 115, 116, 123, 10, 9, 9, 9, 69, 114, 114, 111, 114, 58, 32, 101, 46, 69, 114, 114, 111, 114, 40, 41, 44, 10, 9, 9, 125, 41, 10, 9, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 115, 101, 110, 100, 32, 116, 101, 108, 101, 109, 101, 116, 114, 121, 34, 44, 10, 9, 9, 9, 9, 34, 116, 121, 112, 101, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 32, 101, 114, 114, 46, 69, 114, 114, 111, 114, 40, 41, 44, 10, 9, 9, 9, 41, 10, 9, 9, 125, 10, 9, 125, 40, 41, 10, 125, 10}) + box.Add("/scaffoldings/golang/internal/version/version.go", []byte{112, 97, 99, 107, 97, 103, 101, 32, 118, 101, 114, 115, 105, 111, 110, 10, 10, 118, 97, 114, 32, 40, 10, 9, 47, 47, 32, 65, 108, 108, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 32, 34, 117, 110, 107, 110, 111, 119, 110, 34, 32, 118, 97, 114, 105, 97, 98, 108, 101, 115, 32, 97, 114, 101, 32, 98, 101, 105, 110, 103, 32, 105, 110, 106, 101, 99, 116, 101, 100, 32, 97, 116, 10, 9, 47, 47, 32, 98, 117, 105, 108, 100, 32, 116, 105, 109, 101, 32, 118, 105, 97, 32, 116, 104, 101, 32, 99, 114, 111, 115, 115, 45, 112, 108, 97, 116, 102, 111, 114, 109, 32, 100, 105, 114, 101, 99, 116, 105, 118, 101, 32, 105, 110, 115, 105, 100, 101, 32, 116, 104, 101, 32, 77, 97, 107, 101, 102, 105, 108, 101, 10, 9, 47, 47, 10, 9, 47, 47, 32, 86, 101, 114, 115, 105, 111, 110, 32, 105, 115, 32, 116, 104, 101, 32, 115, 101, 109, 118, 101, 114, 32, 99, 111, 109, 105, 110, 103, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 86, 69, 82, 83, 73, 79, 78, 32, 102, 105, 108, 101, 10, 9, 86, 101, 114, 115, 105, 111, 110, 32, 61, 32, 34, 117, 110, 107, 110, 111, 119, 110, 34, 10, 10, 9, 47, 47, 32, 71, 105, 116, 83, 72, 65, 32, 105, 115, 32, 116, 104, 101, 32, 103, 105, 116, 32, 114, 101, 102, 32, 116, 104, 97, 116, 32, 116, 104, 101, 32, 99, 108, 105, 32, 119, 97, 115, 32, 98, 117, 105, 108, 116, 32, 102, 114, 111, 109, 10, 9, 71, 105, 116, 83, 72, 65, 32, 61, 32, 34, 117, 110, 107, 110, 111, 119, 110, 34, 10, 10, 9, 47, 47, 32, 66, 117, 105, 108, 100, 84, 105, 109, 101, 32, 105, 115, 32, 97, 32, 104, 117, 109, 97, 110, 45, 114, 101, 97, 100, 97, 98, 108, 101, 32, 116, 105, 109, 101, 32, 119, 104, 101, 110, 32, 116, 104, 101, 32, 99, 108, 105, 32, 119, 97, 115, 32, 98, 117, 105, 108, 116, 32, 97, 116, 10, 9, 66, 117, 105, 108, 100, 84, 105, 109, 101, 32, 61, 32, 34, 117, 110, 107, 110, 111, 119, 110, 34, 10, 41, 10}) + box.Add("/scaffoldings/golang/main.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 109, 97, 105, 110, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 99, 109, 100, 34, 10, 41, 10, 10, 102, 117, 110, 99, 32, 109, 97, 105, 110, 40, 41, 32, 123, 10, 9, 99, 109, 100, 46, 69, 120, 101, 99, 117, 116, 101, 40, 41, 10, 125, 10}) + box.Add("/scaffoldings/golang/pkg/README.md", []byte{35, 32, 96, 47, 112, 107, 103, 96, 10, 10, 76, 105, 98, 114, 97, 114, 121, 32, 99, 111, 100, 101, 32, 116, 104, 97, 116, 39, 115, 32, 111, 107, 32, 116, 111, 32, 117, 115, 101, 32, 98, 121, 32, 101, 120, 116, 101, 114, 110, 97, 108, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 115, 32, 40, 101, 46, 103, 46, 44, 32, 96, 47, 112, 107, 103, 47, 109, 121, 112, 117, 98, 108, 105, 99, 108, 105, 98, 96, 41, 46}) + box.Add("/scaffoldings/python/README.md", []byte{}) + box.Add("/scaffoldings/python/src/package/__init__.py", []byte{}) + box.Add("/scaffoldings/python/src/package/__main__.py", []byte{10, 100, 101, 102, 32, 109, 97, 105, 110, 40, 41, 58, 10, 32, 32, 32, 32, 112, 114, 105, 110, 116, 40, 34, 72, 101, 108, 108, 111, 32, 112, 121, 116, 104, 111, 110, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 34, 41, 10, 10, 10, 105, 102, 32, 95, 95, 110, 97, 109, 101, 95, 95, 32, 61, 61, 32, 34, 95, 95, 109, 97, 105, 110, 95, 95, 34, 58, 10, 32, 32, 32, 32, 109, 97, 105, 110, 40, 41, 10}) + box.Add("/vuln_assessment.html", []byte{60, 33, 68, 79, 67, 84, 89, 80, 69, 32, 104, 116, 109, 108, 62, 10, 60, 104, 116, 109, 108, 32, 108, 97, 110, 103, 61, 34, 101, 110, 34, 62, 10, 32, 32, 32, 32, 60, 104, 101, 97, 100, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 104, 116, 116, 112, 45, 101, 113, 117, 105, 118, 61, 34, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 116, 101, 120, 116, 47, 104, 116, 109, 108, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 85, 84, 70, 45, 56, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 104, 116, 116, 112, 45, 101, 113, 117, 105, 118, 61, 34, 88, 45, 85, 65, 45, 67, 111, 109, 112, 97, 116, 105, 98, 108, 101, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 73, 69, 61, 101, 100, 103, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 110, 97, 109, 101, 61, 34, 118, 105, 101, 119, 112, 111, 114, 116, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 119, 105, 100, 116, 104, 61, 100, 101, 118, 105, 99, 101, 45, 119, 105, 100, 116, 104, 44, 105, 110, 105, 116, 105, 97, 108, 45, 115, 99, 97, 108, 101, 61, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 110, 97, 109, 101, 61, 34, 103, 111, 111, 103, 108, 101, 34, 32, 118, 97, 108, 117, 101, 61, 34, 110, 111, 116, 114, 97, 110, 115, 108, 97, 116, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 110, 97, 109, 101, 61, 34, 102, 111, 114, 109, 97, 116, 45, 100, 101, 116, 101, 99, 116, 105, 111, 110, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 116, 101, 108, 101, 112, 104, 111, 110, 101, 61, 110, 111, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 110, 97, 109, 101, 61, 34, 114, 101, 102, 101, 114, 114, 101, 114, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 110, 111, 45, 114, 101, 102, 101, 114, 114, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 105, 116, 108, 101, 62, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 121, 32, 65, 115, 115, 101, 115, 115, 109, 101, 110, 116, 32, 45, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 83, 101, 99, 117, 114, 105, 116, 121, 60, 47, 116, 105, 116, 108, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 110, 107, 32, 114, 101, 108, 61, 34, 115, 104, 111, 114, 116, 99, 117, 116, 32, 105, 99, 111, 110, 34, 32, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 102, 97, 118, 105, 99, 111, 110, 46, 105, 99, 111, 63, 118, 61, 50, 34, 32, 116, 121, 112, 101, 61, 34, 105, 109, 97, 103, 101, 47, 120, 45, 105, 99, 111, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 116, 121, 108, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 102, 114, 111, 109, 58, 32, 47, 117, 105, 47, 115, 116, 121, 108, 101, 115, 104, 101, 101, 116, 115, 47, 100, 97, 121, 45, 97, 110, 116, 46, 99, 115, 115, 32, 42, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 97, 110, 116, 100, 45, 119, 97, 118, 101, 45, 115, 104, 97, 100, 111, 119, 45, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 115, 99, 114, 111, 108, 108, 45, 98, 97, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 118, 97, 114, 105, 97, 110, 116, 58, 116, 97, 98, 117, 108, 97, 114, 45, 110, 117, 109, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 46, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 115, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 34, 116, 110, 117, 109, 34, 44, 34, 116, 110, 117, 109, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 34, 116, 110, 117, 109, 34, 44, 34, 116, 110, 117, 109, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 99, 99, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 51, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 51, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 45, 115, 116, 114, 105, 110, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 45, 111, 114, 105, 103, 105, 110, 58, 48, 32, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 45, 111, 114, 105, 103, 105, 110, 58, 48, 32, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 116, 97, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 118, 97, 114, 105, 97, 110, 116, 58, 116, 97, 98, 117, 108, 97, 114, 45, 110, 117, 109, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 46, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 115, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 34, 116, 110, 117, 109, 34, 44, 34, 116, 110, 117, 109, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 34, 116, 110, 117, 109, 34, 44, 34, 116, 110, 117, 109, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 32, 56, 112, 120, 32, 48, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 32, 55, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 50, 53, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 114, 103, 98, 97, 40, 48, 44, 48, 44, 48, 44, 46, 48, 52, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 52, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 46, 51, 115, 32, 99, 117, 98, 105, 99, 45, 98, 101, 122, 105, 101, 114, 40, 46, 55, 56, 44, 46, 49, 52, 44, 46, 49, 53, 44, 46, 56, 54, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 116, 97, 103, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 46, 56, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 116, 97, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 114, 103, 98, 97, 40, 48, 44, 48, 44, 48, 44, 46, 54, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 102, 114, 111, 109, 58, 32, 47, 117, 105, 47, 115, 116, 121, 108, 101, 115, 104, 101, 101, 116, 115, 47, 100, 97, 121, 46, 99, 115, 115, 32, 42, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 34, 32, 105, 99, 111, 110, 45, 34, 93, 58, 110, 111, 116, 40, 46, 105, 99, 111, 110, 45, 114, 105, 103, 104, 116, 41, 58, 98, 101, 102, 111, 114, 101, 44, 91, 99, 108, 97, 115, 115, 94, 61, 105, 99, 111, 110, 45, 93, 58, 110, 111, 116, 40, 46, 105, 99, 111, 110, 45, 114, 105, 103, 104, 116, 41, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 34, 105, 99, 111, 110, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 112, 101, 97, 107, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 118, 97, 114, 105, 97, 110, 116, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 115, 109, 111, 111, 116, 104, 105, 110, 103, 58, 97, 110, 116, 105, 97, 108, 105, 97, 115, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 111, 122, 45, 111, 115, 120, 45, 102, 111, 110, 116, 45, 115, 109, 111, 111, 116, 104, 105, 110, 103, 58, 103, 114, 97, 121, 115, 99, 97, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 105, 114, 99, 108, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 48, 51, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 118, 117, 108, 110, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 49, 98, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 100, 111, 119, 110, 108, 111, 97, 100, 45, 112, 100, 102, 45, 49, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 50, 100, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 97, 114, 101, 116, 45, 100, 111, 119, 110, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 51, 53, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 114, 101, 99, 101, 110, 116, 115, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 54, 52, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 115, 101, 97, 114, 99, 104, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 54, 54, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 54, 55, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 100, 111, 119, 110, 108, 111, 97, 100, 45, 99, 115, 118, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 54, 57, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 56, 101, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 109, 101, 110, 117, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 57, 102, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 111, 108, 117, 109, 110, 115, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 98, 100, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 104, 101, 108, 112, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 99, 50, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 114, 101, 115, 105, 122, 101, 45, 102, 117, 108, 108, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 99, 97, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 114, 105, 103, 104, 116, 45, 100, 105, 114, 58, 110, 111, 116, 40, 46, 105, 99, 111, 110, 45, 114, 105, 103, 104, 116, 41, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 99, 100, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 115, 97, 110, 115, 45, 115, 101, 114, 105, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 116, 101, 120, 116, 45, 115, 105, 122, 101, 45, 97, 100, 106, 117, 115, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 101, 120, 116, 45, 115, 105, 122, 101, 45, 97, 100, 106, 117, 115, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 115, 105, 100, 101, 44, 102, 105, 103, 117, 114, 101, 44, 102, 111, 111, 116, 101, 114, 44, 104, 101, 97, 100, 101, 114, 44, 109, 97, 105, 110, 44, 115, 101, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 45, 115, 107, 105, 112, 58, 111, 98, 106, 101, 99, 116, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 58, 97, 99, 116, 105, 118, 101, 44, 97, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 50, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 46, 54, 55, 101, 109, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 109, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 118, 103, 58, 110, 111, 116, 40, 58, 114, 111, 111, 116, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 103, 117, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 49, 101, 109, 32, 52, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 117, 116, 116, 111, 110, 44, 105, 110, 112, 117, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 117, 116, 116, 111, 110, 44, 105, 110, 112, 117, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 117, 116, 116, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 117, 116, 116, 111, 110, 44, 104, 116, 109, 108, 32, 91, 116, 121, 112, 101, 61, 98, 117, 116, 116, 111, 110, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 97, 112, 112, 101, 97, 114, 97, 110, 99, 101, 58, 98, 117, 116, 116, 111, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 116, 121, 112, 101, 61, 98, 117, 116, 116, 111, 110, 93, 58, 58, 45, 109, 111, 122, 45, 102, 111, 99, 117, 115, 45, 105, 110, 110, 101, 114, 44, 98, 117, 116, 116, 111, 110, 58, 58, 45, 109, 111, 122, 45, 102, 111, 99, 117, 115, 45, 105, 110, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 116, 121, 112, 101, 61, 98, 117, 116, 116, 111, 110, 93, 58, 45, 109, 111, 122, 45, 102, 111, 99, 117, 115, 114, 105, 110, 103, 44, 98, 117, 116, 116, 111, 110, 58, 45, 109, 111, 122, 45, 102, 111, 99, 117, 115, 114, 105, 110, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 49, 112, 120, 32, 100, 111, 116, 116, 101, 100, 32, 66, 117, 116, 116, 111, 110, 84, 101, 120, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 116, 121, 112, 101, 61, 99, 104, 101, 99, 107, 98, 111, 120, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 58, 45, 119, 101, 98, 107, 105, 116, 45, 105, 110, 112, 117, 116, 45, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 46, 53, 52, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 110, 112, 117, 116, 58, 58, 45, 109, 115, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 44, 58, 97, 102, 116, 101, 114, 44, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 102, 111, 99, 117, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 100, 121, 44, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 44, 97, 115, 105, 100, 101, 44, 98, 111, 100, 121, 44, 100, 105, 118, 44, 102, 105, 103, 117, 114, 101, 44, 102, 111, 111, 116, 101, 114, 44, 104, 49, 44, 104, 52, 44, 104, 53, 44, 104, 101, 97, 100, 101, 114, 44, 104, 116, 109, 108, 44, 105, 109, 103, 44, 108, 97, 98, 101, 108, 44, 108, 105, 44, 112, 44, 115, 101, 99, 116, 105, 111, 110, 44, 115, 112, 97, 110, 44, 117, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 98, 97, 115, 101, 108, 105, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 114, 111, 111, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 49, 102, 50, 102, 52, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 97, 116, 116, 97, 99, 104, 109, 101, 110, 116, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 34, 76, 97, 116, 111, 34, 44, 34, 79, 112, 101, 110, 32, 83, 97, 110, 115, 34, 44, 34, 77, 101, 110, 108, 111, 34, 44, 115, 97, 110, 115, 45, 115, 101, 114, 105, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 120, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 115, 109, 111, 111, 116, 104, 105, 110, 103, 58, 115, 117, 98, 112, 105, 120, 101, 108, 45, 97, 110, 116, 105, 97, 108, 105, 97, 115, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 35, 109, 97, 105, 110, 95, 98, 111, 100, 121, 95, 119, 114, 97, 112, 112, 101, 114, 95, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 45, 119, 101, 98, 107, 105, 116, 45, 102, 117, 108, 108, 45, 115, 99, 114, 101, 101, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 118, 119, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 118, 104, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 65, 112, 112, 67, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 116, 116, 111, 109, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 58, 100, 105, 115, 97, 98, 108, 101, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 58, 97, 99, 116, 105, 118, 101, 44, 97, 58, 102, 111, 99, 117, 115, 44, 97, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 117, 110, 100, 101, 114, 108, 105, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 32, 58, 97, 99, 116, 105, 118, 101, 44, 97, 32, 58, 100, 105, 115, 97, 98, 108, 101, 100, 44, 97, 32, 58, 102, 111, 99, 117, 115, 44, 97, 32, 58, 104, 111, 118, 101, 114, 44, 97, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 97, 99, 116, 105, 118, 101, 44, 97, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 100, 105, 115, 97, 98, 108, 101, 100, 44, 97, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 102, 111, 99, 117, 115, 44, 97, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 48, 32, 46, 53, 114, 101, 109, 32, 50, 114, 101, 109, 32, 45, 46, 53, 114, 101, 109, 32, 114, 103, 98, 97, 40, 49, 52, 48, 44, 49, 53, 51, 44, 49, 53, 57, 44, 46, 55, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 112, 105, 116, 97, 108, 105, 122, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 99, 97, 112, 105, 116, 97, 108, 105, 122, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 101, 110, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 108, 105, 99, 107, 97, 98, 108, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 101, 108, 108, 105, 112, 115, 105, 115, 44, 46, 105, 110, 112, 117, 116, 45, 108, 97, 98, 101, 108, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 115, 112, 97, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 44, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 101, 108, 108, 105, 112, 115, 105, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 100, 105, 115, 97, 98, 108, 101, 100, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 102, 111, 99, 117, 115, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 104, 111, 118, 101, 114, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 100, 105, 115, 97, 98, 108, 101, 100, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 102, 111, 99, 117, 115, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 110, 111, 115, 99, 114, 111, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 105, 100, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 109, 97, 108, 108, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 116, 97, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 114, 103, 98, 97, 40, 48, 44, 48, 44, 48, 44, 46, 49, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 49, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 32, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 117, 112, 112, 101, 114, 99, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 101, 114, 116, 105, 99, 97, 108, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 99, 97, 112, 105, 116, 97, 108, 105, 122, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 101, 114, 116, 105, 99, 97, 108, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 49, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 101, 114, 116, 105, 99, 97, 108, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 46, 51, 55, 50, 53, 114, 101, 109, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 109, 97, 108, 108, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 104, 111, 118, 101, 114, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 97, 99, 116, 105, 118, 101, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 97, 99, 116, 105, 118, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 101, 108, 101, 99, 116, 101, 100, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 45, 93, 58, 98, 101, 102, 111, 114, 101, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 45, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 115, 101, 108, 102, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 49, 50, 53, 101, 109, 32, 46, 50, 53, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 97, 99, 116, 105, 118, 101, 44, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 58, 110, 111, 116, 40, 46, 110, 111, 45, 117, 110, 100, 101, 114, 108, 105, 110, 101, 41, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 117, 110, 100, 101, 114, 108, 105, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 105, 110, 118, 97, 108, 105, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 112, 117, 116, 45, 108, 97, 98, 101, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 49, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 111, 114, 109, 45, 99, 111, 110, 116, 114, 111, 108, 44, 46, 105, 110, 112, 117, 116, 45, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 49, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 53, 48, 109, 115, 32, 101, 97, 115, 101, 45, 111, 117, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 111, 114, 109, 45, 99, 111, 110, 116, 114, 111, 108, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 44, 46, 105, 110, 112, 117, 116, 45, 116, 101, 120, 116, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 112, 117, 116, 45, 116, 101, 120, 116, 46, 102, 111, 114, 109, 45, 99, 111, 110, 116, 114, 111, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 50, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 108, 97, 98, 101, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 32, 48, 32, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 108, 111, 103, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 51, 53, 54, 98, 98, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 99, 111, 108, 111, 114, 32, 46, 50, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 108, 111, 103, 111, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 99, 111, 108, 111, 114, 32, 46, 50, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 108, 111, 103, 111, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 116, 114, 111, 107, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 105, 99, 111, 110, 32, 46, 108, 111, 103, 111, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 49, 44, 104, 52, 44, 104, 53, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 46, 50, 53, 114, 101, 109, 32, 48, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 52, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 53, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 49, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 46, 114, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 46, 114, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 46, 53, 114, 101, 109, 32, 115, 111, 108, 105, 100, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 32, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 48, 32, 46, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 45, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 114, 105, 103, 104, 116, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 45, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 114, 111, 116, 97, 116, 101, 40, 45, 57, 48, 100, 101, 103, 41, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 114, 111, 116, 97, 116, 101, 40, 45, 57, 48, 100, 101, 103, 41, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 111, 118, 101, 114, 108, 97, 121, 84, 114, 105, 103, 103, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 111, 118, 101, 114, 108, 97, 121, 84, 114, 105, 103, 103, 101, 114, 32, 46, 99, 104, 105, 108, 100, 114, 101, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 115, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 58, 102, 105, 114, 115, 116, 45, 99, 104, 105, 108, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 108, 101, 102, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 48, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 97, 117, 116, 111, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 97, 117, 116, 111, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 99, 101, 110, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 115, 101, 108, 102, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 115, 112, 97, 99, 101, 45, 101, 118, 101, 110, 108, 121, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 108, 101, 102, 116, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 99, 111, 110, 116, 101, 110, 116, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 104, 115, 108, 97, 40, 48, 44, 48, 37, 44, 49, 48, 48, 37, 44, 46, 50, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 46, 51, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 116, 116, 111, 109, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 104, 111, 118, 101, 114, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 43, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 45, 98, 97, 114, 32, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 45, 98, 97, 114, 32, 46, 97, 99, 116, 105, 111, 110, 58, 102, 105, 114, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 50, 53, 114, 101, 109, 32, 48, 32, 48, 32, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 45, 98, 97, 114, 32, 46, 97, 99, 116, 105, 111, 110, 58, 108, 97, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 48, 32, 46, 50, 53, 114, 101, 109, 32, 46, 50, 53, 114, 101, 109, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 105, 103, 104, 116, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 45, 98, 97, 114, 32, 46, 97, 99, 116, 105, 111, 110, 62, 98, 117, 116, 116, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 117, 110, 115, 101, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 97, 99, 116, 105, 111, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 62, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 51, 56, 55, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 50, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 97, 99, 116, 105, 111, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 62, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 97, 110, 105, 109, 97, 116, 105, 111, 110, 58, 102, 97, 100, 101, 73, 110, 32, 46, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 110, 105, 109, 97, 116, 105, 111, 110, 58, 102, 97, 100, 101, 73, 110, 32, 46, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 111, 112, 97, 99, 105, 116, 121, 32, 46, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 97, 110, 105, 109, 97, 116, 105, 111, 110, 58, 102, 97, 100, 101, 73, 110, 32, 46, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 110, 105, 109, 97, 116, 105, 111, 110, 58, 102, 97, 100, 101, 73, 110, 32, 46, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 111, 112, 97, 99, 105, 116, 121, 32, 46, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 115, 116, 114, 101, 116, 99, 104, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 114, 101, 109, 32, 49, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 45, 102, 111, 111, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 102, 108, 101, 120, 45, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 102, 108, 101, 120, 45, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 45, 102, 111, 111, 116, 101, 114, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 102, 108, 101, 120, 45, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 45, 102, 111, 111, 116, 101, 114, 45, 105, 110, 102, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 102, 108, 101, 120, 45, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 45, 102, 111, 111, 116, 101, 114, 45, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 105, 116, 97, 108, 105, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 32, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 46, 109, 117, 108, 116, 105, 45, 102, 105, 108, 108, 32, 46, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 110, 99, 104, 111, 114, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 46, 109, 117, 108, 116, 105, 45, 102, 105, 108, 108, 32, 46, 104, 111, 114, 105, 122, 111, 110, 116, 97, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 116, 114, 111, 107, 101, 45, 111, 112, 97, 99, 105, 116, 121, 58, 46, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 116, 114, 111, 107, 101, 45, 119, 105, 100, 116, 104, 58, 49, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 46, 109, 117, 108, 116, 105, 45, 102, 105, 108, 108, 32, 46, 104, 111, 114, 105, 122, 111, 110, 116, 97, 108, 32, 46, 116, 105, 99, 107, 32, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 46, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 112, 97, 116, 104, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 104, 97, 112, 101, 45, 114, 101, 110, 100, 101, 114, 105, 110, 103, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 114, 101, 110, 100, 101, 114, 105, 110, 103, 58, 111, 112, 116, 105, 109, 105, 122, 101, 76, 101, 103, 105, 98, 105, 108, 105, 116, 121, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 114, 101, 99, 116, 46, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 114, 101, 99, 116, 46, 98, 97, 114, 46, 108, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 46, 55, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 114, 101, 99, 116, 46, 98, 97, 114, 58, 104, 111, 118, 101, 114, 58, 110, 111, 116, 40, 46, 100, 105, 115, 97, 98, 108, 101, 100, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 46, 55, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 114, 101, 99, 116, 46, 98, 97, 114, 58, 104, 111, 118, 101, 114, 58, 110, 111, 116, 40, 46, 100, 105, 115, 97, 98, 108, 101, 100, 41, 46, 108, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 45, 109, 115, 45, 103, 114, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 115, 58, 40, 49, 102, 114, 41, 91, 52, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 115, 58, 109, 105, 110, 109, 97, 120, 40, 49, 54, 114, 101, 109, 44, 97, 117, 116, 111, 41, 91, 53, 48, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 103, 114, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 116, 101, 109, 112, 108, 97, 116, 101, 45, 99, 111, 108, 117, 109, 110, 115, 58, 114, 101, 112, 101, 97, 116, 40, 52, 44, 91, 99, 111, 108, 93, 32, 109, 105, 110, 109, 97, 120, 40, 49, 48, 114, 101, 109, 44, 49, 102, 114, 41, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 97, 117, 116, 111, 45, 114, 111, 119, 115, 58, 109, 105, 110, 109, 97, 120, 40, 49, 54, 114, 101, 109, 44, 97, 117, 116, 111, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 103, 97, 112, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 97, 117, 116, 111, 45, 102, 108, 111, 119, 58, 100, 101, 110, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 115, 58, 40, 49, 102, 114, 41, 91, 50, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 116, 101, 109, 112, 108, 97, 116, 101, 45, 99, 111, 108, 117, 109, 110, 115, 58, 114, 101, 112, 101, 97, 116, 40, 50, 44, 91, 99, 111, 108, 93, 32, 109, 105, 110, 109, 97, 120, 40, 49, 50, 114, 101, 109, 44, 49, 102, 114, 41, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 115, 58, 40, 49, 102, 114, 41, 91, 51, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 115, 58, 40, 57, 46, 53, 114, 101, 109, 41, 91, 53, 48, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 116, 101, 109, 112, 108, 97, 116, 101, 45, 99, 111, 108, 117, 109, 110, 115, 58, 114, 101, 112, 101, 97, 116, 40, 51, 44, 91, 99, 111, 108, 93, 32, 109, 105, 110, 109, 97, 120, 40, 49, 51, 46, 56, 51, 51, 51, 51, 114, 101, 109, 44, 49, 102, 114, 41, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 97, 117, 116, 111, 45, 114, 111, 119, 115, 58, 57, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 115, 58, 40, 49, 102, 114, 41, 91, 50, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 116, 101, 109, 112, 108, 97, 116, 101, 45, 99, 111, 108, 117, 109, 110, 115, 58, 114, 101, 112, 101, 97, 116, 40, 50, 44, 91, 99, 111, 108, 93, 32, 109, 105, 110, 109, 97, 120, 40, 49, 51, 46, 56, 51, 51, 51, 51, 114, 101, 109, 44, 49, 102, 114, 41, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 115, 112, 97, 110, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 45, 115, 112, 97, 110, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 97, 108, 105, 103, 110, 58, 115, 116, 97, 114, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 45, 97, 108, 105, 103, 110, 58, 115, 116, 97, 114, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 114, 111, 119, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 46, 104, 45, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 45, 115, 112, 97, 110, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 114, 111, 119, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 49, 54, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 46, 119, 45, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 115, 112, 97, 110, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 46, 119, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 115, 112, 97, 110, 58, 51, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 51, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 102, 105, 114, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 50, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 51, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 51, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 52, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 52, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 53, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 54, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 54, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 46, 119, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 115, 112, 97, 110, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 103, 97, 112, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 103, 114, 105, 100, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 108, 105, 110, 101, 97, 114, 45, 103, 114, 97, 100, 105, 101, 110, 116, 40, 57, 48, 100, 101, 103, 44, 35, 50, 55, 52, 101, 56, 52, 44, 35, 50, 54, 57, 56, 99, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 53, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 62, 46, 97, 99, 116, 105, 111, 110, 58, 110, 111, 116, 40, 58, 102, 105, 114, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 62, 46, 97, 99, 116, 105, 111, 110, 32, 98, 117, 116, 116, 111, 110, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 62, 46, 97, 99, 116, 105, 111, 110, 32, 98, 117, 116, 116, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 99, 111, 108, 111, 114, 32, 46, 49, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 62, 46, 97, 99, 116, 105, 111, 110, 32, 98, 117, 116, 116, 111, 110, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 46, 49, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 58, 108, 97, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 108, 111, 103, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 108, 111, 103, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 88, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 88, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 108, 101, 102, 116, 32, 46, 52, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 50, 55, 57, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 62, 46, 116, 97, 98, 58, 110, 111, 116, 40, 58, 102, 105, 114, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 62, 46, 116, 97, 98, 62, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 45, 109, 101, 103, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 45, 109, 101, 103, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 97, 99, 116, 105, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 51, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 97, 99, 116, 105, 111, 110, 115, 32, 46, 97, 99, 116, 105, 111, 110, 44, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 108, 111, 103, 111, 44, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 44, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 117, 115, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 32, 49, 46, 53, 114, 101, 109, 32, 46, 50, 53, 114, 101, 109, 32, 49, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 51, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 32, 115, 101, 99, 116, 105, 111, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 104, 101, 105, 103, 104, 116, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 98, 111, 116, 116, 111, 109, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 62, 46, 105, 110, 102, 111, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 46, 51, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 104, 101, 105, 103, 104, 116, 58, 50, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 98, 111, 116, 116, 111, 109, 58, 46, 51, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 121, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 52, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 90, 40, 48, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 90, 40, 48, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 97, 99, 116, 105, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 46, 55, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 97, 99, 116, 105, 111, 110, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 54, 50, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 115, 104, 97, 100, 111, 119, 58, 48, 32, 48, 32, 46, 48, 54, 50, 53, 114, 101, 109, 32, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 115, 104, 97, 100, 111, 119, 58, 48, 32, 48, 32, 46, 48, 54, 50, 53, 114, 101, 109, 32, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 58, 104, 111, 118, 101, 114, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 58, 97, 99, 116, 105, 118, 101, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 58, 101, 109, 112, 116, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 50, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 98, 111, 100, 121, 45, 102, 111, 111, 116, 101, 114, 45, 111, 117, 116, 115, 105, 100, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 102, 108, 101, 120, 45, 115, 116, 97, 114, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 115, 111, 108, 105, 100, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 48, 32, 48, 32, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 50, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 32, 46, 116, 97, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 99, 111, 108, 111, 114, 32, 46, 50, 53, 115, 44, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 58, 97, 99, 116, 105, 118, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 58, 58, 45, 109, 111, 122, 45, 115, 101, 108, 101, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 46, 115, 101, 108, 101, 99, 116, 101, 100, 44, 46, 116, 97, 98, 58, 58, 115, 101, 108, 101, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 58, 58, 45, 109, 111, 122, 45, 115, 101, 108, 101, 99, 116, 105, 111, 110, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 114, 103, 98, 97, 40, 50, 53, 53, 44, 49, 56, 48, 44, 52, 54, 44, 46, 49, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 46, 115, 101, 108, 101, 99, 116, 101, 100, 58, 104, 111, 118, 101, 114, 44, 46, 116, 97, 98, 58, 58, 115, 101, 108, 101, 99, 116, 105, 111, 110, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 114, 103, 98, 97, 40, 50, 53, 53, 44, 49, 56, 48, 44, 52, 54, 44, 46, 49, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 117, 115, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 108, 101, 102, 116, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 49, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 49, 48, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 32, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 117, 115, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 108, 101, 102, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 105, 110, 102, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 110, 97, 109, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 101, 108, 108, 105, 112, 115, 105, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 110, 97, 109, 101, 32, 46, 97, 99, 99, 111, 117, 110, 116, 45, 110, 97, 109, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 110, 97, 109, 101, 32, 46, 97, 99, 99, 111, 117, 110, 116, 45, 110, 97, 109, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 114, 111, 108, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 98, 111, 116, 116, 111, 109, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 101, 108, 108, 105, 112, 115, 105, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 105, 109, 97, 103, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 105, 109, 97, 103, 101, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 32, 50, 114, 101, 109, 32, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 46, 56, 55, 53, 114, 101, 109, 32, 52, 46, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 43, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 104, 101, 105, 103, 104, 116, 58, 50, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 43, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 46, 56, 55, 53, 114, 101, 109, 32, 49, 46, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 46, 118, 105, 101, 119, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 46, 118, 105, 101, 119, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 52, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 50, 48, 52, 56, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 52, 48, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 116, 97, 98, 108, 101, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 104, 101, 97, 100, 101, 114, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 49, 54, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 32, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 115, 99, 114, 111, 108, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 49, 114, 101, 109, 41, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 44, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 46, 54, 50, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 49, 112, 120, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 104, 101, 97, 100, 101, 114, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 118, 105, 101, 119, 45, 114, 101, 112, 111, 114, 116, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 98, 97, 114, 45, 99, 104, 97, 114, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 55, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 118, 105, 101, 119, 45, 114, 101, 112, 111, 114, 116, 32, 46, 118, 105, 101, 119, 45, 99, 104, 97, 114, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 100, 114, 111, 112, 100, 111, 119, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 50, 52, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 100, 114, 111, 112, 100, 111, 119, 110, 32, 46, 83, 101, 108, 101, 99, 116, 45, 118, 97, 108, 117, 101, 45, 108, 97, 98, 101, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 112, 114, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 114, 111, 119, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 98, 111, 116, 116, 111, 109, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 50, 52, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 118, 105, 101, 119, 45, 100, 101, 116, 97, 105, 108, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 46, 53, 114, 101, 109, 32, 48, 32, 49, 46, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 118, 105, 101, 119, 45, 100, 101, 116, 97, 105, 108, 115, 32, 46, 118, 117, 108, 110, 45, 115, 101, 97, 114, 99, 104, 45, 98, 117, 116, 116, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 49, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 51, 45, 98, 111, 114, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 51, 53, 54, 98, 98, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 50, 45, 98, 111, 114, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 97, 51, 98, 98, 100, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 51, 45, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 51, 53, 54, 98, 98, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 50, 45, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 97, 51, 98, 98, 100, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 51, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 51, 55, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 114, 111, 119, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 54, 53, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 50, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 55, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 95, 95, 99, 104, 101, 99, 107, 98, 111, 120, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 95, 95, 99, 104, 101, 99, 107, 98, 111, 120, 32, 105, 110, 112, 117, 116, 91, 116, 121, 112, 101, 61, 99, 104, 101, 99, 107, 98, 111, 120, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 32, 49, 46, 53, 114, 101, 109, 32, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 101, 56, 101, 97, 101, 98, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 104, 101, 97, 100, 101, 114, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 117, 112, 112, 101, 114, 99, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 46, 118, 117, 108, 110, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 115, 112, 97, 99, 101, 45, 98, 101, 116, 119, 101, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 55, 56, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 46, 118, 117, 108, 110, 45, 104, 101, 97, 100, 101, 114, 46, 112, 97, 100, 100, 105, 110, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 55, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 55, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 43, 46, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 51, 46, 55, 53, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 51, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 97, 103, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 52, 46, 50, 53, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 52, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 114, 111, 111, 116, 32, 46, 112, 97, 103, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 121, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 55, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 97, 103, 101, 46, 99, 111, 109, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 121, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 101, 108, 108, 105, 112, 115, 105, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 32, 45, 119, 101, 98, 107, 105, 116, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 108, 105, 110, 101, 45, 99, 108, 97, 109, 112, 58, 32, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 98, 111, 120, 45, 111, 114, 105, 101, 110, 116, 58, 32, 118, 101, 114, 116, 105, 99, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 111, 114, 100, 45, 98, 114, 101, 97, 107, 58, 32, 98, 114, 101, 97, 107, 45, 97, 108, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 32, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 91, 99, 108, 97, 115, 115, 94, 61, 67, 97, 114, 100, 71, 114, 105, 100, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 97, 117, 116, 111, 45, 114, 111, 119, 115, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 99, 97, 114, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 99, 97, 114, 100, 32, 46, 97, 99, 116, 105, 111, 110, 44, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 99, 97, 114, 100, 32, 104, 52, 44, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 99, 97, 114, 100, 32, 115, 101, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 52, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 116, 97, 103, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 102, 108, 111, 119, 58, 114, 111, 119, 32, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 116, 97, 103, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 97, 110, 116, 45, 116, 97, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 53, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 98, 97, 99, 107, 102, 97, 99, 101, 45, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 102, 97, 99, 101, 45, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 62, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 62, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 46, 116, 111, 111, 108, 116, 105, 112, 45, 99, 111, 108, 117, 109, 110, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 109, 111, 117, 115, 101, 65, 114, 101, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 101, 119, 45, 114, 101, 115, 105, 122, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 45, 46, 51, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 105, 103, 104, 116, 45, 115, 116, 121, 108, 101, 58, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 105, 103, 104, 116, 45, 119, 105, 100, 116, 104, 58, 49, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 104, 105, 100, 100, 101, 110, 69, 108, 101, 109, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 46, 54, 50, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 49, 112, 120, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 115, 67, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 98, 111, 100, 121, 82, 111, 119, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 114, 103, 98, 97, 40, 48, 44, 48, 44, 48, 44, 46, 48, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 87, 114, 97, 112, 112, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 48, 50, 56, 52, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 45, 46, 48, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 46, 48, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 100, 105, 118, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 46, 55, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 115, 112, 97, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 98, 111, 116, 116, 111, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 115, 112, 97, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 46, 53, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 115, 112, 97, 110, 58, 108, 97, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 49, 46, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 97, 115, 66, 111, 116, 116, 111, 109, 66, 111, 114, 100, 101, 114, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 48, 32, 49, 112, 120, 32, 48, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 115, 67, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 45, 49, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 109, 117, 108, 116, 105, 45, 98, 117, 116, 116, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 114, 111, 119, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 114, 105, 103, 104, 116, 45, 98, 117, 116, 116, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 51, 56, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 102, 111, 111, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 98, 117, 116, 116, 111, 110, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 116, 114, 97, 110, 115, 102, 111, 114, 109, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 116, 114, 97, 110, 115, 102, 111, 114, 109, 32, 46, 50, 53, 115, 44, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 98, 111, 108, 100, 45, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 50, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 110, 112, 117, 116, 91, 116, 121, 112, 101, 61, 99, 104, 101, 99, 107, 98, 111, 120, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 46, 104, 97, 115, 45, 118, 97, 108, 117, 101, 46, 83, 101, 108, 101, 99, 116, 45, 45, 115, 105, 110, 103, 108, 101, 62, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 32, 46, 83, 101, 108, 101, 99, 116, 45, 118, 97, 108, 117, 101, 32, 46, 83, 101, 108, 101, 99, 116, 45, 118, 97, 108, 117, 101, 45, 108, 97, 98, 101, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 32, 105, 110, 112, 117, 116, 58, 58, 45, 109, 115, 45, 99, 108, 101, 97, 114, 44, 46, 83, 101, 108, 101, 99, 116, 32, 105, 110, 112, 117, 116, 58, 58, 45, 109, 115, 45, 114, 101, 118, 101, 97, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 97, 114, 114, 111, 119, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 46, 51, 49, 50, 53, 114, 101, 109, 32, 46, 51, 49, 50, 53, 114, 101, 109, 32, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 97, 114, 114, 111, 119, 45, 122, 111, 110, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 116, 97, 98, 108, 101, 45, 99, 101, 108, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 51, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 46, 53, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 116, 97, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 108, 97, 112, 115, 101, 58, 115, 101, 112, 97, 114, 97, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 32, 35, 98, 97, 99, 50, 99, 53, 32, 35, 101, 56, 101, 97, 101, 98, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 49, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 112, 97, 99, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 48, 32, 49, 112, 120, 32, 48, 32, 114, 103, 98, 97, 40, 50, 44, 51, 50, 44, 52, 52, 44, 46, 48, 54, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 62, 58, 108, 97, 115, 116, 45, 99, 104, 105, 108, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 51, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 32, 46, 83, 101, 108, 101, 99, 116, 45, 105, 110, 112, 117, 116, 58, 102, 111, 99, 117, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 105, 110, 112, 117, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 108, 101, 102, 116, 58, 46, 54, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 54, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 105, 110, 112, 117, 116, 62, 105, 110, 112, 117, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 110, 111, 110, 101, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 46, 48, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 32, 48, 32, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 97, 112, 112, 101, 97, 114, 97, 110, 99, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 109, 117, 108, 116, 105, 45, 118, 97, 108, 117, 101, 45, 119, 114, 97, 112, 112, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 54, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 45, 115, 105, 110, 103, 108, 101, 62, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 32, 46, 83, 101, 108, 101, 99, 116, 45, 118, 97, 108, 117, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 50, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 108, 101, 102, 116, 58, 46, 54, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 54, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 101, 108, 108, 105, 112, 115, 105, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 116, 116, 111, 109, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 32, 46, 83, 101, 108, 101, 99, 116, 45, 97, 114, 114, 111, 119, 45, 122, 111, 110, 101, 58, 104, 111, 118, 101, 114, 62, 46, 83, 101, 108, 101, 99, 116, 45, 97, 114, 114, 111, 119, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 45, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 101, 102, 97, 117, 108, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 99, 114, 105, 116, 105, 99, 97, 108, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 98, 56, 48, 99, 48, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 98, 56, 48, 99, 48, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 104, 105, 103, 104, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 49, 50, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 102, 102, 49, 50, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 109, 101, 100, 105, 117, 109, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 97, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 102, 102, 97, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 108, 111, 119, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 100, 49, 53, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 102, 102, 100, 49, 53, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 105, 110, 102, 111, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 44, 46, 105, 110, 102, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 32, 46, 105, 110, 102, 111, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 102, 111, 58, 104, 111, 118, 101, 114, 44, 98, 117, 116, 116, 111, 110, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 102, 111, 58, 97, 99, 116, 105, 118, 101, 44, 98, 117, 116, 116, 111, 110, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 97, 99, 116, 105, 118, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 49, 101, 55, 54, 57, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 108, 101, 114, 116, 45, 105, 110, 102, 111, 44, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 58, 104, 111, 118, 101, 114, 44, 98, 117, 116, 116, 111, 110, 46, 97, 108, 101, 114, 116, 45, 105, 110, 102, 111, 58, 104, 111, 118, 101, 114, 44, 98, 117, 116, 116, 111, 110, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 50, 54, 57, 56, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 54, 57, 56, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 58, 97, 99, 116, 105, 118, 101, 44, 98, 117, 116, 116, 111, 110, 46, 97, 108, 101, 114, 116, 45, 105, 110, 102, 111, 58, 97, 99, 116, 105, 118, 101, 44, 98, 117, 116, 116, 111, 110, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 58, 97, 99, 116, 105, 118, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 102, 114, 111, 109, 58, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 101, 98, 45, 115, 100, 107, 46, 97, 112, 116, 114, 105, 110, 115, 105, 99, 46, 99, 111, 109, 47, 115, 116, 121, 108, 101, 46, 99, 115, 115, 63, 97, 61, 65, 80, 45, 70, 73, 85, 69, 74, 71, 90, 75, 52, 73, 90, 77, 45, 50, 32, 42, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 112, 116, 45, 103, 117, 105, 100, 101, 45, 111, 118, 101, 114, 108, 97, 121, 45, 116, 111, 112, 44, 46, 97, 112, 116, 45, 103, 117, 105, 100, 101, 45, 111, 118, 101, 114, 108, 97, 121, 45, 98, 111, 116, 116, 111, 109, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 49, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 57, 57, 57, 57, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 112, 116, 45, 103, 117, 105, 100, 101, 45, 111, 118, 101, 114, 108, 97, 121, 45, 108, 101, 102, 116, 44, 46, 97, 112, 116, 45, 103, 117, 105, 100, 101, 45, 111, 118, 101, 114, 108, 97, 121, 45, 114, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 49, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 57, 57, 57, 57, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 107, 101, 121, 102, 114, 97, 109, 101, 115, 32, 42, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 45, 119, 101, 98, 107, 105, 116, 45, 107, 101, 121, 102, 114, 97, 109, 101, 115, 32, 102, 97, 100, 101, 73, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 48, 37, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 107, 101, 121, 102, 114, 97, 109, 101, 115, 32, 102, 97, 100, 101, 73, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 48, 37, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 46, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 116, 116, 111, 109, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 102, 111, 110, 116, 102, 97, 99, 101, 115, 32, 42, 47, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 34, 105, 99, 111, 110, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 101, 111, 116, 63, 52, 56, 51, 48, 49, 50, 51, 56, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 101, 111, 116, 63, 52, 56, 51, 48, 49, 50, 51, 56, 35, 105, 101, 102, 105, 120, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 101, 109, 98, 101, 100, 100, 101, 100, 45, 111, 112, 101, 110, 116, 121, 112, 101, 34, 41, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 119, 111, 102, 102, 50, 63, 52, 56, 51, 48, 49, 50, 51, 56, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 119, 111, 102, 102, 50, 34, 41, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 119, 111, 102, 102, 63, 52, 56, 51, 48, 49, 50, 51, 56, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 119, 111, 102, 102, 34, 41, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 116, 116, 102, 63, 52, 56, 51, 48, 49, 50, 51, 56, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 116, 114, 117, 101, 116, 121, 112, 101, 34, 41, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 115, 118, 103, 63, 52, 56, 51, 48, 49, 50, 51, 56, 35, 105, 99, 111, 110, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 115, 118, 103, 34, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 57, 119, 52, 66, 77, 85, 84, 80, 72, 104, 55, 85, 83, 83, 119, 97, 80, 71, 81, 51, 113, 53, 100, 48, 78, 55, 119, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 57, 119, 52, 66, 77, 85, 84, 80, 72, 104, 55, 85, 83, 83, 119, 105, 80, 71, 81, 51, 113, 53, 100, 48, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 121, 119, 52, 66, 77, 85, 84, 80, 72, 106, 120, 65, 119, 88, 105, 87, 116, 70, 67, 102, 81, 55, 65, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 121, 119, 52, 66, 77, 85, 84, 80, 72, 106, 120, 52, 119, 88, 105, 87, 116, 70, 67, 99, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 57, 119, 52, 66, 77, 85, 84, 80, 72, 104, 54, 85, 86, 83, 119, 97, 80, 71, 81, 51, 113, 53, 100, 48, 78, 55, 119, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 57, 119, 52, 66, 77, 85, 84, 80, 72, 104, 54, 85, 86, 83, 119, 105, 80, 71, 81, 51, 113, 53, 100, 48, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 88, 45, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 54, 48, 45, 48, 53, 50, 70, 44, 32, 85, 43, 49, 67, 56, 48, 45, 49, 67, 56, 56, 44, 32, 85, 43, 50, 48, 66, 52, 44, 32, 85, 43, 50, 68, 69, 48, 45, 50, 68, 70, 70, 44, 32, 85, 43, 65, 54, 52, 48, 45, 65, 54, 57, 70, 44, 32, 85, 43, 70, 69, 50, 69, 45, 70, 69, 50, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 86, 117, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 48, 48, 45, 48, 52, 53, 70, 44, 32, 85, 43, 48, 52, 57, 48, 45, 48, 52, 57, 49, 44, 32, 85, 43, 48, 52, 66, 48, 45, 48, 52, 66, 49, 44, 32, 85, 43, 50, 49, 49, 54, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 88, 117, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 49, 70, 48, 48, 45, 49, 70, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 85, 101, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 51, 55, 48, 45, 48, 51, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 88, 101, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 50, 45, 48, 49, 48, 51, 44, 32, 85, 43, 48, 49, 49, 48, 45, 48, 49, 49, 49, 44, 32, 85, 43, 48, 49, 50, 56, 45, 48, 49, 50, 57, 44, 32, 85, 43, 48, 49, 54, 56, 45, 48, 49, 54, 57, 44, 32, 85, 43, 48, 49, 65, 48, 45, 48, 49, 65, 49, 44, 32, 85, 43, 48, 49, 65, 70, 45, 48, 49, 66, 48, 44, 32, 85, 43, 49, 69, 65, 48, 45, 49, 69, 70, 57, 44, 32, 85, 43, 50, 48, 65, 66, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 88, 79, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 85, 117, 104, 112, 75, 75, 83, 84, 106, 119, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 87, 74, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 54, 48, 45, 48, 53, 50, 70, 44, 32, 85, 43, 49, 67, 56, 48, 45, 49, 67, 56, 56, 44, 32, 85, 43, 50, 48, 66, 52, 44, 32, 85, 43, 50, 68, 69, 48, 45, 50, 68, 70, 70, 44, 32, 85, 43, 65, 54, 52, 48, 45, 65, 54, 57, 70, 44, 32, 85, 43, 70, 69, 50, 69, 45, 70, 69, 50, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 85, 90, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 48, 48, 45, 48, 52, 53, 70, 44, 32, 85, 43, 48, 52, 57, 48, 45, 48, 52, 57, 49, 44, 32, 85, 43, 48, 52, 66, 48, 45, 48, 52, 66, 49, 44, 32, 85, 43, 50, 49, 49, 54, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 87, 90, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 49, 70, 48, 48, 45, 49, 70, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 86, 112, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 51, 55, 48, 45, 48, 51, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 87, 112, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 50, 45, 48, 49, 48, 51, 44, 32, 85, 43, 48, 49, 49, 48, 45, 48, 49, 49, 49, 44, 32, 85, 43, 48, 49, 50, 56, 45, 48, 49, 50, 57, 44, 32, 85, 43, 48, 49, 54, 56, 45, 48, 49, 54, 57, 44, 32, 85, 43, 48, 49, 65, 48, 45, 48, 49, 65, 49, 44, 32, 85, 43, 48, 49, 65, 70, 45, 48, 49, 66, 48, 44, 32, 85, 43, 49, 69, 65, 48, 45, 49, 69, 70, 57, 44, 32, 85, 43, 50, 48, 65, 66, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 87, 53, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 86, 90, 48, 98, 102, 56, 112, 107, 65, 103, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 88, 45, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 54, 48, 45, 48, 53, 50, 70, 44, 32, 85, 43, 49, 67, 56, 48, 45, 49, 67, 56, 56, 44, 32, 85, 43, 50, 48, 66, 52, 44, 32, 85, 43, 50, 68, 69, 48, 45, 50, 68, 70, 70, 44, 32, 85, 43, 65, 54, 52, 48, 45, 65, 54, 57, 70, 44, 32, 85, 43, 70, 69, 50, 69, 45, 70, 69, 50, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 86, 117, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 48, 48, 45, 48, 52, 53, 70, 44, 32, 85, 43, 48, 52, 57, 48, 45, 48, 52, 57, 49, 44, 32, 85, 43, 48, 52, 66, 48, 45, 48, 52, 66, 49, 44, 32, 85, 43, 50, 49, 49, 54, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 88, 117, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 49, 70, 48, 48, 45, 49, 70, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 85, 101, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 51, 55, 48, 45, 48, 51, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 88, 101, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 50, 45, 48, 49, 48, 51, 44, 32, 85, 43, 48, 49, 49, 48, 45, 48, 49, 49, 49, 44, 32, 85, 43, 48, 49, 50, 56, 45, 48, 49, 50, 57, 44, 32, 85, 43, 48, 49, 54, 56, 45, 48, 49, 54, 57, 44, 32, 85, 43, 48, 49, 65, 48, 45, 48, 49, 65, 49, 44, 32, 85, 43, 48, 49, 65, 70, 45, 48, 49, 66, 48, 44, 32, 85, 43, 49, 69, 65, 48, 45, 49, 69, 70, 57, 44, 32, 85, 43, 50, 48, 65, 66, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 88, 79, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 85, 117, 104, 112, 75, 75, 83, 84, 106, 119, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 116, 121, 108, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 99, 114, 105, 112, 116, 32, 100, 97, 116, 97, 45, 100, 97, 112, 112, 45, 100, 101, 116, 101, 99, 116, 105, 111, 110, 61, 34, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40, 102, 117, 110, 99, 116, 105, 111, 110, 40, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 116, 32, 97, 108, 114, 101, 97, 100, 121, 73, 110, 115, 101, 114, 116, 101, 100, 77, 101, 116, 97, 84, 97, 103, 32, 61, 32, 102, 97, 108, 115, 101, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 95, 95, 105, 110, 115, 101, 114, 116, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 101, 100, 40, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 33, 97, 108, 114, 101, 97, 100, 121, 73, 110, 115, 101, 114, 116, 101, 100, 77, 101, 116, 97, 84, 97, 103, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 115, 116, 32, 109, 101, 116, 97, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 39, 109, 101, 116, 97, 39, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 101, 116, 97, 46, 110, 97, 109, 101, 32, 61, 32, 39, 100, 97, 112, 112, 45, 100, 101, 116, 101, 99, 116, 101, 100, 39, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 104, 101, 97, 100, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 109, 101, 116, 97, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 114, 101, 97, 100, 121, 73, 110, 115, 101, 114, 116, 101, 100, 77, 101, 116, 97, 84, 97, 103, 32, 61, 32, 116, 114, 117, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 119, 105, 110, 100, 111, 119, 46, 104, 97, 115, 79, 119, 110, 80, 114, 111, 112, 101, 114, 116, 121, 40, 39, 119, 101, 98, 51, 39, 41, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 78, 111, 116, 101, 32, 97, 32, 99, 108, 111, 115, 117, 114, 101, 32, 99, 97, 110, 39, 116, 32, 98, 101, 32, 117, 115, 101, 100, 32, 102, 111, 114, 32, 116, 104, 105, 115, 32, 118, 97, 114, 32, 98, 101, 99, 97, 117, 115, 101, 32, 115, 111, 109, 101, 32, 115, 105, 116, 101, 115, 32, 108, 105, 107, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 119, 119, 119, 46, 119, 110, 121, 99, 46, 111, 114, 103, 32, 100, 111, 32, 97, 32, 115, 101, 99, 111, 110, 100, 32, 115, 99, 114, 105, 112, 116, 32, 101, 120, 101, 99, 117, 116, 105, 111, 110, 32, 118, 105, 97, 32, 101, 118, 97, 108, 32, 102, 111, 114, 32, 115, 111, 109, 101, 32, 114, 101, 97, 115, 111, 110, 46, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 110, 100, 111, 119, 46, 95, 95, 100, 105, 115, 97, 98, 108, 101, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 105, 111, 110, 73, 110, 115, 101, 114, 116, 105, 111, 110, 32, 61, 32, 116, 114, 117, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 76, 105, 107, 101, 108, 121, 32, 111, 108, 100, 87, 101, 98, 51, 32, 105, 115, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 97, 110, 100, 32, 105, 116, 32, 104, 97, 115, 32, 97, 32, 112, 114, 111, 112, 101, 114, 116, 121, 32, 111, 110, 108, 121, 32, 98, 101, 99, 97, 117, 115, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 119, 101, 32, 100, 101, 102, 105, 110, 101, 100, 32, 105, 116, 46, 32, 83, 111, 109, 101, 32, 115, 105, 116, 101, 115, 32, 108, 105, 107, 101, 32, 119, 110, 121, 99, 46, 111, 114, 103, 32, 97, 114, 101, 32, 101, 118, 97, 108, 105, 110, 103, 32, 97, 108, 108, 32, 115, 99, 114, 105, 112, 116, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 116, 104, 97, 116, 32, 101, 120, 105, 115, 116, 32, 97, 103, 97, 105, 110, 44, 32, 115, 111, 32, 116, 104, 105, 115, 32, 105, 115, 32, 112, 114, 111, 116, 101, 99, 116, 105, 111, 110, 32, 97, 103, 97, 105, 110, 115, 116, 32, 109, 117, 108, 116, 105, 112, 108, 101, 32, 99, 97, 108, 108, 115, 46, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 119, 105, 110, 100, 111, 119, 46, 119, 101, 98, 51, 32, 61, 61, 61, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 101, 116, 117, 114, 110, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 95, 105, 110, 115, 101, 114, 116, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 101, 100, 40, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 32, 101, 108, 115, 101, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 97, 114, 32, 111, 108, 100, 87, 101, 98, 51, 32, 61, 32, 119, 105, 110, 100, 111, 119, 46, 119, 101, 98, 51, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 79, 98, 106, 101, 99, 116, 46, 100, 101, 102, 105, 110, 101, 80, 114, 111, 112, 101, 114, 116, 121, 40, 119, 105, 110, 100, 111, 119, 44, 32, 39, 119, 101, 98, 51, 39, 44, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 102, 105, 103, 117, 114, 97, 98, 108, 101, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 101, 116, 58, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 118, 97, 108, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 33, 119, 105, 110, 100, 111, 119, 46, 95, 95, 100, 105, 115, 97, 98, 108, 101, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 105, 111, 110, 73, 110, 115, 101, 114, 116, 105, 111, 110, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 95, 105, 110, 115, 101, 114, 116, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 101, 100, 40, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 108, 100, 87, 101, 98, 51, 32, 61, 32, 118, 97, 108, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 101, 116, 58, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 33, 119, 105, 110, 100, 111, 119, 46, 95, 95, 100, 105, 115, 97, 98, 108, 101, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 105, 111, 110, 73, 110, 115, 101, 114, 116, 105, 111, 110, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 95, 105, 110, 115, 101, 114, 116, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 101, 100, 40, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 101, 116, 117, 114, 110, 32, 111, 108, 100, 87, 101, 98, 51, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 41, 40, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 99, 114, 105, 112, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 99, 114, 105, 112, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 116, 101, 120, 116, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 97, 114, 32, 116, 101, 120, 116, 65, 114, 101, 97, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 116, 101, 120, 116, 97, 114, 101, 97, 34, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 118, 97, 108, 117, 101, 32, 61, 32, 116, 101, 120, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 115, 116, 121, 108, 101, 46, 116, 111, 112, 32, 61, 32, 34, 48, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 115, 116, 121, 108, 101, 46, 108, 101, 102, 116, 32, 61, 32, 34, 48, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 115, 116, 121, 108, 101, 46, 112, 111, 115, 105, 116, 105, 111, 110, 32, 61, 32, 34, 102, 105, 120, 101, 100, 34, 59, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 98, 111, 100, 121, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 116, 101, 120, 116, 65, 114, 101, 97, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 102, 111, 99, 117, 115, 40, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 115, 101, 108, 101, 99, 116, 40, 41, 59, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 121, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 97, 114, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 101, 120, 101, 99, 67, 111, 109, 109, 97, 110, 100, 40, 39, 99, 111, 112, 121, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 101, 114, 116, 40, 39, 34, 39, 32, 43, 32, 116, 101, 120, 116, 32, 43, 32, 39, 34, 32, 99, 111, 112, 105, 101, 100, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 46, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 32, 101, 108, 115, 101, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 101, 114, 116, 40, 39, 85, 110, 97, 98, 108, 101, 32, 116, 111, 32, 99, 111, 112, 121, 32, 116, 101, 120, 116, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 46, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 32, 99, 97, 116, 99, 104, 32, 40, 101, 114, 114, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 101, 114, 116, 40, 39, 70, 97, 108, 108, 98, 97, 99, 107, 58, 32, 79, 111, 112, 115, 44, 32, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 99, 111, 112, 121, 39, 44, 32, 101, 114, 114, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 98, 111, 100, 121, 46, 114, 101, 109, 111, 118, 101, 67, 104, 105, 108, 100, 40, 116, 101, 120, 116, 65, 114, 101, 97, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 99, 114, 105, 112, 116, 62, 10, 32, 32, 32, 32, 60, 47, 104, 101, 97, 100, 62, 10, 32, 32, 32, 32, 60, 98, 111, 100, 121, 32, 100, 97, 116, 97, 45, 110, 101, 119, 45, 103, 114, 45, 99, 45, 115, 45, 99, 104, 101, 99, 107, 45, 108, 111, 97, 100, 101, 100, 61, 34, 49, 52, 46, 57, 56, 50, 46, 48, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 105, 100, 61, 34, 109, 97, 105, 110, 95, 98, 111, 100, 121, 95, 119, 114, 97, 112, 112, 101, 114, 95, 99, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 65, 112, 112, 67, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 105, 100, 61, 34, 110, 111, 116, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 101, 97, 100, 101, 114, 32, 99, 108, 97, 115, 115, 61, 34, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 108, 111, 103, 111, 32, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 34, 32, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 123, 123, 46, 65, 99, 99, 111, 117, 110, 116, 125, 125, 46, 108, 97, 99, 101, 119, 111, 114, 107, 46, 110, 101, 116, 47, 117, 105, 47, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 47, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 32, 116, 105, 116, 108, 101, 61, 34, 76, 97, 99, 101, 119, 111, 114, 107, 32, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 102, 105, 103, 117, 114, 101, 32, 99, 108, 97, 115, 115, 61, 34, 108, 111, 103, 111, 34, 62, 60, 105, 109, 103, 32, 115, 114, 99, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 123, 123, 46, 65, 99, 99, 111, 117, 110, 116, 125, 125, 46, 108, 97, 99, 101, 119, 111, 114, 107, 46, 110, 101, 116, 47, 117, 105, 47, 105, 109, 97, 103, 101, 115, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 108, 111, 103, 111, 45, 119, 104, 105, 116, 101, 46, 115, 118, 103, 34, 32, 116, 105, 116, 108, 101, 61, 34, 76, 97, 99, 101, 119, 111, 114, 107, 32, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 32, 99, 108, 97, 115, 115, 61, 34, 108, 111, 103, 111, 45, 105, 99, 111, 110, 34, 32, 97, 108, 116, 61, 34, 34, 62, 60, 47, 102, 105, 103, 117, 114, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 97, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 105, 99, 111, 110, 32, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 34, 32, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 123, 123, 46, 65, 99, 99, 111, 117, 110, 116, 125, 125, 46, 108, 97, 99, 101, 119, 111, 114, 107, 46, 110, 101, 116, 47, 117, 105, 47, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 47, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 32, 116, 105, 116, 108, 101, 61, 34, 76, 97, 99, 101, 119, 111, 114, 107, 32, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 102, 105, 103, 117, 114, 101, 32, 99, 108, 97, 115, 115, 61, 34, 108, 111, 103, 111, 34, 62, 60, 105, 109, 103, 32, 115, 114, 99, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 123, 123, 46, 65, 99, 99, 111, 117, 110, 116, 125, 125, 46, 108, 97, 99, 101, 119, 111, 114, 107, 46, 110, 101, 116, 47, 117, 105, 47, 105, 109, 97, 103, 101, 115, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 105, 99, 111, 110, 45, 119, 104, 105, 116, 101, 46, 115, 118, 103, 34, 32, 116, 105, 116, 108, 101, 61, 34, 76, 97, 99, 101, 119, 111, 114, 107, 32, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 32, 99, 108, 97, 115, 115, 61, 34, 108, 111, 103, 111, 45, 105, 99, 111, 110, 34, 32, 97, 108, 116, 61, 34, 34, 62, 60, 47, 102, 105, 103, 117, 114, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 97, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 115, 32, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 45, 109, 101, 103, 97, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 100, 101, 102, 97, 117, 108, 116, 34, 62, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 32, 105, 110, 102, 111, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 45, 98, 117, 116, 116, 111, 110, 32, 32, 105, 99, 111, 110, 45, 109, 101, 110, 117, 32, 110, 117, 108, 108, 32, 118, 101, 114, 116, 105, 99, 97, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 82, 101, 99, 101, 110, 116, 32, 100, 111, 115, 115, 105, 101, 114, 115, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 62, 77, 101, 110, 117, 60, 47, 98, 117, 116, 116, 111, 110, 62, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 98, 45, 98, 97, 114, 32, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 34, 62, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 117, 115, 101, 114, 34, 32, 116, 105, 116, 108, 101, 61, 34, 80, 114, 111, 102, 105, 108, 101, 32, 79, 112, 116, 105, 111, 110, 115, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 117, 115, 101, 114, 45, 105, 110, 102, 111, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 117, 115, 101, 114, 45, 114, 111, 108, 101, 34, 62, 60, 104, 52, 62, 76, 111, 99, 97, 108, 32, 77, 111, 100, 101, 60, 47, 104, 52, 62, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 32, 117, 115, 101, 114, 45, 105, 109, 97, 103, 101, 32, 117, 115, 101, 114, 45, 105, 109, 97, 103, 101, 45, 99, 111, 108, 111, 114, 32, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 45, 99, 105, 114, 99, 108, 101, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 51, 50, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 51, 50, 112, 120, 59, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 32, 51, 50, 112, 120, 59, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 32, 49, 114, 101, 109, 59, 34, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 45, 115, 116, 114, 105, 110, 103, 34, 32, 115, 116, 121, 108, 101, 61, 34, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 32, 51, 50, 112, 120, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 115, 99, 97, 108, 101, 40, 49, 41, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 88, 40, 45, 53, 48, 37, 41, 59, 34, 62, 67, 76, 73, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 104, 101, 97, 100, 101, 114, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 99, 111, 109, 112, 32, 112, 97, 103, 101, 32, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 101, 97, 100, 101, 114, 32, 99, 108, 97, 115, 115, 61, 34, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 105, 110, 102, 111, 45, 98, 97, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 105, 99, 111, 110, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 118, 117, 108, 110, 34, 62, 60, 115, 112, 97, 110, 62, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 121, 32, 65, 115, 115, 101, 115, 115, 109, 101, 110, 116, 38, 110, 98, 115, 112, 59, 38, 110, 98, 115, 112, 59, 38, 103, 116, 59, 38, 110, 98, 115, 112, 59, 38, 110, 98, 115, 112, 59, 73, 109, 97, 103, 101, 60, 47, 115, 112, 97, 110, 62, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 115, 32, 32, 32, 99, 101, 110, 116, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 105, 110, 112, 117, 116, 45, 108, 97, 98, 101, 108, 34, 62, 60, 47, 115, 112, 97, 110, 62, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 100, 101, 102, 97, 117, 108, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 83, 101, 108, 101, 99, 116, 32, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 100, 114, 111, 112, 100, 111, 119, 110, 32, 104, 97, 115, 45, 118, 97, 108, 117, 101, 32, 105, 115, 45, 115, 101, 97, 114, 99, 104, 97, 98, 108, 101, 32, 83, 101, 108, 101, 99, 116, 45, 45, 115, 105, 110, 103, 108, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 105, 110, 112, 117, 116, 32, 110, 97, 109, 101, 61, 34, 102, 111, 114, 109, 45, 102, 105, 101, 108, 100, 45, 110, 97, 109, 101, 34, 32, 116, 121, 112, 101, 61, 34, 104, 105, 100, 100, 101, 110, 34, 32, 118, 97, 108, 117, 101, 61, 34, 99, 98, 53, 99, 52, 52, 100, 97, 97, 54, 49, 98, 53, 51, 48, 53, 48, 52, 57, 57, 50, 50, 50, 48, 56, 52, 55, 52, 52, 98, 52, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 104, 101, 97, 100, 101, 114, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 103, 114, 105, 100, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 82, 101, 112, 111, 115, 105, 116, 111, 114, 121, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 32, 110, 117, 108, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 111, 112, 121, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 39, 123, 123, 46, 82, 101, 112, 111, 115, 105, 116, 111, 114, 121, 125, 125, 39, 41, 34, 62, 60, 47, 98, 117, 116, 116, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 32, 99, 108, 97, 115, 115, 61, 34, 115, 117, 109, 109, 97, 114, 121, 45, 101, 108, 108, 105, 112, 115, 105, 115, 34, 62, 123, 123, 46, 82, 101, 112, 111, 115, 105, 116, 111, 114, 121, 125, 125, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 73, 68, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 32, 110, 117, 108, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 111, 112, 121, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 39, 123, 123, 46, 73, 68, 125, 125, 39, 41, 34, 41, 62, 60, 47, 98, 117, 116, 116, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 32, 99, 108, 97, 115, 115, 61, 34, 115, 117, 109, 109, 97, 114, 121, 45, 101, 108, 108, 105, 112, 115, 105, 115, 34, 62, 123, 123, 46, 73, 68, 125, 125, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 68, 105, 103, 101, 115, 116, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 32, 110, 117, 108, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 111, 112, 121, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 39, 123, 123, 46, 68, 105, 103, 101, 115, 116, 125, 125, 39, 41, 34, 62, 60, 47, 98, 117, 116, 116, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 32, 99, 108, 97, 115, 115, 61, 34, 115, 117, 109, 109, 97, 114, 121, 45, 101, 108, 108, 105, 112, 115, 105, 115, 34, 62, 123, 123, 46, 68, 105, 103, 101, 115, 116, 125, 125, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 73, 109, 97, 103, 101, 32, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109, 101, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 32, 99, 108, 97, 115, 115, 61, 34, 34, 62, 123, 123, 46, 67, 114, 101, 97, 116, 101, 100, 84, 105, 109, 101, 125, 125, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 123, 123, 46, 83, 105, 122, 101, 125, 125, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 53, 32, 99, 108, 97, 115, 115, 61, 34, 34, 62, 73, 109, 97, 103, 101, 32, 83, 105, 122, 101, 60, 47, 104, 53, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 32, 108, 97, 98, 101, 108, 34, 62, 84, 97, 103, 115, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 116, 97, 103, 115, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 103, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 114, 97, 110, 103, 101, 32, 36, 116, 97, 103, 32, 58, 61, 32, 46, 84, 97, 103, 115, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 97, 110, 116, 45, 116, 97, 103, 34, 62, 123, 123, 32, 36, 116, 97, 103, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 118, 105, 101, 119, 32, 118, 117, 108, 110, 45, 118, 105, 101, 119, 45, 114, 101, 112, 111, 114, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 114, 111, 119, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 123, 123, 46, 84, 111, 116, 97, 108, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 32, 117, 110, 105, 113, 117, 101, 32, 118, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 32, 119, 101, 114, 101, 32, 100, 101, 116, 101, 99, 116, 101, 100, 32, 105, 110, 32, 116, 104, 105, 115, 32, 97, 115, 115, 101, 115, 115, 109, 101, 110, 116, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 62, 60, 115, 112, 97, 110, 62, 123, 123, 46, 70, 105, 120, 97, 98, 108, 101, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 32, 102, 105, 120, 101, 100, 32, 118, 101, 114, 115, 105, 111, 110, 115, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 60, 47, 115, 112, 97, 110, 62, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 118, 105, 101, 119, 45, 99, 104, 97, 114, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 98, 97, 114, 45, 99, 104, 97, 114, 116, 34, 32, 105, 100, 61, 34, 98, 97, 114, 45, 99, 104, 97, 114, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 118, 103, 32, 99, 108, 97, 115, 115, 61, 34, 99, 104, 97, 114, 116, 32, 98, 97, 114, 32, 109, 117, 108, 116, 105, 45, 102, 105, 108, 108, 34, 32, 119, 105, 100, 116, 104, 61, 34, 49, 48, 48, 37, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 55, 48, 34, 32, 112, 114, 101, 115, 101, 114, 118, 101, 65, 115, 112, 101, 99, 116, 82, 97, 116, 105, 111, 61, 34, 120, 77, 105, 110, 89, 77, 105, 110, 32, 109, 101, 101, 116, 34, 32, 118, 105, 101, 119, 66, 111, 120, 61, 34, 49, 48, 48, 37, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 103, 32, 99, 108, 97, 115, 115, 61, 34, 99, 104, 97, 114, 116, 45, 103, 114, 111, 117, 112, 34, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 61, 34, 116, 114, 97, 110, 115, 108, 97, 116, 101, 40, 48, 44, 32, 48, 41, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 103, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 45, 103, 114, 111, 117, 112, 34, 32, 119, 105, 100, 116, 104, 61, 34, 49, 53, 52, 54, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 99, 114, 105, 116, 105, 99, 97, 108, 34, 32, 102, 105, 108, 108, 61, 34, 35, 98, 56, 48, 99, 48, 57, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 48, 37, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 99, 114, 105, 116, 105, 99, 97, 108, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 104, 105, 103, 104, 34, 32, 102, 105, 108, 108, 61, 34, 35, 102, 102, 49, 50, 48, 48, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 88, 32, 34, 104, 105, 103, 104, 34, 32, 46, 125, 125, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 104, 105, 103, 104, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 109, 101, 100, 105, 117, 109, 34, 32, 102, 105, 108, 108, 61, 34, 35, 102, 102, 97, 52, 48, 48, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 88, 32, 34, 109, 101, 100, 105, 117, 109, 34, 32, 46, 125, 125, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 109, 101, 100, 105, 117, 109, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 108, 111, 119, 34, 32, 102, 105, 108, 108, 61, 34, 35, 102, 102, 100, 49, 53, 49, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 88, 32, 34, 108, 111, 119, 34, 32, 46, 125, 125, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 108, 111, 119, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 105, 110, 102, 111, 34, 32, 102, 105, 108, 108, 61, 34, 35, 50, 101, 98, 57, 102, 48, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 88, 32, 34, 105, 110, 102, 111, 34, 32, 46, 125, 125, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 105, 110, 102, 111, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 99, 114, 105, 116, 105, 99, 97, 108, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 99, 114, 105, 116, 105, 99, 97, 108, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 67, 114, 105, 116, 105, 99, 97, 108, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 104, 105, 103, 104, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 104, 105, 103, 104, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 72, 105, 103, 104, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 109, 101, 100, 105, 117, 109, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 109, 101, 100, 105, 117, 109, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 77, 101, 100, 105, 117, 109, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 108, 111, 119, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 108, 111, 119, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 76, 111, 119, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 105, 110, 102, 111, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 105, 110, 102, 111, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 73, 110, 102, 111, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 103, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 103, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 118, 103, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 118, 105, 101, 119, 45, 100, 101, 116, 97, 105, 108, 115, 34, 32, 105, 100, 61, 34, 86, 117, 108, 110, 95, 69, 118, 97, 108, 73, 109, 97, 103, 101, 68, 101, 116, 97, 105, 108, 115, 66, 121, 69, 118, 97, 108, 71, 117, 105, 100, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 123, 123, 46, 84, 97, 98, 108, 101, 72, 101, 105, 103, 104, 116, 125, 125, 112, 120, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 98, 108, 101, 45, 98, 111, 100, 121, 45, 102, 111, 111, 116, 101, 114, 45, 111, 117, 116, 115, 105, 100, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 110, 111, 115, 99, 114, 111, 108, 108, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 48, 48, 37, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 101, 97, 100, 101, 114, 32, 99, 108, 97, 115, 115, 61, 34, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 104, 101, 97, 100, 101, 114, 32, 118, 117, 108, 110, 45, 104, 101, 97, 100, 101, 114, 32, 112, 97, 100, 100, 105, 110, 103, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 62, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 32, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 104, 101, 97, 100, 101, 114, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 34, 32, 115, 116, 121, 108, 101, 61, 34, 34, 62, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 34, 32, 97, 114, 105, 97, 45, 114, 111, 119, 99, 111, 117, 110, 116, 61, 34, 56, 56, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 123, 123, 46, 84, 97, 98, 108, 101, 72, 101, 105, 103, 104, 116, 125, 125, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 115, 67, 111, 110, 116, 97, 105, 110, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 123, 123, 46, 84, 97, 98, 108, 101, 72, 101, 105, 103, 104, 116, 125, 125, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 104, 105, 100, 100, 101, 110, 69, 108, 101, 109, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 95, 109, 97, 105, 110, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 52, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 109, 111, 117, 115, 101, 65, 114, 101, 97, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 52, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 49, 59, 32, 100, 105, 115, 112, 108, 97, 121, 58, 32, 98, 108, 111, 99, 107, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 109, 97, 105, 110, 32, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 34, 32, 114, 111, 108, 101, 61, 34, 114, 111, 119, 34, 32, 97, 114, 105, 97, 45, 114, 111, 119, 105, 110, 100, 101, 120, 61, 34, 49, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 98, 111, 100, 121, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 50, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 48, 57, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 48, 57, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 48, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 52, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 67, 86, 69, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 67, 86, 69, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 50, 52, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 32, 116, 111, 111, 108, 116, 105, 112, 45, 99, 111, 108, 117, 109, 110, 45, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 83, 69, 86, 69, 82, 73, 84, 89, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 111, 118, 101, 114, 108, 97, 121, 84, 114, 105, 103, 103, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 104, 105, 100, 101, 32, 99, 104, 105, 108, 100, 114, 101, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 97, 115, 105, 100, 101, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 32, 114, 105, 103, 104, 116, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 97, 117, 116, 111, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 97, 117, 116, 111, 59, 32, 112, 97, 100, 100, 105, 110, 103, 58, 32, 49, 48, 112, 120, 32, 53, 112, 120, 59, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 32, 49, 52, 112, 120, 59, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 32, 112, 114, 101, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 32, 45, 49, 52, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 104, 101, 97, 100, 101, 114, 32, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 98, 111, 100, 121, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 45, 110, 97, 109, 101, 34, 62, 83, 101, 118, 101, 114, 105, 116, 121, 32, 108, 101, 118, 101, 108, 32, 97, 116, 116, 114, 105, 98, 117, 116, 101, 100, 32, 116, 111, 32, 68, 105, 115, 116, 114, 111, 32, 115, 111, 117, 114, 99, 101, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 101, 114, 101, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 32, 97, 110, 100, 32, 105, 102, 32, 110, 111, 116, 32, 100, 101, 102, 97, 117, 108, 116, 115, 32, 116, 111, 32, 78, 86, 68, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 97, 115, 105, 100, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 53, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 52, 52, 54, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 32, 116, 111, 111, 108, 116, 105, 112, 45, 99, 111, 108, 117, 109, 110, 45, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 53, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 83, 67, 79, 82, 69, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 111, 118, 101, 114, 108, 97, 121, 84, 114, 105, 103, 103, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 104, 105, 100, 101, 32, 99, 104, 105, 108, 100, 114, 101, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 97, 115, 105, 100, 101, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 32, 114, 105, 103, 104, 116, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 97, 117, 116, 111, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 97, 117, 116, 111, 59, 32, 112, 97, 100, 100, 105, 110, 103, 58, 32, 49, 48, 112, 120, 32, 53, 112, 120, 59, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 32, 49, 52, 112, 120, 59, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 32, 112, 114, 101, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 32, 45, 49, 52, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 104, 101, 97, 100, 101, 114, 32, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 98, 111, 100, 121, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 45, 110, 97, 109, 101, 34, 62, 68, 101, 102, 97, 117, 108, 116, 115, 32, 116, 111, 32, 67, 86, 83, 83, 118, 51, 32, 115, 99, 111, 114, 101, 32, 97, 110, 100, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 67, 86, 83, 83, 118, 50, 32, 105, 102, 32, 118, 51, 32, 115, 99, 111, 114, 101, 115, 32, 97, 114, 101, 32, 110, 111, 116, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 97, 115, 105, 100, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 54, 57, 54, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 80, 65, 67, 75, 65, 71, 69, 32, 78, 65, 77, 69, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 80, 65, 67, 75, 65, 71, 69, 32, 78, 65, 77, 69, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 56, 57, 57, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 67, 85, 82, 82, 69, 78, 84, 32, 86, 69, 82, 83, 73, 79, 78, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 67, 85, 82, 82, 69, 78, 84, 32, 86, 69, 82, 83, 73, 79, 78, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 49, 48, 50, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 70, 73, 88, 32, 86, 69, 82, 83, 73, 79, 78, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 70, 73, 88, 32, 86, 69, 82, 83, 73, 79, 78, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 52, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 51, 48, 53, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 73, 78, 84, 82, 79, 68, 85, 67, 69, 68, 32, 73, 78, 32, 76, 65, 89, 69, 82, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 52, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 73, 78, 84, 82, 79, 68, 85, 67, 69, 68, 32, 73, 78, 32, 76, 65, 89, 69, 82, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 53, 48, 57, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 50, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 115, 99, 114, 111, 108, 108, 98, 97, 114, 83, 112, 97, 99, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 53, 48, 55, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 114, 97, 110, 103, 101, 32, 36, 118, 117, 108, 110, 32, 58, 61, 32, 46, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 48, 59, 32, 100, 105, 115, 112, 108, 97, 121, 58, 32, 98, 108, 111, 99, 107, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 123, 123, 32, 36, 118, 117, 108, 110, 46, 82, 111, 119, 72, 101, 105, 103, 104, 116, 32, 125, 125, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 104, 105, 103, 104, 108, 105, 103, 104, 116, 101, 100, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 111, 100, 100, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 98, 111, 100, 121, 82, 111, 119, 34, 32, 114, 111, 108, 101, 61, 34, 114, 111, 119, 34, 32, 97, 114, 105, 97, 45, 114, 111, 119, 105, 110, 100, 101, 120, 61, 34, 51, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 98, 111, 100, 121, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 50, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 48, 57, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 48, 57, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 48, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 123, 123, 32, 36, 118, 117, 108, 110, 46, 67, 86, 69, 32, 125, 125, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 67, 86, 69, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 50, 52, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 100, 101, 102, 97, 117, 108, 116, 34, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 105, 99, 111, 110, 45, 99, 105, 114, 99, 108, 101, 32, 99, 97, 112, 105, 116, 97, 108, 105, 122, 101, 32, 97, 99, 116, 105, 118, 101, 32, 97, 108, 101, 114, 116, 45, 123, 123, 32, 36, 118, 117, 108, 110, 46, 83, 101, 118, 101, 114, 105, 116, 121, 72, 84, 77, 76, 67, 108, 97, 115, 115, 32, 125, 125, 45, 105, 99, 111, 110, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 83, 101, 118, 101, 114, 105, 116, 121, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 53, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 52, 52, 54, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 105, 102, 32, 36, 118, 117, 108, 110, 46, 85, 115, 101, 86, 51, 83, 99, 111, 114, 101, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 118, 51, 45, 98, 111, 114, 100, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 112, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 49, 32, 118, 51, 45, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 34, 62, 67, 86, 83, 83, 32, 51, 46, 48, 60, 47, 112, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 112, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 50, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 86, 51, 83, 99, 111, 114, 101, 32, 125, 125, 32, 111, 102, 32, 49, 48, 60, 47, 112, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 105, 102, 32, 36, 118, 117, 108, 110, 46, 85, 115, 101, 86, 50, 83, 99, 111, 114, 101, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 118, 50, 45, 98, 111, 114, 100, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 112, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 49, 32, 118, 50, 45, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 34, 62, 67, 86, 83, 83, 32, 50, 46, 48, 60, 47, 112, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 112, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 50, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 86, 50, 83, 99, 111, 114, 101, 32, 125, 125, 32, 111, 102, 32, 49, 48, 60, 47, 112, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 105, 102, 32, 36, 118, 117, 108, 110, 46, 85, 115, 101, 78, 111, 83, 99, 111, 114, 101, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 78, 47, 65, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 54, 57, 54, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 78, 97, 109, 101, 32, 125, 125, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 78, 97, 109, 101, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 56, 57, 57, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 86, 101, 114, 115, 105, 111, 110, 32, 125, 125, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 86, 101, 114, 115, 105, 111, 110, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 49, 48, 50, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 70, 105, 120, 101, 100, 32, 125, 125, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 70, 105, 120, 101, 100, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 52, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 51, 48, 53, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 115, 32, 32, 104, 111, 118, 101, 114, 32, 108, 101, 102, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 100, 101, 102, 97, 117, 108, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 32, 110, 117, 108, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 111, 112, 121, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 39, 123, 123, 32, 36, 118, 117, 108, 110, 46, 76, 97, 121, 101, 114, 32, 125, 125, 39, 41, 34, 62, 60, 47, 98, 117, 116, 116, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 32, 123, 123, 32, 108, 97, 121, 101, 114, 80, 114, 105, 110, 116, 32, 36, 118, 117, 108, 110, 46, 76, 97, 121, 101, 114, 32, 125, 125, 34, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 98, 111, 108, 100, 45, 116, 101, 120, 116, 34, 62, 123, 123, 32, 108, 97, 121, 101, 114, 73, 110, 115, 116, 114, 117, 99, 116, 105, 111, 110, 32, 36, 118, 117, 108, 110, 46, 76, 97, 121, 101, 114, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 32, 32, 123, 123, 32, 108, 97, 121, 101, 114, 80, 114, 105, 110, 116, 32, 36, 118, 117, 108, 110, 46, 76, 97, 121, 101, 114, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 53, 48, 57, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 50, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 115, 99, 114, 111, 108, 108, 98, 97, 114, 83, 112, 97, 99, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 53, 48, 55, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 98, 111, 116, 116, 111, 109, 83, 104, 97, 100, 111, 119, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 98, 111, 116, 116, 111, 109, 83, 104, 97, 100, 111, 119, 34, 32, 115, 116, 121, 108, 101, 61, 34, 116, 111, 112, 58, 32, 52, 48, 50, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 60, 47, 98, 111, 100, 121, 62, 10, 60, 47, 104, 116, 109, 108, 62, 10}) +} diff --git a/vendor/github.com/lacework/go-sdk/internal/databox/box.go b/vendor/github.com/lacework/go-sdk/internal/databox/box.go new file mode 100644 index 000000000..66ac4f71a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/databox/box.go @@ -0,0 +1,91 @@ +//go:generate go run generator/main.go +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package databox + +import ( + "fmt" + "strings" +) + +// Embed a global data box +var box = &data{box: make(map[string][]byte)} + +// Add a file to the global box +func Add(file string, content []byte) { + box.Add(file, content) +} + +// Get a file from the global box +func Get(file string) ([]byte, bool) { + return box.Get(file) +} + +// List all files inside the global box +func ListAll() []string { + return box.List("/") +} + +// List of files from a directory inside the global box +// +// Example: +// ```go +// databox.ListFilesFromDir("/scaffoldings/golang") +// ``` +func ListFilesFromDir(prefix string) []string { + return box.List(prefix) +} + +// Data box definition +type data struct { + box map[string][]byte +} + +// Add a file to the box +func (d *data) Add(file string, content []byte) { + d.box[file] = content +} + +// Get a file from the box +func (d *data) Get(file string) ([]byte, bool) { + if !strings.HasPrefix(file, "/") { + file = fmt.Sprintf("/%s", file) + } + + f, ok := d.box[file] + return f, ok +} + +// List of files inside the box +func (d *data) List(prefix string) []string { + if prefix == "" { + prefix = "/" + } else if !strings.HasPrefix(prefix, "/") { + prefix = fmt.Sprintf("/%s", prefix) + } + + tree := []string{} + for f := range d.box { + if strings.HasPrefix(f, prefix) { + tree = append(tree, f) + } + } + + return tree +} diff --git a/vendor/github.com/lacework/go-sdk/internal/failon/count_operation.go b/vendor/github.com/lacework/go-sdk/internal/failon/count_operation.go new file mode 100644 index 000000000..ebd84ff64 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/failon/count_operation.go @@ -0,0 +1,49 @@ +package failon + +import ( + "regexp" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +const operationRE = `^(>|>=|<|<=|={1,2}|!=)\s*(\d+)$` + +type CountOperation struct { + operator string + num int +} + +func (co *CountOperation) Parse(s string) error { + re := regexp.MustCompile(operationRE) + + s = strings.TrimSpace(s) + + var opParts []string + if opParts = re.FindStringSubmatch(s); s == "" || opParts == nil { + return errors.Errorf("count operation (%s) is invalid", s) + } + co.num, _ = strconv.Atoi(opParts[2]) + co.operator = opParts[1] + return nil +} + +func (co CountOperation) IsFail(count int) (bool, error) { + switch co.operator { + case ">": + return count > co.num, nil + case ">=": + return count >= co.num, nil + case "<": + return count < co.num, nil + case "<=": + return count <= co.num, nil + case "=", "==": + return count == co.num, nil + case "!=": + return count != co.num, nil + default: + return true, errors.Errorf("count operation (%s) is invalid", co.operator) + } +} diff --git a/vendor/github.com/lacework/go-sdk/internal/file/file.go b/vendor/github.com/lacework/go-sdk/internal/file/file.go new file mode 100644 index 000000000..a424dbdd2 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/file/file.go @@ -0,0 +1,55 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package file + +import ( + "os" + "path/filepath" +) + +// FileExists checks if a file exists and is not a directory +func FileExists(filename string) bool { + f, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + if f == nil { + return false + } + return !f.IsDir() +} + +// Copy a file +func Copy(src, dst string) error { + srcAbs, err := filepath.Abs(src) + if err != nil { + return err + } + dstAbs, err := filepath.Abs(dst) + if err != nil { + return err + } + + input, err := os.ReadFile(srcAbs) + if err != nil { + return err + } + + return os.WriteFile(dstAbs, input, 0755) +} diff --git a/vendor/github.com/lacework/go-sdk/internal/format/secret.go b/vendor/github.com/lacework/go-sdk/internal/format/secret.go new file mode 100644 index 000000000..3d383f100 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/format/secret.go @@ -0,0 +1,32 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package format + +func Secret(nToShow int, secret string) string { + secretSize := len(secret) + if secretSize <= nToShow { + return secret + } + + var chars = []byte(secret) + for i := 0; i < (secretSize - nToShow); i++ { + chars[i] = '*' + } + return string(chars) +} diff --git a/vendor/github.com/lacework/go-sdk/internal/format/strings.go b/vendor/github.com/lacework/go-sdk/internal/format/strings.go new file mode 100644 index 000000000..6424bdcc0 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/format/strings.go @@ -0,0 +1,46 @@ +package format + +import ( + "bytes" + "fmt" + "unicode" +) + +// SpaceUpperCase add a space each occurrence of an upper case letter. +// +// res := format.SpaceUpperCase("myExampleString") +// -> my Example String +func SpaceUpperCase(s string) string { + buf := &bytes.Buffer{} + prev := false + spaces := []*unicode.RangeTable{unicode.White_Space, unicode.Space} + for i, rune := range s { + if unicode.IsOneOf(spaces, rune) { + prev = true + } + + // if previous value is a space skip + if prev { + prev = false + continue + } + if unicode.IsUpper(rune) && i > 0 { + buf.WriteRune(' ') + } + buf.WriteRune(rune) + } + return buf.String() +} + +// Truncate reduce a string to specified char limit. Append ellipsis. +// +// res := format.Truncate("myExampleString", 10) +// +// -> myExampleS... +func Truncate(s string, max int) string { + if max > len(s) { + return s + } + + return fmt.Sprintf("%s...", s[:max]) +} diff --git a/vendor/github.com/lacework/go-sdk/internal/intgguid/rand.go b/vendor/github.com/lacework/go-sdk/internal/intgguid/rand.go new file mode 100644 index 000000000..a1444f338 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/intgguid/rand.go @@ -0,0 +1,53 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package intgguid + +// This package generates Globally Unique Identifiers (GUID) +// matching Lacework INTD_GUID that is used for integrations + +import ( + "fmt" + "math/rand" + "time" +) + +var ( + charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + randomSeed *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) +) + +func New() string { + return NewPrefix("MOCK") +} + +func NewPrefix(prefix string) string { + return fmt.Sprintf("%s_%s", prefix, randomString(47)) +} + +func stringFromCharset(length int, charset string) string { + bytes := make([]byte, length) + for i := range bytes { + bytes[i] = charset[randomSeed.Intn(len(charset))] + } + return string(bytes) +} + +func randomString(length int) string { + return stringFromCharset(length, charset) +} diff --git a/vendor/github.com/lacework/go-sdk/internal/lacework/server.go b/vendor/github.com/lacework/go-sdk/internal/lacework/server.go new file mode 100644 index 000000000..58583b5a1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/lacework/server.go @@ -0,0 +1,97 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lacework + +import ( + "fmt" + "net/http" + "net/http/httptest" + "time" +) + +// Mock is a quick HTTP server that can be used to mock a Lacework API +// server, you can use it to avoid using a real server in our unit tests +// +// A simple usage: +// +// func TestSomethingNew(t *testing.T) { +// fakeServer := lacework.NewServer() +// fakeServer.MockToken("TOKEN") +// defer fakeServer.Close() +// +// // Make sure to pass the fake API server URL +// c, err := api.NewClient("test", api.WithURL(fakeServer.URL())) +// if assert.Nil(t, err) { +// // The client c is ready to be used +// } +// } +type Mock struct { + Mux *http.ServeMux + Server *httptest.Server + ApiVersion string +} + +// MockServer returns a new mocked http server with a mutex +func MockServer() *Mock { + mux := http.NewServeMux() + return &Mock{ + Mux: mux, + Server: httptest.NewServer(mux), + ApiVersion: "v2", + } +} + +func MockUnstartedServer() *Mock { + mux := http.NewServeMux() + return &Mock{ + Mux: mux, + Server: httptest.NewUnstartedServer(mux), + ApiVersion: "v2", + } +} + +// MockAPI will mock the api path inside the server mutex with the provided handler function +func (m *Mock) MockAPI(p string, handler func(http.ResponseWriter, *http.Request)) { + m.Mux.HandleFunc(fmt.Sprintf("/api/%s/%s", m.ApiVersion, p), handler) +} + +func (s *Mock) MockToken(token string) { + s.MockAPI("access/tokens", func(w http.ResponseWriter, r *http.Request) { + expiration := time.Now().AddDate(0, 0, 1) + fmt.Fprintf(w, ` + { + "expiresAt": "`+expiration.Format(time.RFC3339)+`", + "token": "`+token+`" + } + `) + }) +} + +func (m Mock) URL() string { + if m.Server != nil { + return m.Server.URL + } + return "" +} + +func (m Mock) Close() { + if m.Server != nil { + m.Server.Close() + } +} diff --git a/vendor/github.com/lacework/go-sdk/internal/pointer/bool.go b/vendor/github.com/lacework/go-sdk/internal/pointer/bool.go new file mode 100644 index 000000000..1706c285d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/pointer/bool.go @@ -0,0 +1,10 @@ +package pointer + +// CompareBoolPtr compares a bool pointer to a bool primitive +// Returns false if bool pointer is nil +func CompareBoolPtr(ptr *bool, b bool) bool { + if ptr == nil { + return false + } + return *ptr == b +} diff --git a/vendor/github.com/lacework/go-sdk/internal/unique/strings.go b/vendor/github.com/lacework/go-sdk/internal/unique/strings.go new file mode 100644 index 000000000..69dcf9cb2 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/unique/strings.go @@ -0,0 +1,19 @@ +package unique + +func StringSlice(slice []string) []string { + _map := make(map[string]string) + + for _, f := range slice { + _map[f] = "" + } + + set := make([]string, len(_map)) + + i := 0 + for key := range _map { + set[i] = key + i++ + } + + return set +} diff --git a/vendor/github.com/lacework/go-sdk/internal/validate/email.go b/vendor/github.com/lacework/go-sdk/internal/validate/email.go new file mode 100644 index 000000000..7cf6e85ce --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/internal/validate/email.go @@ -0,0 +1,22 @@ +package validate + +import ( + "net/mail" + + "github.com/pkg/errors" +) + +// validate email address against RFC 5322 +func EmailAddress(val interface{}) error { + switch value := val.(type) { + case string: + // This validates the email format against RFC 5322 + _, err := mail.ParseAddress(value) + if err != nil { + return errors.Wrap(err, "supplied email address is not a valid email address format") + } + default: + return errors.New("value must be a string") + } + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/helpers/helper.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/helpers/helper.go new file mode 100644 index 000000000..5398941ec --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/helpers/helper.go @@ -0,0 +1,79 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package helpers + +import ( + "fmt" + "os" + "strings" +) + +const ( + GCP_PROJECT_TYPE = "PROJECT" + GCP_ORGANIZATION_TYPE = "ORGANIZATION" +) + +func SkipEntry(Entry string, skipList, allowList map[string]bool) bool { + if skipList != nil { + if _, skip := skipList[Entry]; skip { + return true + } + } + + if allowList != nil { + if _, allow := allowList[Entry]; allow { + return false + } else { + // skip all other entries + return true + } + } + + return false +} + +func GetGcpFormatedLabel(in string) string { + lower := strings.ToLower(in) + out := strings.ReplaceAll(lower, ":", "-") + out = strings.ReplaceAll(out, ".", "-") + out = strings.ReplaceAll(out, "\"", "-") + out = strings.ReplaceAll(out, "{", "-") + out = strings.ReplaceAll(out, "}", "-") + return out +} + +func CombineErrors(old error, new error) error { + if new == nil { + return old + } + + if old == nil { + return new + } + + return fmt.Errorf("%s, %s", old.Error(), new.Error()) +} + +func IsProjectScanScope() bool { + return os.Getenv("GCP_SCAN_SCOPE") == GCP_PROJECT_TYPE +} + +func IsOrgScanScope() bool { + return os.Getenv("GCP_SCAN_SCOPE") == GCP_ORGANIZATION_TYPE +} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/folders/folders.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/folders/folders.go new file mode 100644 index 000000000..c2e3b4d39 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/folders/folders.go @@ -0,0 +1,107 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package resources + +import ( + "context" + "fmt" + + resourcemanager "cloud.google.com/go/resourcemanager/apiv3" + "github.com/lacework/go-sdk/lwcloud/gcp/helpers" + "google.golang.org/api/iterator" + "google.golang.org/api/option" + + resourcemanagerpb "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" +) + +type FolderInfo struct { + Name string + Parent string + DisplayName string + Ancestory string +} + +func EnumerateFolders(ctx context.Context, + clientOptions option.ClientOption, ParentId string, Ancestory string, + skipList, allowList map[string]bool) ([]FolderInfo, error) { + + var ( + client *resourcemanager.FoldersClient + err error + ) + + if clientOptions != nil { + client, err = resourcemanager.NewFoldersClient(ctx, clientOptions) + } else { + client, err = resourcemanager.NewFoldersClient(ctx) + } + + if err != nil { + return nil, fmt.Errorf("cannot enumerate folders in (%s) due to %s", Ancestory, err.Error()) + } + defer client.Close() + + req := &resourcemanagerpb.ListFoldersRequest{ + Parent: ParentId, + } + + folders := make([]FolderInfo, 0) + + for { + + it := client.ListFolders(ctx, req) + + for { + resp, err := it.Next() + if err == iterator.Done { + break + } + + if err != nil { + return nil, fmt.Errorf("cannot iterate folders in ancestory (%s) due to %s", Ancestory, err.Error()) + } + + if helpers.SkipEntry(resp.Name, skipList, allowList) { + continue + } + + fi := FolderInfo{ + Name: resp.Name, + DisplayName: resp.DisplayName, + Parent: resp.Parent, + Ancestory: Ancestory, + } + folders = append(folders, fi) + + // search for folders recursively; ignore errors + subFolders, _ := EnumerateFolders( + ctx, clientOptions, resp.Name, Ancestory+" -> "+resp.DisplayName+" ("+resp.Name+")", skipList, allowList, + ) + if len(subFolders) != 0 { + folders = append(folders, subFolders...) + } + } + + if req.GetPageToken() == "" { + break + } + } + + return folders, nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/instances/instances.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/instances/instances.go new file mode 100644 index 000000000..67dec6684 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/instances/instances.go @@ -0,0 +1,238 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package resources + +import ( + "context" + "encoding/json" + "fmt" + "path/filepath" + "strings" + "time" + + helpers "github.com/lacework/go-sdk/lwcloud/gcp/helpers" + projects "github.com/lacework/go-sdk/lwcloud/gcp/resources/projects" + + models "github.com/lacework/go-sdk/lwcloud/gcp/resources/models" + + compute "cloud.google.com/go/compute/apiv1" + "cloud.google.com/go/compute/apiv1/computepb" + "google.golang.org/api/iterator" + "google.golang.org/api/option" +) + +func EnumerateInstancesInProject( + ctx context.Context, clientOption option.ClientOption, region string, ProjectId string, +) ([]models.InstanceDetails, error) { + + var ( + client *compute.InstancesClient + err error + ) + + if clientOption != nil { + client, err = compute.NewInstancesRESTClient(ctx, clientOption) + } else { + client, err = compute.NewInstancesRESTClient(ctx) + } + + if err != nil { + return nil, err + } + defer client.Close() + + var filter string + if region != "" { + filter = fmt.Sprintf("zone eq .*%s.*", region) + } + + req := &computepb.AggregatedListInstancesRequest{ + Project: ProjectId, + Filter: &filter, + } + + instances := make([]models.InstanceDetails, 0) + + for { + it := client.AggregatedList(ctx, req) + + for { + resp, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + return nil, err + } + + if resp.Value == nil || len(resp.Value.Instances) == 0 { + continue + } + + for _, instance := range resp.Value.Instances { + + launchTime, _ := time.Parse(time.RFC3339, instance.GetCreationTimestamp()) + instanceIdStr := fmt.Sprintf("%d", instance.GetId()) + + diskIds := make([]string, len(instance.GetDisks())) + for i, disk := range instance.GetDisks() { + diskIds[i] = disk.GetSource() + } + + zone := filepath.Base(instance.GetZone()) + zoneStart := strings.LastIndex(zone, "-") + region := zone[:zoneStart] + + tags := make(map[string]string) + + privateIp, externalIp, vpcId := getNetworkInfo(instance.GetNetworkInterfaces(), tags) + + md := getMetadata(instance.GetMetadata()) + + instanceInfo := models.InstanceDetails{ + InstanceID: instanceIdStr, + Type: instance.GetMachineType(), + State: instance.GetStatus(), + Name: instance.GetName(), + Zone: zone, + Region: region, + ImageID: instance.GetSourceMachineImage(), + AccountID: filepath.Base(ProjectId), + VpcID: vpcId, + PublicIP: externalIp, + PrivateIP: privateIp, + LaunchTime: launchTime, + Tags: tags, + Props: md, + } + instances = append(instances, instanceInfo) + } + } + + if req.GetPageToken() == "" { + break + } + } + + return instances, nil +} + +func EnumerateInstancesInOrg( + ctx context.Context, clientOption option.ClientOption, region string, + OrgId string, skipList map[string]bool, allowList map[string]bool, +) (map[string][]models.InstanceDetails, error) { + + projects, err := projects.EnumerateProjects(ctx, clientOption, OrgId, OrgId, skipList, allowList) + if err != nil { + return nil, err + } + + m := make(map[string][]models.InstanceDetails, 0) + + for _, project := range projects { + + if helpers.SkipEntry("projects/"+project.ProjectId, skipList, allowList) { + continue + } + + projectInstances, err := EnumerateInstancesInProject(ctx, clientOption, region, project.ProjectId) + if err != nil { + // TODO log error and continue + continue + } + + m[project.Name] = projectInstances + } + + return m, nil +} + +type NwIntfInfo struct { + Ipaddr string `json:"ipAddr,omitempty"` + Ipv6addr string `json:"ipv6Addr,omitempty"` + Kind string `json:"kind,omitempty"` + Name string `json:"name,omitempty"` + NicType string `json:"nicType,omitempty"` + Network string `json:"network,omitempty"` + SubNetwork string `json:"subNetwork,omitempty"` + AccessConfigs []*computepb.AccessConfig `json:"accessConfigs,omitempty"` +} + +func getNetworkInfo(nwIntfs []*computepb.NetworkInterface, tags map[string]string) (string, string, string) { + privateIp := "" + externalIp := "" + vpcId := "" + + for _, intf := range nwIntfs { + + nwInfo := NwIntfInfo{ + Ipaddr: intf.GetNetworkIP(), + Ipv6addr: intf.GetIpv6Address(), + Kind: intf.GetKind(), + Name: intf.GetName(), + NicType: intf.GetNicType(), + Network: intf.GetNetwork(), + SubNetwork: intf.GetSubnetwork(), + AccessConfigs: intf.GetAccessConfigs(), + } + + if nwInfo.Ipaddr != "" && privateIp == "" { + privateIp = nwInfo.Ipaddr + } + + if nwInfo.Ipv6addr != "" && privateIp == "" { + privateIp = nwInfo.Ipv6addr + } + + if nwInfo.Network != "" && vpcId == "" { + vpcId = nwInfo.Network + } + + accessConfigs := intf.GetAccessConfigs() + if len(accessConfigs) != 0 { + for _, accessConfig := range accessConfigs { + natIp := accessConfig.GetNatIP() + externalIpv6Length := accessConfig.GetExternalIpv6() + + if natIp != "" && externalIp == "" { + externalIp = natIp + } + + if externalIpv6Length != "" && externalIp == "" { + externalIp = externalIpv6Length + } + } + } + + nwIntfJson, err := json.Marshal(nwInfo) + if err == nil { + tags["InterfaceInfo:"+nwInfo.Name] = string(nwIntfJson) + } + } + + return privateIp, externalIp, vpcId +} + +func getMetadata(rawMd *computepb.Metadata) map[string]string { + mappedMd := make(map[string]string) + for _, item := range rawMd.GetItems() { + mappedMd[item.GetKey()] = item.GetValue() + } + return mappedMd +} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/models/models.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/models/models.go new file mode 100644 index 000000000..2207a5311 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/models/models.go @@ -0,0 +1,70 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package resources + +import "time" + +type InstanceDetails struct { + InstanceID string `json:"INSTANCE_ID"` + Type string `json:"TYPE"` + State string `json:"STATE"` + Name string `json:"NAME"` + Region string `json:"REGION"` + Zone string `json:"ZONE"` + ImageID string `json:"IMAGE_ID"` + VpcID string `json:"VPC_ID"` + AccountID string `json:"ACCOUNT_ID"` + PublicIP string `json:"PUBLIC_IP"` + PrivateIP string `json:"PRIVATE_IP"` + LaunchTime time.Time `json:"LAUNCH_TIME"` + + // HardwareDetails contains the "expanded out" CPU and Memory information for the instance type. + HardwareDetails HardwareDetails `json:"HARDWARE_DETAILS"` + + Tags map[string]string `json:"TAGS"` + Props map[string]string `json:"PROPS"` + ConfigProps ConfigProps `json:"CONFIG_PROPS"` +} + +type HardwareDetails struct { + CpuVendorID string `json:"CPU_VENDOR_ID"` + CpuModelName string `json:"CPU_MODEL_NAME"` + CpuCores int `json:"CPU_CORES"` + MemoryTotalBytes int64 `json:"MEMORY_TOTAL_BYTES"` + MemoryECCType string `json:"MEMORY_ECC_TYPE"` +} + +type ConfigProps struct { + ScanFrequency int64 `json:"SCAN_FREQUENCY"` + ScanContainers bool `json:"SCAN_CONTAINERS"` + ScanHostVulnerabilities bool `json:"SCAN_HOST_VULNERABILITIES"` + + // Cross-account role used for org access and internal access. + CrossAccountCredentials ConfigCredentialsProps `json:"CROSS_ACCOUNT_CREDENTIALS"` + + // Org-specific properties + ManagementAccount string `json:"MANAGEMENT_ACCOUNT"` + MonitoredAccounts string `json:"MONITORED_ACCOUNTS"` + ScanningAccount string `json:"SCANNING_ACCOUNT"` +} + +type ConfigCredentialsProps struct { + RoleARN string `json:"ROLE_ARN"` + ExternalId string `json:"EXTERNAL_ID"` +} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/projects/projects.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/projects/projects.go new file mode 100644 index 000000000..8b8470dec --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/projects/projects.go @@ -0,0 +1,132 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package resources + +import ( + "context" + "fmt" + + "github.com/lacework/go-sdk/lwcloud/gcp/helpers" + folders "github.com/lacework/go-sdk/lwcloud/gcp/resources/folders" + + resourcemanager "cloud.google.com/go/resourcemanager/apiv3" + resourcemanagerpb "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" + "google.golang.org/api/iterator" + "google.golang.org/api/option" +) + +type ProjectInfo struct { + Name string + Parent string + DisplayName string + ProjectId string +} + +func enumerateTopLevelProjects( + ctx context.Context, clientOption option.ClientOption, ParentId string, + Ancestory string, skipList, allowList map[string]bool, +) ([]ProjectInfo, error) { + + var ( + client *resourcemanager.ProjectsClient + err error + ) + + if clientOption != nil { + client, err = resourcemanager.NewProjectsClient(ctx, clientOption) + } else { + client, err = resourcemanager.NewProjectsClient(ctx) + } + if err != nil { + return nil, fmt.Errorf("cannot enumerate projects in (%s) due to %s", Ancestory, err.Error()) + } + defer client.Close() + + projects := make([]ProjectInfo, 0) + + req := &resourcemanagerpb.ListProjectsRequest{ + Parent: ParentId, + } + + for { + it := client.ListProjects(ctx, req) + + for { + resp, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + return nil, fmt.Errorf("cannot iterate projects in (%s) due to %s", Ancestory, err.Error()) + } + + if helpers.SkipEntry("projects/"+resp.ProjectId, skipList, allowList) { + continue + } + + pi := ProjectInfo{ + Name: resp.Name, + DisplayName: resp.DisplayName, + Parent: resp.Parent, + ProjectId: resp.ProjectId, + } + projects = append(projects, pi) + } + + if req.GetPageToken() == "" { + break + } + + } + return projects, nil +} + +func EnumerateProjects( + ctx context.Context, clientOptions option.ClientOption, ParentId string, + Ancestory string, skipList, allowList map[string]bool, +) ([]ProjectInfo, error) { + + // find top level projects under Parent first + projects, err := enumerateTopLevelProjects(ctx, clientOptions, ParentId, Ancestory, skipList, allowList) + if err != nil { + return projects, err + } + + // find all sub folders first + subFolders, err := folders.EnumerateFolders(ctx, clientOptions, ParentId, Ancestory, skipList, allowList) + if err != nil { + return projects, err + } + + // list all projects in the nested folders + var retError error + for _, folder := range subFolders { + nested_projects, err := enumerateTopLevelProjects(ctx, clientOptions, folder.Name, + folder.Ancestory+" -> "+folder.DisplayName+" ("+folder.Name+")", skipList, allowList) + if err != nil { + // combine errors + retError = helpers.CombineErrors(retError, err) + continue + } + + projects = append(projects, nested_projects...) + } + + return projects, retError +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/DESIGN.md b/vendor/github.com/lacework/go-sdk/lwcomponent/DESIGN.md new file mode 100644 index 000000000..3673e9c7e --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/DESIGN.md @@ -0,0 +1,158 @@ +# Lacework CDK (Cloud Development Kit) + +Proposed by: [Salim Afiune Maya](https://github.com/afiune) + +As the Lacework platform grows and introduces new products and services, our security ecosystem will +have to grow at the same speed (or even faster) to adopt such new products. This design is a proposal +for adopting a new model to deliver tools and libraries to Lacework users. + +## Motivation + +The Lacework CLI was designed with the goal of providing fast, accurate, and actionable insights into +the Lacework platform. The first release [v0.1.0](https://github.com/lacework/go-sdk/releases/tag/v0.1.0) +was on March 27, 2020, and since then, the Lacework CLI has been broadly adopted. A year later, Lacework +released a couple more binaries, and today, it is clear to us that we will adopt this model of releasing +separate binaries for more products and services in the future. + +This model of modular binaries is flexible and will allow us to release features faster to our users, +but it also lacks a cohesive ecosystem, an easy way for users to discover, install, configure and +manage all of these tools. + +This design aims to solve this problem by introducing the concept of **components** into the Lacework CLI, +with these new components, the Lacework CLI will evolve into the SDK realm and therefore, we are proposing +to name it the Lacework CDK (Cloud Development Kit) since it will now provide truly a combination of +tools and libraries to build a robust and flexible ecosystem for our users. + +## Scope + +The main goal of this design is to introduce the concept of components to build a flexible and robust ecosystem +around the Lacework platform. + +This design also aims to: + +* Unify the installation and configuration of tools provided by Lacework +* Help users discover new tools from new Lacework products +* Provide the same experience to manage tools in the Lacework ecosystem +* Allow users to create new tools to enhance security workflows +* Make it easy to manage and distribute libraries and content + +## High-level Diagram + +![Lacework CDK Diagram](imgs/lacework-cdk-high-level-diagram.jpeg) + +## What Are Components? + +A component can be a command-line tool, a new command that extends the Lacework CLI, or a library that +contains files used by another Lacework component. + +### Component Specifications + +Every component should follow the following specifications: + +| Name | Type | Description +| ------ | --------- | ----------------------------------------------------------------------- +| `name` | `string` | The name of the component +| `description` | `string` | A long description that describes the purpose of the component +| `type` | `enum(Type)` | The component type (read more about types here) +| `version` | `string` | The version of the component in semantic format (`MAJOR.MINOR.PATCH`) | +| `artifacts` | `array(Artifact)` | List of artifacts that the component supports +| `dependencies` | `array(Component)` | A list of components that the component depends on + +#### Component Artifact + +A component could run on multiple platforms. Every component has a list of artifacts. An `Artifact` is the actual +component for specific platforms. The specification of an artifact is: + +| Name | Type | Description +| ------ | --------- | ------------------------------------------------------------- +| `os` | `string` | The operating system (`darwin`, `linux`, `windows`) +| `arch` | `string` | The artifact architecture (`amd64`, `386`, `arm64`) +| `size` | `int64` | The artifact size in bytes +| `signature` | `string` | GPG signature of the artifact +| `url` | `string` | The URL from where to download the component artifact + +These specifications are designed to be extensible since they will change as we expand the usage and purpose of +these components. + +The specification of all components will be provided by a new service (`cdk-store`) which will have a semantic +version (`MAJOR.MINOR.PATCH`) that indicates the version of the specifications. If the specifications change, a +new version will be released and our users will get notified. + +### Components Internal Service (`cdk-store`) + +We should have a very lightweight service that will be the single source of truth of all available Lacework components, +this service should fulfill the following use cases: + +* Provide an API to fetch the current state of all Lacework components and their specifications +* Provide an API to define (create) new components, when new components are added to this service, our users will be notified automatically +* Provide an API to deprecate a component, read more about deprecations below +* Provide an API to trigger a sync of a single component, useful for orchestrating release pipelines +* Configure a batch process that runs every 10 minutes to verify that all components are in sync + +### Component Synchronization + +A component synchronization is a task that the internal components' service does to verify the latest version of one +or multiple components, when there is a new version of a component, this task updates the description, version, size, +signature, and dependencies of the component. + +Note that changing the type of the component is discouraged. + +### Component Signature + +As a security company, we need to ensure that any artifact we install on the users' workstation is coming from us, the +execution, installation, and upgrade process will have a requirement that every component should be signed with Lacework's +PGP key, if the downloaded component doesn't match the PGP signature, we should delete the downloaded binary and notify +the user. + +### Create A New Component + +To create a new component, we need to define the following things: + +* Define the component type (`BINARY`, `COMMAND`, or `LIBRARY`) +* For binary components, provide cross-platform binaries and signatures (support `windows`, `linux`, `darwin`) +* Automate the release process via CD pipelines +* Make the first release of the new component +* Add the component to our components internal service (this is when users will discover the new component) + +## Attribution to third-party tools + +A component can be a third-party tool that helps users enable specific workflows such as infrastructure-as-code. +During the installation and upgrade of these types of components, we need to give attribution by including the +copyright and permissions statement. + +One example of a third-party tool we use today is [Terraform](https://github.com/hashicorp/terraform), which allows +Laceworks' users to follow the GitOps methodology to describe their Lacework accounts as code. + +Lacework believes in a growing open-source community, we envision the adoption of other third-party tools that will +help our users improve their security postures. + +## Deprecations + +We aim to adopt this robust ecosystem of tools to release new products, features, bug fixes, and functionalities to +our users as fast as possible, with that speed, there will be times where we will release pieces of a component, or +even an entire component that might not be well received by our users, in those cases our deprecation policy will be: + +* Mark the flag, command, functionality, or component as deprecated +* Communicate to our users about the deprecation +* Wait for 90 days until removal +* Remove the deprecation and release with a major version bump + +## Telemetry and Observability + +We use [Honeycomb](https://www.honeycomb.io/) as our platform to understand how our customers are adopting tools +and integrations we build. Today, it allows us to detect early on when customers are experiencing issues, what +are the most used commands, what are the most common errors our users are experiencing, and more. + +When implementing this component-based model, we need to add end-to-end observability to trace requests that are +initiated at the command line and go all the way to our backend. This means that every component will need to +have the instrumentation to send telemetry to Honeycomb, as well as passing the correct tracing information to +the underlying API endpoint. + +Internally, our API server should accept this tracing information and propagate it to subsequent requests to internal +services, this will allow us to group all of these requests in a single transaction or trace. + +## Open Questions: + +* Will there be components that don't have cross-platform support? If yes, document examples and additional details about how to handle these cases +* ... + diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/api_info.go b/vendor/github.com/lacework/go-sdk/lwcomponent/api_info.go new file mode 100644 index 000000000..728b1d690 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/api_info.go @@ -0,0 +1,38 @@ +package lwcomponent + +import ( + "github.com/Masterminds/semver" +) + +type ApiInfo struct { + Id int32 `json:"id"` + Name string `json:"name"` + Version *semver.Version `json:"version"` + AllVersions []*semver.Version `json:"allVersions"` + Desc string `json:"desc"` + SizeKB int64 `json:"sizeKB"` + Deprecated bool `json:"deprecated"` + ComponentType Type `json:"componentType"` +} + +func NewAPIInfo( + id int32, + name string, + version *semver.Version, + allVersions []*semver.Version, + desc string, + size int64, + deprecated bool, + componentType Type, +) *ApiInfo { + return &ApiInfo{ + Id: id, + Name: name, + Version: version, + AllVersions: allVersions, + Desc: desc, + SizeKB: size, + Deprecated: deprecated, + ComponentType: componentType, + } +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/catalog.go b/vendor/github.com/lacework/go-sdk/lwcomponent/catalog.go new file mode 100644 index 000000000..78d852b4f --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/catalog.go @@ -0,0 +1,468 @@ +package lwcomponent + +import ( + "encoding/xml" + "fmt" + "os" + "path/filepath" + "runtime" + + "aead.dev/minisign" + "github.com/Masterminds/semver" + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/cache" + "github.com/pkg/errors" +) + +const ( + componentCacheDir string = "components" + cdkCacheName string = "cdk_cache" + featureFlag string = "PUBLIC.cdk.v4" + operatingSystem string = runtime.GOOS + architecture string = runtime.GOARCH +) + +func CatalogV1Enabled(client *api.Client) bool { + if os.Getenv("LW_CLI_INTEGRATION_MODE") != "" { + return true + } + response, err := client.V2.FeatureFlags.GetFeatureFlagsMatchingPrefix(featureFlag) + if err != nil { + return false + } + + return len(response.Data.Flags) >= 1 +} + +// Returns the local directory that Components will be stored in. +func CatalogCacheDir() (string, error) { + cacheDir, err := cache.CacheDir() + if err != nil { + return "", errors.Wrap(err, "unable to locate components directory") + } + + path := filepath.Join(cacheDir, componentCacheDir) + + if _, err = os.Stat(path); err != nil { + if err = os.MkdirAll(path, os.ModePerm); err != nil { + return "", err + } + } + + return path, nil +} + +type Catalog struct { + client *api.Client + + Components map[string]CDKComponent + stageConstructor StageConstructor +} + +func (c *Catalog) ComponentCount() int { + return len(c.Components) +} + +// Return a CDKComponent that is present on the host. +func (c *Catalog) GetComponent(name string) (*CDKComponent, error) { + component, exists := c.Components[name] + if !exists { + return nil, errors.New(fmt.Sprintf("component %s not found", name)) + } + + return &component, nil +} + +func (c *Catalog) ListComponentVersions(component *CDKComponent) ([]*semver.Version, error) { + if component.ApiInfo == nil { + return nil, errors.Errorf("component '%s' api info not available", component.Name) + } + + if component.ApiInfo.AllVersions != nil { + return component.ApiInfo.AllVersions, nil + } + + return listComponentVersions(c.client, component.ApiInfo.Id) +} + +func (c *Catalog) PrintComponents() [][]string { + result := [][]string{} + + for _, component := range c.Components { + result = append(result, component.PrintSummary()) + } + + return result +} + +func (c *Catalog) Stage( + component *CDKComponent, + version string, + progressClosure func(filepath string, sizeB int64)) (stageClose func(), err error) { + var ( + semv *semver.Version + ) + + stageClose = func() {} + + if version == "" { + semv = component.ApiInfo.Version + } else { + semv, err = semver.NewVersion(version) + if err != nil { + return + } + } + + if component.HostInfo != nil { + var installedVersion *semver.Version + + installedVersion, err = component.HostInfo.Version() + if err != nil { + return + } + + if installedVersion.Equal(semv) { + err = errors.Errorf("version '%s' already installed", semv.String()) + return + } + } + + response, err := c.client.V2.Components.FetchComponentArtifact( + component.ApiInfo.Id, + operatingSystem, + architecture, + semv.String()) + if err != nil { + return + } + + if len(response.Data) == 0 { + err = errors.New("Invalid API response") + return + } + + data := response.Data[0] + + component.InstallMessage = data.InstallMessage + component.UpdateMessage = data.UpdateMessage + + stage, err := c.stageConstructor(component.Name, data.ArtifactUrl, data.Size) + if err != nil { + return + } + + if err = stage.Download(progressClosure); err != nil { + stage.Close() + return + } + + err = parseAWSXMLError(filepath.Join(stage.Directory(), stage.Filename())) + if err != nil { + return + } + + if err = stage.Unpack(); err != nil { + stage.Close() + return + } + + if err = stage.Validate(); err != nil { + stage.Close() + return + } + + component.stage = stage + stageClose = stage.Close + + return +} + +func (c *Catalog) Verify(component *CDKComponent) error { + path := filepath.Join(component.stage.Directory(), component.Name) + + if operatingSystem == "windows" { + path = fmt.Sprintf("%s.exe", path) + } + + data, err := os.ReadFile(path) + if err != nil { + return err + } + + sig, err := component.stage.Signature() + if err != nil { + return err + } + + rootPublicKey := minisign.PublicKey{} + if err := rootPublicKey.UnmarshalText([]byte(publicKey)); err != nil { + return errors.Wrap(err, "unable to load root public key") + } + + return verifySignature(rootPublicKey, data, sig) +} + +func (c *Catalog) Install(component *CDKComponent) error { + if component.stage == nil { + return errors.Errorf("component '%s' not staged", component.Name) + } + + componentDir, err := componentDirectory(component.Name) + if err != nil { + return err + } + + err = os.MkdirAll(componentDir, os.ModePerm) + if err != nil { + return err + } + + err = component.stage.Commit(componentDir) + if err != nil { + return err + } + + component.HostInfo, err = NewHostInfo(componentDir, component.Description, component.Type) + if err != nil { + return err + } + + path := filepath.Join(componentDir, component.Name) + + if operatingSystem == "windows" { + path = fmt.Sprintf("%s.exe", path) + } + + if component.ApiInfo != nil && + (component.ApiInfo.ComponentType == BinaryType || component.ApiInfo.ComponentType == CommandType) { + if err := os.Chmod(path, 0744); err != nil { + return errors.Wrap(err, "unable to make component executable") + } + } + + return nil +} + +// Delete a CDKComponent +// +// Remove the Component install directory and all sub-directory. This function will not return an +// error if the Component is not installed. +func (c *Catalog) Delete(component *CDKComponent) error { + componentDir, err := componentDirectory(component.Name) + if err != nil { + return err + } + + _, err = os.Stat(componentDir) + if err != nil { + return errors.Errorf("component not installed. Try running 'lacework component install %s'", component.Name) + } + + return os.RemoveAll(componentDir) +} + +func NewCatalog( + client *api.Client, + stageConstructor StageConstructor, +) (*Catalog, error) { + if stageConstructor == nil { + return nil, errors.New("StageConstructor is not specified to create new catalog") + } + + response, err := client.V2.Components.ListComponents(operatingSystem, architecture) + if err != nil { + return nil, err + } + + var rawComponents []api.LatestComponentVersion + + if len(response.Data) > 0 { + rawComponents = response.Data[0].Components + } + + cdkComponents := make(map[string]CDKComponent, len(rawComponents)) + + for _, c := range rawComponents { + ver, err := semver.NewVersion(c.Version) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("component '%s' version '%s'", c.Name, c.Version)) + } + + var allVersions []*semver.Version + + apiInfo := NewAPIInfo(c.Id, c.Name, ver, allVersions, c.Description, c.Size, c.Deprecated, Type(c.ComponentType)) + cdkComponents[c.Name] = NewCDKComponent(apiInfo, nil) + } + + components, err := mergeComponents(cdkComponents) + if err != nil { + return nil, err + } + + return &Catalog{client, components, stageConstructor}, nil +} + +func NewCachedCatalog( + client *api.Client, + stageConstructor StageConstructor, + cachedComponentsApiInfo map[string]*ApiInfo, +) (*Catalog, error) { + if stageConstructor == nil { + return nil, errors.New("StageConstructor is not specified to create new catalog") + } + + cachedComponents := make(map[string]CDKComponent, len(cachedComponentsApiInfo)) + + for _, apiInfo := range cachedComponentsApiInfo { + cachedComponents[apiInfo.Name] = NewCDKComponent(apiInfo, nil) + } + + components, err := mergeComponents(cachedComponents) + if err != nil { + return nil, err + } + + return &Catalog{client, components, stageConstructor}, nil +} + +// mergeComponents combines the passed in components with the local components +func mergeComponents(components map[string]CDKComponent) (allComponents map[string]CDKComponent, err error) { + localComponents, err := LoadLocalComponents() + if err != nil { + return + } + + allComponents = make(map[string]CDKComponent, len(localComponents)+len(components)) + + for _, c := range components { + var hostInfo *HostInfo + component, ok := localComponents[c.Name] + if ok { + hostInfo = component.HostInfo + delete(localComponents, c.Name) + } + allComponents[c.Name] = NewCDKComponent(c.ApiInfo, hostInfo) + } + + for _, c := range localComponents { + allComponents[c.Name] = c + } + + return +} + +func LoadLocalComponents() (components map[string]CDKComponent, err error) { + cacheDir, err := CatalogCacheDir() + if err != nil { + return + } + + subDir, err := os.ReadDir(cacheDir) + if err != nil { + return + } + + components = make(map[string]CDKComponent, len(subDir)) + + // Prototype backwards compatibility + prototypeState, err := LocalState() + if err != nil { + prototypeState = new(State) + err = nil + } + prototypeComponents := make(map[string]Component, len(prototypeState.Components)) + for _, component := range prototypeState.Components { + prototypeComponents[component.Name] = component + } + + for _, file := range subDir { + if !file.IsDir() { + continue + } + + hostInfo, _ := LoadHostInfo(filepath.Join(cacheDir, file.Name())) + if hostInfo == nil { + component, found := prototypeComponents[file.Name()] + if !found { + continue + } + + hostInfo, err = NewHostInfo(filepath.Join(cacheDir, file.Name()), component.Description, component.Type) + if err != nil { + return nil, err + } + } + + if hostInfo.Development() { + _, err := newDevInfo(hostInfo.Dir) + if err != nil { + return nil, err + } + components[hostInfo.Name] = NewCDKComponent(nil, hostInfo) + } else { + components[hostInfo.Name] = NewCDKComponent(nil, hostInfo) + } + } + + return +} + +func listComponentVersions(client *api.Client, componentId int32) ([]*semver.Version, error) { + response, err := client.V2.Components.ListComponentVersions(componentId, operatingSystem, architecture) + if err != nil { + return nil, err + } + + var rawVersions []string + + if len(response.Data) > 0 { + rawVersions = response.Data[0].Versions + } + + versions := make([]*semver.Version, len(rawVersions)) + + for idx, v := range rawVersions { + ver, err := semver.NewVersion(v) + if err != nil { + return nil, err + } + + versions[idx] = ver + } + + return versions, nil +} + +// Returns the directory that the component executable and configuration is stored in. +func componentDirectory(componentName string) (string, error) { + dir, err := CatalogCacheDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, componentName), nil +} + +type awsXMLError struct { + xml.Name + Code string `xml:"Code"` + Message string `xml:"Message"` +} + +func parseAWSXMLError(path string) error { + data, err := os.ReadFile(path) + if err != nil { + return err + } + + xmlError := &awsXMLError{} + err = xml.Unmarshal(data, xmlError) + if err != nil { + return nil + } + + log.Error(string(data)) + + return errors.Errorf("Code: %s. Message: %s", xmlError.Code, xmlError.Message) +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_component.go b/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_component.go new file mode 100644 index 000000000..259380496 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_component.go @@ -0,0 +1,239 @@ +package lwcomponent + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/Masterminds/semver" + "github.com/fatih/color" + "github.com/lacework/go-sdk/internal/file" + "github.com/pkg/errors" +) + +const ( + DevelopmentEnv = "LW_CDK_DEV_COMPONENT" +) + +type CDKComponent struct { + Name string `json:"name"` + Description string `json:"description"` + Type Type `json:"type"` + Status Status `json:"-"` + InstallMessage string `json:"-"` + UpdateMessage string `json:"-"` + + Exec Executer `json:"-"` + + ApiInfo *ApiInfo `json:"apiInfo,omitempty"` + HostInfo *HostInfo `json:"-"` + stage Stager +} + +func NewCDKComponent(apiInfo *ApiInfo, hostInfo *HostInfo) CDKComponent { + var ( + exec Executer = &nonExecutable{} + ) + + status := status(apiInfo, hostInfo) + + switch status { + case Installed, UpdateAvailable, InstalledDeprecated, Development: + { + dir := hostInfo.Dir + + if hostInfo.ComponentType == BinaryType || hostInfo.ComponentType == CommandType { + exec = NewExecuable(hostInfo.Name, dir) + } + } + default: + { + + } + } + + if apiInfo != nil { + return CDKComponent{ + Name: apiInfo.Name, + Description: apiInfo.Desc, + Type: apiInfo.ComponentType, + Status: status, + Exec: exec, + ApiInfo: apiInfo, + HostInfo: hostInfo, + } + } + + if hostInfo != nil { + return CDKComponent{ + Name: hostInfo.Name, + Description: hostInfo.Desc, + Type: hostInfo.ComponentType, + Status: status, + Exec: exec, + ApiInfo: apiInfo, + HostInfo: hostInfo, + } + } + + return CDKComponent{} +} + +func (c *CDKComponent) Dir() (string, error) { + dir, err := CatalogCacheDir() + if err != nil { + return "", err + } + + return filepath.Join(dir, c.Name), nil +} + +func (c *CDKComponent) EnterDevMode() error { + if c.HostInfo != nil && c.HostInfo.Development() { + return errors.New("component already under development.") + } + + dir, err := c.Dir() + if err != nil { + return errors.New("unable to detect RootPath") + } + + devFile := filepath.Join(dir, DevelopmentFile) + if !file.FileExists(devFile) { + devInfo := &DevInfo{ + ComponentType: c.Type, + Desc: fmt.Sprintf("(dev-mode) %s", c.Description), + Name: c.Name, + Version: "0.0.0-dev", + } + + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(devInfo); err != nil { + return err + } + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + + return os.WriteFile(devFile, buf.Bytes(), 0644) + } + + return nil +} + +func (c *CDKComponent) InstalledVersion() *semver.Version { + if c.HostInfo != nil { + version, err := c.HostInfo.Version() + if err == nil { + return version + } + + if componentDir, err := c.Dir(); err == nil { + if devInfo, err := newDevInfo(componentDir); err == nil { + version, err = semver.NewVersion(devInfo.Version) + if err == nil { + return version + } + } + } + } + + return nil +} + +func (c *CDKComponent) LatestVersion() *semver.Version { + if c.ApiInfo != nil { + return c.ApiInfo.Version + } + + return nil +} + +func (c *CDKComponent) PrintSummary() []string { + var ( + colorize *color.Color + version *semver.Version + ) + + switch c.Status { + case Installed, InstalledDeprecated, UpdateAvailable, Development, Tainted: + version = c.InstalledVersion() + case NotInstalled, NotInstalledDeprecated: + version = c.ApiInfo.Version + default: + version = &semver.Version{} + } + + colorize = c.Status.Color() + + return []string{ + colorize.Sprintf(c.Status.String()), + c.Name, + version.String(), + c.Description, + } +} + +func status(apiInfo *ApiInfo, hostInfo *HostInfo) Status { + status := UnknownStatus + + if hostInfo != nil { + if hostInfo.Development() { + return Development + } + + if err := hostInfo.Validate(); err != nil { + return UnknownStatus + } + + if apiInfo != nil { + installedVer, err := hostInfo.Version() + if err != nil { + return UnknownStatus + } + + if isTainted(apiInfo, installedVer) { + return Tainted + } + + if apiInfo.Deprecated { + return InstalledDeprecated + } + + latestVer := apiInfo.Version + if latestVer.GreaterThan(installedVer) { + return UpdateAvailable + } else { + return Installed + } + } else { + return InstalledDeprecated + } + } + + if apiInfo != nil && hostInfo == nil { + if apiInfo.Deprecated { + return NotInstalledDeprecated + } + + return NotInstalled + } + + return status +} + +func isTainted(apiInfo *ApiInfo, installedVer *semver.Version) bool { + if len(apiInfo.AllVersions) == 0 { + return false + } + + for _, ver := range apiInfo.AllVersions { + if ver.Equal(installedVer) { + return false + } + } + return true +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_executable.go b/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_executable.go new file mode 100644 index 000000000..01ef2a3e6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_executable.go @@ -0,0 +1,119 @@ +package lwcomponent + +import ( + "bytes" + "os" + "os/exec" + "path/filepath" + "runtime" + + "github.com/pkg/errors" +) + +var ( + ErrNonExecutable error = errors.New("component not executable") + ErrRun string = "unable to run component" +) + +type Executer interface { + Executable() bool + + Execute(args []string, envs ...string) (stdout string, stderr string, err error) + + ExecuteInline(args []string, envs ...string) (err error) + + Path() string +} + +type executable struct { + path string +} + +func NewExecuable(name string, dir string) Executer { + path := filepath.Join(dir, name) + if runtime.GOOS == "windows" { + path += ".exe" + } + + return &executable{path: path} +} + +func (e *executable) Path() string { + return e.path +} + +func (e *executable) Executable() bool { + return true +} + +func (e *executable) Execute(args []string, envs ...string) (stdout string, stderr string, err error) { + return execute(e.path, args, envs...) +} + +func (e *executable) ExecuteInline(args []string, envs ...string) (err error) { + return executeInline(e.path, args, envs...) +} + +func execute(path string, args []string, envs ...string) (stdout string, stderr string, err error) { + var outBuf, errBuf bytes.Buffer + + cmd := exec.Command(path, args...) + + cmd.Env = append(os.Environ(), envs...) + + cmd.Stdin = nil + cmd.Stdout = &outBuf + cmd.Stderr = &errBuf + + err = run(cmd) + + stdout, stderr = outBuf.String(), errBuf.String() + + return +} + +func executeInline(path string, args []string, envs ...string) error { + cmd := exec.Command(path, args...) + + cmd.Env = append(os.Environ(), envs...) + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return run(cmd) +} + +func run(cmd *exec.Cmd) error { + if err := cmd.Run(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + return &RunError{ + Err: err, + Message: ErrRun, + ExitCode: exitError.ExitCode(), + } + } + return errors.Wrap(err, ErrRun) + } + + return nil +} + +type nonExecutable struct { +} + +func (e *nonExecutable) Executable() bool { + return false +} + +func (e *nonExecutable) Execute(args []string, envs ...string) (stdout string, stderr string, err error) { + return "", "", ErrNonExecutable +} + +func (e *nonExecutable) ExecuteInline(args []string, envs ...string) (err error) { + return ErrNonExecutable +} + +func (e *nonExecutable) Path() string { + return "" +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/component.go b/vendor/github.com/lacework/go-sdk/lwcomponent/component.go new file mode 100644 index 000000000..6d41a8df8 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/component.go @@ -0,0 +1,769 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A development kit for the cloud based of modular components. +package lwcomponent + +import ( + "bytes" + _ "embed" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "sort" + "strings" + "time" + + "aead.dev/minisign" + "github.com/Masterminds/semver" + "github.com/cenkalti/backoff/v4" + dircopy "github.com/otiai10/copy" + "github.com/pkg/errors" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/archive" + "github.com/lacework/go-sdk/internal/cache" + "github.com/lacework/go-sdk/internal/file" +) + +// State holds the components specification +// +// You can load the state from the Lacework API server by passing an `api.Client`. +// +// client, err := api.NewClient(account, opts...) +// cState, err := lwcomponent.LoadState(client) +// +// Or, you can load the state from the local storage. +// +// cState, err := lwcomponent.LocalState() +type State struct { + Version string `json:"version"` + Components []Component `json:"components"` +} + +// LoadState loads the state from the Lacework API server +func LoadState(client *api.Client) (*State, error) { + if client != nil { + s := new(State) + + // load remote components - this involves a network call that may fail due + // to network issues or rate limits, so we retry this if it fails + err := backoff.Retry(func() error { + return client.RequestDecoder("GET", "v2/Components", nil, s) + }, backoffStrategy()) + if err != nil { + return s, err + } + + // load local components + s.loadComponentsFromDisk() + + // load dev components + s.loadDevComponents() + + return s, s.WriteState() + } + return nil, errors.New("invalid api client") +} + +func backoffStrategy() *backoff.ExponentialBackOff { + strategy := backoff.NewExponentialBackOff() + strategy.InitialInterval = 2 * time.Second + strategy.MaxElapsedTime = 1 * time.Minute + return strategy +} + +// loadComponentsFromDisk will load all component from disk (local) +func (s *State) loadComponentsFromDisk() { + if dir, err := Dir(); err == nil { + components, err := os.ReadDir(dir) + if err != nil { + return + } + + // traverse components dir + for _, c := range components { + if !c.IsDir() { + continue + } + + // load components that are not already registered + if _, found := s.GetComponent(c.Name()); !found { + component := Component{Name: c.Name()} + + // verify that the directory is a component, that means that the + // directory contains either a '.dev' file or, both '.version' + // and '.signature' files + // + // TODO @afiune maybe we should deploy a .specs file? + err := component.isVerified() + + if component.UnderDevelopment() || err == nil { + s.Components = append(s.Components, component) + } + } + } + } +} + +// loadDevComponents will load all components that are under development +func (s *State) loadDevComponents() { + for i := range s.Components { + if s.Components[i].UnderDevelopment() { + // existing component being developed + if err := s.Components[i].loadDevSpecs(); err != nil { + s.Components[i].Description = err.Error() + } + } + } + + if devComponent := os.Getenv("LW_CDK_DEV_COMPONENT"); devComponent != "" { + // component is not yet defined, add it to the state + dev := Component{Name: devComponent} + if err := dev.loadDevSpecs(); err != nil { + dev.Description = err.Error() + } + s.Components = append(s.Components, dev) + } +} + +// LocalState loads the state from the local storage ("Dir()/state") +func LocalState() (*State, error) { + state := new(State) + componentsFile, err := Dir() + if err != nil { + return state, err + } + + stateFile := filepath.Join(componentsFile, "state") + + stateBytes, err := os.ReadFile(stateFile) + if err != nil { + return state, err + } + if err := json.Unmarshal(stateBytes, state); err != nil { + return state, err + } + + // load local components + state.loadComponentsFromDisk() + + // load dev components + state.loadDevComponents() + + return state, nil +} + +// GetComponent returns the pointer of a component, if the component is not +// found, this function will return a `nil` pointer and `false` +// +// Usage: +// +// component, found := state.GetComponent(name) +// +// if !found { +// fmt.Println("Component %s not found", name) +// } +func (s State) GetComponent(name string) (*Component, bool) { + for i := range s.Components { + if s.Components[i].Name == name { + return &s.Components[i], true + } + } + return nil, false +} + +// WriteState stores the components state to disk +func (s State) WriteState() error { + dir, err := Dir() + if err != nil { + return err + } + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + + stateFile := filepath.Join(dir, "state") + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(s); err != nil { + return err + } + + if err := os.WriteFile(stateFile, buf.Bytes(), 0644); err != nil { + return err + } + + return nil +} + +// Dir returns the directory where the components will be stored +func Dir() (string, error) { + cacheDir, err := cache.CacheDir() + if err != nil { + return "", errors.Wrap(err, "unable to locate components directory") + } + return filepath.Join(cacheDir, "components"), nil +} + +func (s State) Install(component *Component, version string, progressClosure func(path string, sizeB int64)) error { + rPath, err := component.RootPath() + if err != nil { + return err + } + + // verify development mode + if component.UnderDevelopment() { + p, _ := component.Path() // @afiune we don't care if the component exists or not + msg := "components under development can't be installed.\n\n" + + "Deploy the component manually at '" + p + "'" + return errors.New(msg) + } + + // @afiune verify if component is in latest + artifact, found := component.ArtifactForRunningHost(version) + if !found { + return errors.Errorf( + "could not find an artifact for version %s on the current platform (%s/%s)", + version, runtime.GOOS, runtime.GOARCH, + ) + } + + path, err := component.Path() + // it is ok if the component is not found yet + if err != nil && !IsNotFound(err) { + return err + } + + // download to temp dir, this must be different from staging dir + // because the archive may have the same name as the extracted folder + downloadDir, err := os.MkdirTemp("", "cdk-component-stage-download") + if err != nil { + return err + } + defer os.RemoveAll(downloadDir) + downloadPath := filepath.Join(downloadDir, component.Name) + + // Stage to temp dir before installing + stagingDir, err := os.MkdirTemp("", "cdk-component-stage-extract") + if err != nil { + return err + } + defer os.RemoveAll(stagingDir) + + stagingPath := filepath.Join(stagingDir, component.Name) + + if _, err = os.Create(downloadPath); err != nil { + return err + } + + // There is no artifact.Size so we pass 0 + go progressClosure(downloadPath, 0) + + err = DownloadFile(downloadPath, artifact.URL) + if err != nil { + return errors.Wrap(err, "unable to download component artifact") + } + + // if the component is a tgz archive unpack it, otherwise leave it alone + if err = archive.DetectTGZAndUnpack(downloadPath, stagingDir); err != nil { + return err + } + + //if the component was not an archive then nothing was created in the staging dir + //we must move it over + if _, err := os.Stat(stagingPath); errors.Is(err, os.ErrNotExist) { + err = file.Copy(downloadPath, stagingPath) + if err != nil { + return err + } + } + + // if the component is not an archive make a dir for it to live in + f, err := os.Stat(stagingPath) + if err != nil { + return err + } + if !f.IsDir() { + if err := os.MkdirAll(rPath, os.ModePerm); err != nil { + return err + } + //move the component from the staging dir to it's path + if err = file.Copy(stagingPath, path); err != nil { + return err + } + } else { + //move the component from the staging dir to it's root path + if err = dircopy.Copy(stagingPath, rPath); err != nil { + return err + } + } + + if err := component.WriteVersion(artifact.Version); err != nil { + return err + } + + // @afiune check 1) cross-platform and 2) correct permissions + // if the file has permissions already, can we avoid this? + if component.IsExecutable() { + if err := os.Chmod(path, 0744); err != nil { + return errors.Wrap(err, "unable to make component executable") + } + } + + return nil +} + +func (s State) Verify(component *Component, version string) error { + artifact, found := component.ArtifactForRunningHost(version) + if !found { + return errors.Errorf( + "could not find an artifact for version %s on the current platform (%s/%s)", + version, runtime.GOOS, runtime.GOARCH, + ) + } + + if err := component.WriteSignature([]byte(artifact.Signature)); err != nil { + return err + } + + rPath, err := component.RootPath() + if err != nil { + return err + } + + // verify component + if err := component.isVerified(); err != nil { + // @afiune notify and remove installed component + defer os.RemoveAll(rPath) + return err + } + + return nil +} + +var ( + baseRunErr string = "unable to run component" +) + +type Artifact struct { + OS string `json:"os"` + ARCH string `json:"arch"` + URL string `json:"url,omitempty"` + Signature string `json:"signature"` + Version string `json:"version"` + UpdateMessage string `json:"updateMessage"` + //Size ? +} + +// Components should leave a trail/crumb after installation or update, +// these messages will be shown by the Lacework CLI +type Breadcrumbs struct { + InstallationMessage string `json:"installationMessage,omitempty"` + UpdateMessage string `json:"updateMessage,omitempty"` +} + +// Component can be a command-line tool, a new command that extends the Lacework CLI, or a library that +// contains files used by another Lacework component. +type Component struct { + Name string `json:"name"` + Description string `json:"description"` + Type Type `json:"type"` + LatestVersion semver.Version `json:"-"` + Artifacts []Artifact `json:"artifacts"` + Breadcrumbs Breadcrumbs `json:"breadcrumbs,omitempty"` + + // @dhazekamp command_name required when CLICommand is true? + CommandName string `json:"command_name,omitempty"` +} + +func (c *Component) UnmarshalJSON(data []byte) error { + type ComponentAlias Component + type T struct { + *ComponentAlias `json:",inline"` + LatestVersionString string `json:"version"` + } + + temp := &T{ComponentAlias: (*ComponentAlias)(c)} + err := json.Unmarshal(data, temp) + if err != nil { + return err + } + + latestVersion, err := semver.NewVersion(temp.LatestVersionString) + if err != nil { + return err + } + c.LatestVersion = *latestVersion + + return nil +} + +func (c Component) MarshalJSON() ([]byte, error) { + type ComponentAlias Component + type T struct { + ComponentAlias `json:",inline"` + LatestVersionString string `json:"version"` + } + + obj := &T{ComponentAlias: (ComponentAlias)(c), LatestVersionString: c.LatestVersion.String()} + return json.Marshal(obj) +} + +// RootPath returns the component's root path ("Dir()/{name}") +func (c Component) RootPath() (string, error) { + dir, err := Dir() + if err != nil { + return "", err + } + + return filepath.Join(dir, c.Name), nil +} + +// Path returns the path to the component ("RootPath()/{name}") +func (c Component) Path() (string, error) { + dir, err := c.RootPath() + if err != nil { + return "", err + } + + // @afiune maybe component/version/bin + // but why would we want older versions? + cPath := filepath.Join(dir, c.Name) + if runtime.GOOS == "windows" { + cPath += ".exe" + } + + if file.FileExists(cPath) { + return cPath, nil + } + return cPath, ErrComponentNotFound +} + +// CurrentVersion returns the current installed version of the component +func (c Component) CurrentVersion() (*semver.Version, error) { + // development mode, avoid loading the current version, + // return latest which is what's inside the '.dev' specs + if c.UnderDevelopment() { + return &c.LatestVersion, nil + } + + dir, err := c.RootPath() + if err != nil { + return nil, err + } + + cvPath := filepath.Join(dir, ".version") + if !file.FileExists(cvPath) { + // @afiune help the user fix this issue with a better error message + return nil, errors.New("component version file does not exist") + } + + dat, err := os.ReadFile(cvPath) + if err != nil { + return nil, errors.Wrap(err, "unable to read component version file") + } + + cv, err := semver.NewVersion(strings.TrimSpace(string(dat))) + if err != nil { + err = errors.New("unable to parse component version") + } + return cv, err +} + +// SignatureFromDisk returns the component signature stored on disk ("RootPath()/.signature") +func (c Component) SignatureFromDisk() ([]byte, error) { + var sig []byte + + dir, err := c.RootPath() + if err != nil { + return nil, err + } + + csPath := filepath.Join(dir, ".signature") + if !file.FileExists(csPath) { + return sig, errors.New("component signature file does not exist") + } + + sig, err = os.ReadFile(csPath) + if err != nil { + return sig, errors.Wrap(err, "unable to read component signature file") + } + + // Artifact signature may or may not be b64encoded + decoded_sig, err := base64.StdEncoding.DecodeString(string(sig)) + if err == nil { + sig = decoded_sig + } + + return sig, nil +} + +// WriteSignature stores the component signature on disk +func (c Component) WriteSignature(signature []byte) error { + dir, err := c.RootPath() + if err != nil { + return err + } + + cvPath := filepath.Join(dir, ".signature") + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + + return os.WriteFile(cvPath, signature, 0644) +} + +// WriteVersion stores the component version on disk +func (c Component) WriteVersion(installed string) error { + dir, err := c.RootPath() + if err != nil { + return err + } + + cvPath := filepath.Join(dir, ".version") + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + + if installed == "" { + installed = c.LatestVersion.String() + } + return os.WriteFile(cvPath, []byte(installed), 0644) +} + +// UpdateAvailable returns true if there is a newer version of the component +func (c Component) UpdateAvailable() (bool, error) { + cv, err := c.CurrentVersion() + if err != nil { + return false, err + } + + return c.LatestVersion.GreaterThan(cv), nil +} + +// Status returns the component status +func (c Component) Status() Status { + _, err := c.Path() + if err == nil { + // check if the component has an update + update, err := c.UpdateAvailable() + if err == nil && update { + return UpdateAvailable + + } + return Installed + } + if IsNotFound(err) { + return NotInstalled + } + return UnknownStatus +} + +// ArtifactForRunningHost returns the right component artifact for the running host, +func (c Component) ArtifactForRunningHost(version string) (*Artifact, bool) { + for _, artifact := range c.Artifacts { + if artifact.OS == runtime.GOOS && artifact.ARCH == runtime.GOARCH && artifact.Version == version { + return &artifact, true + } + } + return nil, false +} + +// loadDevSpecs will lookup for the '.dev' specs file under the +// component root path to load it into the component itself +func (c *Component) loadDevSpecs() error { + dir, err := c.RootPath() + if err != nil { + return errors.New("unable to detect RootPath") + } + + devSpecs := filepath.Join(dir, ".dev") + if file.FileExists(devSpecs) { + devSpecsBytes, err := os.ReadFile(devSpecs) + if err != nil { + return errors.Errorf("unable to read %s file", devSpecs) + } + err = json.Unmarshal(devSpecsBytes, c) + if err != nil { + return errors.Errorf("unable to unmarshal %s file", devSpecs) + } + } else { + return errors.Errorf("create dev specs file '%s'", devSpecs) + } + + return nil +} + +// UnderDevelopment returns true if the component is under development +// that is, if the component root path has the '.dev' specs file or, if +// the environment variable 'LW_CDK_DEV_COMPONENT' matches the component name +func (c Component) UnderDevelopment() bool { + if os.Getenv("LW_CDK_DEV_COMPONENT") == c.Name { + return true + } + + dir, err := c.RootPath() + if err != nil { + return false + } + + return file.FileExists(filepath.Join(dir, ".dev")) +} + +// isVerified checks if the component has a valid signature +func (c Component) isVerified() error { + // development mode, avoid verifying + if c.UnderDevelopment() { + return nil + } + + // get component signature + sig, err := c.SignatureFromDisk() + if err != nil { + return err + } + + // get component path + cPath, err := c.Path() + if err != nil { + return err + } + + // open the component + f, err := os.ReadFile(cPath) + if err != nil { + return errors.New("unable to read component file") + } + + // load public key + rootPublicKey := minisign.PublicKey{} + if err := rootPublicKey.UnmarshalText([]byte(publicKey)); err != nil { + return errors.Wrap(err, "unable to load root public key") + } + + // validate the signature + return verifySignature(rootPublicKey, f, sig) +} + +func (c Component) EnterDevelopmentMode() error { + if c.UnderDevelopment() { + return errors.New("component already under development.") + } + + dir, err := c.RootPath() + if err != nil { + return errors.New("unable to detect RootPath") + } + + devSpecs := filepath.Join(dir, ".dev") + if !file.FileExists(devSpecs) { + // remove prod artifacts + c.Artifacts = make([]Artifact, 0) + + // configure dev version + cv, _ := semver.NewVersion("0.0.0-dev") + c.LatestVersion = *cv + + // update description + c.Description = fmt.Sprintf("(dev-mode) %s", c.Description) + + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(c); err != nil { + return err + } + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + + return os.WriteFile(devSpecs, buf.Bytes(), 0644) + } + + return nil +} + +func (c Component) getVersionsAndBreadcrumbs() ([]*semver.Version, map[semver.Version]string) { + versionToBreadcrumb := make(map[semver.Version]string) + allVersions := make([]*semver.Version, 0) + versionToBreadcrumb[c.LatestVersion] = c.Breadcrumbs.UpdateMessage + allVersions = append(allVersions, &c.LatestVersion) + for _, artifact := range c.Artifacts { + parsedVersion, err := semver.NewVersion(artifact.Version) + if err == nil { + // We don't expect invalid versions from the server, but if we do get them + // let's just recover and ignore that version rather than crashing. + _, alreadySeen := versionToBreadcrumb[*parsedVersion] + if !alreadySeen { + versionToBreadcrumb[*parsedVersion] = artifact.UpdateMessage + allVersions = append(allVersions, parsedVersion) + } + } + } + sort.Sort(semver.Collection(allVersions)) + return allVersions, versionToBreadcrumb +} + +func (c Component) MakeUpdateMessage(from, to semver.Version) string { + if from.LessThan(&to) { + versions, breadcrumbs := c.getVersionsAndBreadcrumbs() + updateMessage := "" + for _, version := range versions { + if version.LessThan(&from) || version.Equal(&from) { + // We're before the breadcrumbs we care about, we don't include this one but keep going + continue + } + if version.GreaterThan(&to) { + // We're past the breadcrumbs we care about, we can stop iterating + break + } + if breadcrumbs[*version] != "" { + // We've found a breadcrumb in the range (from, to] which is one we care about + updateMessage += "\n" + updateMessage += breadcrumbs[*version] + } + } + return fmt.Sprintf("Successfully upgraded component from %s to %s%s", from.String(), to.String(), updateMessage) + } + return fmt.Sprintf("Successfully downgraded component from %s to %s", from.String(), to.String()) +} + +func (c Component) ListVersions(installed *semver.Version) string { + versions, _ := c.getVersionsAndBreadcrumbs() + result := "The following versions of this component are available to install:" + foundInstalled := false + for _, version := range versions { + result += "\n" + result += " - " + version.String() + if installed != nil && version.Equal(installed) { + result += " (installed)" + foundInstalled = true + } + } + if installed != nil && !foundInstalled { + result += fmt.Sprintf( + "\n\nThe currently installed version %s is no longer available to install.", + installed.String(), + ) + } + return result +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/dev_info.go b/vendor/github.com/lacework/go-sdk/lwcomponent/dev_info.go new file mode 100644 index 000000000..2b05c7846 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/dev_info.go @@ -0,0 +1,41 @@ +package lwcomponent + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/Masterminds/semver" + "github.com/pkg/errors" +) + +type DevInfo struct { + ComponentType Type + Desc string + Name string + Version string +} + +func newDevInfo(dir string) (*DevInfo, error) { + path := filepath.Join(dir, DevelopmentFile) + + data, err := os.ReadFile(path) + if err != nil { + return nil, errors.Errorf("unable to read %s file", path) + } + + info := DevInfo{} + + err = json.Unmarshal(data, &info) + if err != nil { + return nil, errors.Errorf("unable to unmarshal %s file", path) + } + + _, err = semver.NewVersion(info.Version) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("development component '%s' version '%s'", info.Name, info.Version)) + } + + return &info, nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/error.go b/vendor/github.com/lacework/go-sdk/lwcomponent/error.go new file mode 100644 index 000000000..04d010d95 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/error.go @@ -0,0 +1,38 @@ +package lwcomponent + +import ( + "fmt" + + "github.com/pkg/errors" +) + +var ( + ErrComponentNotFound = errors.New("component not found on disk") +) + +// IsNotFound returns a boolean indicating whether the error is known to +// have determined the component is not found. It is satisfied by +// ErrNotApplyComment +func IsNotFound(err error) bool { + return errors.Is(err, ErrComponentNotFound) +} + +// RunError is a struct used to pass an error when a component tries to run and +// it fails, a few functions will return this error so that callers (upstream +// packages) can unwrap and identify that the error comes from this package +type RunError struct { + ExitCode int + Message string + Err error +} + +func (e *RunError) Error() string { + if e.ExitCode == 0 { + return "" + } + return fmt.Sprintf("%s: %s", e.Message, e.Err.Error()) +} + +func (e *RunError) Unwrap() error { + return e.Err +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/executable.go b/vendor/github.com/lacework/go-sdk/lwcomponent/executable.go new file mode 100644 index 000000000..a1fb5cbb9 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/executable.go @@ -0,0 +1,99 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lwcomponent + +import ( + "bytes" + "io" + "os" + "os/exec" + + "github.com/pkg/errors" +) + +// RunAndOutput runs the command and outputs to os.Stdout and os.Stderr, +// the provided environment variables will be accessible by the component +func (c Component) RunAndOutput(args []string, envs ...string) error { + loc, err := c.Path() + if err != nil { + return errors.Wrap(err, baseRunErr) + } + + cmd := exec.Command(loc, args...) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, envs...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return c.run(cmd) +} + +// RunAndReturn runs the command and returns its standard output and standard error, +// the provided environment variables will be accessible by the component +func (c Component) RunAndReturn(args []string, stdin io.Reader, envs ...string) ( + stdout string, + stderr string, + err error, +) { + var outBuff, errBuff bytes.Buffer + + loc, err := c.Path() + if err != nil { + err = errors.Wrap(err, baseRunErr) + return + } + + cmd := exec.Command(loc, args...) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, envs...) + cmd.Stdin = stdin + cmd.Stdout = &outBuff + cmd.Stderr = &errBuff + + err = c.run(cmd) + + stdout, stderr = outBuff.String(), errBuff.String() + return +} + +func (c Component) run(cmd *exec.Cmd) error { + if c.IsExecutable() { + + // verify component + if err := c.isVerified(); err != nil { + return errors.Wrap(err, baseRunErr) + } + + if err := cmd.Run(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + return &RunError{ + Err: err, + Message: baseRunErr, + ExitCode: exitError.ExitCode(), + } + } + return errors.Wrap(err, baseRunErr) + } + + return nil + } + + return errors.Errorf("%s: component %s is not a binary", baseRunErr, c.Name) +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/host_info.go b/vendor/github.com/lacework/go-sdk/lwcomponent/host_info.go new file mode 100644 index 000000000..31930a579 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/host_info.go @@ -0,0 +1,152 @@ +package lwcomponent + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Masterminds/semver" + "github.com/lacework/go-sdk/internal/file" + "github.com/pkg/errors" +) + +var ( + VersionFile = ".version" + SignatureFile = ".signature" + InfoFile = ".info" + DevelopmentFile = ".dev" +) + +type HostInfo struct { + Name string `json:"name"` + ComponentType Type `json:"type"` + Desc string `json:"description"` + Dir string `json:"-"` +} + +func LoadHostInfo(dir string) (*HostInfo, error) { + path := filepath.Join(dir, InfoFile) + + data, err := os.ReadFile(path) + if err != nil { + return nil, errors.Errorf("unable to read %s file", path) + } + + hostInfo := HostInfo{} + + err = json.Unmarshal(data, &hostInfo) + if err != nil { + return nil, errors.Errorf("unable to unmarshal %s file", path) + } + + hostInfo.Dir = dir + + if hostInfo.Name == "" { + hostInfo.Name = filepath.Base(dir) + } + + return &hostInfo, nil +} + +func NewHostInfo(dir string, desc string, componentType Type) (*HostInfo, error) { + path := filepath.Join(dir, InfoFile) + + if !file.FileExists(path) { + info := &HostInfo{ + Name: filepath.Base(dir), + Dir: dir, + ComponentType: componentType, + Desc: desc, + } + + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(info); err != nil { + return nil, err + } + + return info, os.WriteFile(path, buf.Bytes(), 0644) + } + + return LoadHostInfo(dir) +} + +func (h *HostInfo) Delete() error { + return os.RemoveAll(h.Dir) +} + +func (h *HostInfo) Development() bool { + return file.FileExists(filepath.Join(h.Dir, DevelopmentFile)) +} + +func (h *HostInfo) Signature() (sig []byte, err error) { + _, err = os.Stat(h.Dir) + if os.IsNotExist(err) { + return + } + + path := filepath.Join(h.Dir, SignatureFile) + if !file.FileExists(path) { + return + } + + sig, err = os.ReadFile(path) + if err != nil { + return + } + + return +} + +func (h *HostInfo) Version() (version *semver.Version, err error) { + _, err = os.Stat(h.Dir) + if os.IsNotExist(err) { + return + } + + path := filepath.Join(h.Dir, VersionFile) + if !file.FileExists(path) { + return nil, errors.New("missing .version file") + } + + data, err := os.ReadFile(path) + if err != nil { + return + } + + return semver.NewVersion(strings.TrimSpace(string(data))) +} + +func (h *HostInfo) Validate() (err error) { + data, err := os.ReadFile(filepath.Join(h.Dir, VersionFile)) + if err != nil { + return + } + + version := string(data) + + _, err = semver.NewVersion(strings.TrimSpace(version)) + if err != nil { + return + } + + componentName := h.Name + + if !file.FileExists(filepath.Join(h.Dir, SignatureFile)) { + return errors.New(fmt.Sprintf("missing file '%s'", componentName)) + } + + path := filepath.Join(h.Dir, componentName) + + if operatingSystem == "windows" { + path = fmt.Sprintf("%s.exe", path) + } + + if !file.FileExists(path) { + return errors.New(fmt.Sprintf("missing file '%s'", componentName)) + } + + return +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/http.go b/vendor/github.com/lacework/go-sdk/lwcomponent/http.go new file mode 100644 index 000000000..cf08a374d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/http.go @@ -0,0 +1,81 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lwcomponent + +import ( + "os" + "strconv" + "time" + + "github.com/go-resty/resty/v2" + "github.com/lacework/go-sdk/lwlogger" +) + +const ( + DefaultMaxRetry = 3 +) + +var log = lwlogger.New("INFO").Sugar() + +// Retry 3 times (4 requests total) +// Resty default RetryWaitTime is 100ms +// Exponential backoff to a maximum of RetryWaitTime of 2s +func DownloadFile(path string, url string) error { + client := resty.New() + + download_timeout := os.Getenv("CDK_DOWNLOAD_TIMEOUT_MINUTES") + if download_timeout != "" { + val, err := strconv.Atoi(download_timeout) + + if err == nil { + client.SetTimeout(time.Duration(val) * time.Minute) + } + } + + client.SetRetryCount(DefaultMaxRetry) + + client.OnError(func(req *resty.Request, err error) { + fields := []interface{}{ + "raw_error", err, + } + + if v, ok := err.(*resty.ResponseError); ok { + + fields = append(fields, "response_body", string(v.Response.Body())) + + if v.Response.Request != nil { + trace := v.Response.Request.TraceInfo() + fields = append(fields, "trace_info", trace) + } + + if v.Err != nil { + fields = append(fields, "response_error", v.Err.Error()) + } + } + + log.Warnw("Failed to download component", fields...) + }) + + _, err := client.R(). + EnableTrace(). + SetOutput(path). + Get(url) + + return err +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/library.go b/vendor/github.com/lacework/go-sdk/lwcomponent/library.go new file mode 100644 index 000000000..3a31b27ac --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/library.go @@ -0,0 +1,34 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lwcomponent + +// A library component provides one or more files that other components use +type Library interface { + + // Install downloads the library and deploys the files and index + Install() error + + // Index returns the index of files that the library contains + Index() []string + + // GetFile returns the content of one file from the library + GetFile(string) ([]byte, error) +} + +// @hazekamp figure out LibraryComponent (if component is a library how do we interact with it) diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/minisign.go b/vendor/github.com/lacework/go-sdk/lwcomponent/minisign.go new file mode 100644 index 000000000..24cd184ad --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/minisign.go @@ -0,0 +1,72 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A Lacework component package to help facilitate the loading and execution of components +package lwcomponent + +import ( + "crypto/sha256" + _ "embed" + "encoding/base64" + "strings" + + "aead.dev/minisign" + "github.com/pkg/errors" +) + +// Lacework's public key +const publicKey = "RWTcmYcv9P0yMqHghe1Fu4gg65yoBuZCY7r02rD0N7o2XSK5Jq9h1Quk" + +// Verify will verify a two-level signature. +// TODO(pjm): Can we make the signature format be an argument? +func verifySignature(rootKey minisign.PublicKey, file, sig []byte) error { + h := sha256.New() + if _, err := h.Write(file); err != nil { + return errors.New("unable to compute hash for component") + } + + var parsedSig minisign.Signature + if err := parsedSig.UnmarshalText(sig); err != nil { + return errors.New("unable to parse signature") + } + + sigParts := strings.Split(parsedSig.TrustedComment, ".") + if len(sigParts) != 4 { + return errors.New("invalid signature trusted comment") + } + + parsedRootSig, err := base64.StdEncoding.DecodeString(sigParts[3]) + if err != nil { + return errors.Wrap(err, "unable to parse root signature from trusted comment") + } + + if !minisign.Verify(rootKey, []byte(sigParts[1]), parsedRootSig) { + return errors.New("invalid root signature over signing key") + } + + var signingKey minisign.PublicKey + if err := signingKey.UnmarshalText([]byte(sigParts[1])); err != nil { + return errors.Wrap(err, "unable to parse signing key") + } + + if !minisign.Verify(signingKey, h.Sum(nil), sig) { + return errors.New("invalid signature over component") + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/staging.go b/vendor/github.com/lacework/go-sdk/lwcomponent/staging.go new file mode 100644 index 000000000..0fa5e0cef --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/staging.go @@ -0,0 +1,253 @@ +package lwcomponent + +import ( + "archive/tar" + "compress/gzip" + "encoding/base64" + "fmt" + "io" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/Masterminds/semver" + "github.com/lacework/go-sdk/internal/file" + dircopy "github.com/otiai10/copy" + "github.com/pkg/errors" +) + +type StageConstructor func(name, artifactUrl string, size int64) (stage Stager, err error) + +type Stager interface { + Close() + + Commit(string) error + + Directory() string + + Download(progressClosure func(filepath string, sizeB int64)) error + + Filename() string + + Signature() (sig []byte, err error) + + Unpack() error + + Validate() error +} + +type stageTarGz struct { + artifactUrl *url.URL + dir string + name string + size int64 +} + +func NewStageTarGz(name, artifactUrl string, size int64) (stage Stager, err error) { + dir, err := os.MkdirTemp("", "cdk-component-stage-tar-gz-") + if err != nil { + return + } + + _url, err := url.Parse(artifactUrl) + if err != nil { + os.RemoveAll(dir) + return + } + + stage = &stageTarGz{artifactUrl: _url, dir: dir, name: name, size: size} + + return +} + +func (s *stageTarGz) Close() { + os.RemoveAll(s.dir) +} + +func (s *stageTarGz) Commit(targetDir string) (err error) { + _, err = os.Stat(s.dir) + if os.IsNotExist(err) { + err = errors.New("component not staged") + return + } + + _, err = os.Stat(targetDir) + if os.IsNotExist(err) { + err = errors.New("target install directory doesn't exist") + return + } + + if err = dircopy.Copy(s.dir, targetDir); err != nil { + return + } + + return +} + +func (s *stageTarGz) Directory() string { + return s.dir +} + +func (s *stageTarGz) Filename() string { + return filepath.Base(s.artifactUrl.Path) +} + +func (s *stageTarGz) Download(progressClosure func(filepath string, sizeB int64)) error { + fileName := filepath.Base(s.artifactUrl.Path) + + path := filepath.Join(s.dir, fileName) + + if _, err := os.Create(path); err != nil { + return err + } + + go progressClosure(path, s.size*1024) + + return DownloadFile(path, s.artifactUrl.String()) +} + +func (s *stageTarGz) Signature() ([]byte, error) { + _, err := os.Stat(s.dir) + if os.IsNotExist(err) { + return nil, errors.New("component not staged") + } + + path := filepath.Join(s.dir, SignatureFile) + if !file.FileExists(path) { + return nil, errors.New("missing .signature file") + } + + sig, err := os.ReadFile(path) + if err != nil { + return sig, err + } + + // Artifact signature may or may not be b64encoded + decoded_sig, err := base64.StdEncoding.DecodeString(string(sig)) + if err == nil { + return decoded_sig, nil + } + return sig, nil +} + +func (s *stageTarGz) Unpack() (err error) { + fileName := filepath.Base(s.artifactUrl.Path) + + gzFile := filepath.Join(s.dir, fileName) + tarball := filepath.Join(s.dir, strings.TrimRight(fileName, ".gz")) + + err = gunzip(gzFile, tarball) + if err != nil { + return + } + + err = unTar(tarball, s.dir) + if err != nil { + return + } + + os.Remove(gzFile) + os.Remove(tarball) + + return nil +} + +func (s *stageTarGz) Validate() error { + data, err := os.ReadFile(filepath.Join(s.dir, VersionFile)) + if err != nil { + return err + } + + version := string(data) + + _, err = semver.NewVersion(strings.TrimSpace(version)) + if err != nil { + return errors.Errorf("invalid staged semantic version '%s' for component '%s'", version, s.name) + } + + if !file.FileExists(filepath.Join(s.dir, SignatureFile)) { + return errors.Errorf("missing file '%s'", s.name) + } + + path := filepath.Join(s.dir, s.name) + + if operatingSystem == "windows" { + path = fmt.Sprintf("%s.exe", path) + } + + if !file.FileExists(path) { + return errors.Errorf("missing file '%s'", path) + } + + return nil +} + +// Inflate GZip file. +// +// Writes decompressed data to target path. +func gunzip(source string, target string) (err error) { + reader, err := os.Open(source) + if err != nil { + return + } + defer reader.Close() + + archive, err := gzip.NewReader(reader) + if err != nil { + return + } + defer archive.Close() + + writer, err := os.Create(target) + if err != nil { + return + } + defer writer.Close() + + _, err = io.Copy(writer, archive) + + return +} + +func unTar(tarball string, dir string) error { + reader, err := os.Open(tarball) + if err != nil { + return err + } + defer reader.Close() + + tarReader := tar.NewReader(reader) + + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } else if err != nil { + return err + } + + path := filepath.Join(dir, header.Name) + + info := header.FileInfo() + if info.IsDir() { + if err := os.MkdirAll(path, info.Mode()); err != nil { + return err + } + continue + } + + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(file, tarReader) + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/status.go b/vendor/github.com/lacework/go-sdk/lwcomponent/status.go new file mode 100644 index 000000000..07636cfc2 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/status.go @@ -0,0 +1,84 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lwcomponent + +import "github.com/fatih/color" + +type Status int + +const ( + UnknownStatus Status = iota + Development + NotInstalled + NotInstalledDeprecated + Installed + InstalledDeprecated + UpdateAvailable + Tainted +) + +func (s Status) Color() *color.Color { + switch s { + case Development: + return color.New(color.FgBlue, color.Bold) + case NotInstalled: + return color.New(color.FgWhite, color.Bold) + case Installed: + return color.New(color.FgGreen, color.Bold) + case UpdateAvailable: + return color.New(color.FgYellow, color.Bold) + case InstalledDeprecated, NotInstalledDeprecated, Tainted: + return color.New(color.FgRed, color.Bold) + default: + return color.New(color.FgRed, color.Bold) + } +} + +func (s Status) String() string { + switch s { + case Development: + return "Development" + case Installed: + return "Installed" + case InstalledDeprecated: + return "Installed (Deprecated)" + case NotInstalled: + return "Not Installed" + case NotInstalledDeprecated: + return "Not Installed (Deprecated)" + case Tainted: + return "Tainted (Please update)" + case UpdateAvailable: + return "Update Available" + default: + return "Unknown" + } +} + +// IsInstalled returns true if the component is installed on disk +// +// TODO: @jon-stewart: remove - is in wrong place +func (c Component) IsInstalled() bool { + switch c.Status() { + case Installed, UpdateAvailable: + return true + default: + return false + } +} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sh b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sh new file mode 100644 index 000000000..b2f94c336 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Hello ${LW_TEST}!" diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sig b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sig new file mode 100644 index 000000000..b2e9f166c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sig @@ -0,0 +1 @@ +dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpVktIRE5vR1A3amlNZEJ1WktUWEJLRUZpVFBBRTFidVFVQVlJbUthd1RiWVg4dU1GYXJOc2hQMThVZElySGRhTmxkSE1QeEsxMUsrUEpNdUtoWVE0UTA9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0Ka3BoUVlieFp2UUw0cEJzYTdtRVZWMXZwRUYzU0dXWmEvK29UdUN3TEFxRmVwai9ISzFibmJCdlFGY010Vy9STVJKU2lxSFV6TWwwa0ROcXJoSjBqQkE9PQ== diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sh b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sh new file mode 100644 index 000000000..67464fb16 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo "Hello World!" diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sig b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sig new file mode 100644 index 000000000..27a910457 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sig @@ -0,0 +1 @@ +dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpUmwrdm03ZE5wcDdKcnQrcUxBTnN1OW0rVGFIU2dWeUpPOUxkZW81WlB3L0dRSnUxZkpaQnVDcnNNVlc1S2J1QjZkRVB6UXZpOGN0MXpKZk1rZ1ZzZ3M9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0KY0p3aDdYRGN0R1I4ZHBjMU5FSm5QR1U1SVhOdk1OWGE2aGRNQTBCQlp2dm5zS1FIZVZWRWtNYzd6bzcyaUZzSEVLUnhlK0FtWGtyeU5nVmk3R2cwRFE9PQ== \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sh b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sh new file mode 100644 index 000000000..f84fa094c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Hello $1!" +read line +echo "Hello $line!" >&2 diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sig b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sig new file mode 100644 index 000000000..da40d402d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sig @@ -0,0 +1 @@ +dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpVHlSQjZTUnJCTHp0UDdmK2x0UGZ6RTAxdTFMQlE5akE2ei9ESFNXcG5oRFVCQVJTKzJXRzh2RjhMeHRDQmpXd3hvK21ySFI3Y3BYOWZpdWgxRlJlUXM9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0KK2NvQ242enpFVkV5MCs0ZENQOUR4bVN4Nk9yaGJUTFZ2RXo5SnR0d3Q0anlYdjFTNk0vTmI2dENBcXNzZ1Rhc1FJNEVBVzJpUnZKVElTcFo3UGx4QWc9PQ== \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/types.go b/vendor/github.com/lacework/go-sdk/lwcomponent/types.go new file mode 100644 index 000000000..c23025003 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwcomponent/types.go @@ -0,0 +1,50 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lwcomponent + +type Type string + +const ( + EmptyType Type = "" + + // the component is a binary + BinaryType = "BINARY" + + // will this component be accessible via the CLI + CommandType = "CLI_COMMAND" + + // the component is a library, only provides content for the CLI or other components + LibraryType = "LIBRARY" + + // the component is standalone, should be available in $PATH + StandaloneType = "STANDALONE" +) + +func (c Component) IsExecutable() bool { + switch c.Type { + case BinaryType, CommandType: + return true + default: + return false + } +} + +func (c Component) IsCommandType() bool { + return c.Type == CommandType +} diff --git a/vendor/github.com/lacework/go-sdk/lwconfig/README.md b/vendor/github.com/lacework/go-sdk/lwconfig/README.md new file mode 100644 index 000000000..a76ec6b4d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwconfig/README.md @@ -0,0 +1,46 @@ +# Lacework Config + +A Go library to manage the Lacework configuration file (`$HOME/.lacework.toml`) + +## Usage + +Download the library into your `$GOPATH`: + + $ go get github.com/lacework/go-sdk/lwconfig + +Import the library into your tool: + +```go +import "github.com/lacework/go-sdk/lwconfig" +``` + +## Examples + +Load the default Lacework configuration file and detect if there is a profile named `test`: +```go +package main + +import ( + "fmt" + "os" + + "github.com/lacework/go-sdk/lwconfig" +) + +func main() { + profiles, err := lwconfig.LoadProfiles() + if err != nil { + fmt.Printf("Error trying to load profiles: %s\n", err) + os.Exit(1) + } + + config, ok := profiles["test"] + if !ok { + fmt.Println("You have a test profile configured!") + } else { + fmt.Println("'test' profile not found") + } +} +``` + +Look at the [examples/](examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/lwconfig/config.go b/vendor/github.com/lacework/go-sdk/lwconfig/config.go new file mode 100644 index 000000000..5a0d2b374 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwconfig/config.go @@ -0,0 +1,184 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A package to manage the Lacework configuration file ($HOME/.lacework.toml) +package lwconfig + +import ( + "bytes" + "fmt" + "os" + "path" + + "github.com/BurntSushi/toml" + "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" +) + +// Profiles is the representation of the $HOME/.lacework.toml +// +// Example: +// +// [default] +// account = "example" +// api_key = "EXAMPLE_0123456789" +// api_secret = "_0123456789" +// +// [dev] +// account = "dev" +// api_key = "DEV_0123456789" +// api_secret = "_0123456789" +// +// [prod] +// account = "coolcorp" +// subaccount = "prod-business" +// api_key = "PROD_0123456789" +// api_secret = "_0123456789" +// version = 2 +type Profiles map[string]Profile + +// Profile represents a single profile within a configuration file +type Profile struct { + Account string `toml:"account"` + Subaccount string `toml:"subaccount,omitempty"` + ApiKey string `toml:"api_key" survey:"api_key"` + ApiSecret string `toml:"api_secret" survey:"api_secret"` + Version int `toml:"version,omitzero"` +} + +const ( + ApiKeyMinLength = 55 + ApiSecretMinLength = 30 +) + +// Verify will return an error is there is one required setting missing +func (p *Profile) Verify() error { + if p.Account == "" { + return errors.New("account missing") + } + + if err := p.verifyApiKey(); err != nil { + return err + } + + if err := p.verifyApiSecret(); err != nil { + return err + } + + return nil +} + +func (p *Profile) verifyApiKey() error { + if p.ApiKey == "" { + return errors.New("api_key missing") + } + + if len(p.ApiKey) < ApiKeyMinLength { + return errors.New(fmt.Sprintf("api_key must have more than %d characters", ApiKeyMinLength)) + } + + return nil +} + +func (p *Profile) verifyApiSecret() error { + if p.ApiSecret == "" { + return errors.New("api_secret missing") + } + + if len(p.ApiSecret) < ApiSecretMinLength { + return errors.New(fmt.Sprintf("api_secret must have more than %d characters", ApiSecretMinLength)) + } + + return nil +} + +// DefaultConfigPath returns the default path where the Lacework config file +// is located, which is at $HOME/.lacework.toml +func DefaultConfigPath() (string, error) { + home, err := homedir.Dir() + if err != nil { + return "", err + } + return path.Join(home, ".lacework.toml"), nil +} + +// LoadProfiles loads all the profiles from the default location ($HOME/.lacework.toml) +func LoadProfiles() (Profiles, error) { + configPath, err := DefaultConfigPath() + if err != nil { + return Profiles{}, err + } + + return LoadProfilesFrom(configPath) +} + +// LoadProfilesFrom loads all the profiles from the provided location +func LoadProfilesFrom(configPath string) (Profiles, error) { + if configPath == "" { + return Profiles{}, errors.New("unable to load profiles. Specify a configuration file.") + } + + var profiles Profiles + if _, err := toml.DecodeFile(configPath, &profiles); err != nil { + return profiles, errors.Wrap(err, "unable to decode profiles from config") + } + + return profiles, nil +} + +// StoreProfileAt updates a single profile from the provided configuration file +func StoreProfileAt(configPath, name string, profile Profile) error { + if configPath == "" { + defaultPath, err := DefaultConfigPath() + if err != nil { + return err + } + configPath = defaultPath + } + + var ( + profiles = Profiles{} + err error + ) + if _, err = os.Stat(configPath); err == nil { + if profiles, err = LoadProfilesFrom(configPath); err != nil { + return err + } + } + + profiles[name] = profile + return StoreAt(configPath, profiles) +} + +// StoreAt stores the provided profiles into the selected configuration file +func StoreAt(configPath string, profiles Profiles) error { + if configPath == "" { + defaultPath, err := DefaultConfigPath() + if err != nil { + return err + } + configPath = defaultPath + } + + var buf = new(bytes.Buffer) + if err := toml.NewEncoder(buf).Encode(profiles); err != nil { + return err + } + + return os.WriteFile(configPath, buf.Bytes(), 0600) +} diff --git a/vendor/github.com/lacework/go-sdk/lwdomain/README.md b/vendor/github.com/lacework/go-sdk/lwdomain/README.md new file mode 100644 index 000000000..f7eec8273 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwdomain/README.md @@ -0,0 +1,43 @@ +# Lacework Domain + +Use this package to disseminate a domain URL into account, cluster and whether or not +it is an internal account. + +## Usage + +Download the library into your `$GOPATH`: + + $ go get github.com/lacework/go-sdk/lwdomain + +Import the library into your tool: + +```go +import "github.com/lacework/go-sdk/lwdomain" +``` + +## Examples + +The following URL `https://account.lacework.net` would be disseminated into: +* `account` as the account name + +```go +package main + +import ( + "fmt" + "os" + + "github.com/lacework/go-sdk/lwdomain" +) + +func main() { + domain, err := lwdomain.New("https://account.lacework.net") + if err != nil { + fmt.Printf("Error %s\n", err) + os.Exit(1) + } + + // Output: Lacework Account Name: account + fmt.Println("Lacework Account Name: %s", domain.Account) +} +``` diff --git a/vendor/github.com/lacework/go-sdk/lwdomain/domain.go b/vendor/github.com/lacework/go-sdk/lwdomain/domain.go new file mode 100644 index 000000000..1df284e29 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwdomain/domain.go @@ -0,0 +1,99 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A package to disseminate a domain URL into account, cluster and whether or not is internal. +package lwdomain + +import ( + "fmt" + "regexp" + "strings" + + "github.com/pkg/errors" +) + +type domain struct { + Account string + Cluster string + Internal bool +} + +// New returns domain information from the provided URL +// +// For instance, the following URL: +// +// d, err := lwdomain.New("https://account.lacework.net") +// +// Would be disseminated into: +// - `account` as the account name +// - `fra` as the cluster name +func New(url string) (domain, error) { + // for the full url https://ACCOUNT.lacework.net + // remove the prefixes https:// or http:// + rx, err := regexp.Compile(`(http://|https://)`) + if err == nil { + url = rx.ReplaceAllString(url, "") + } + + // for the full domain ACCOUNT[.CUSTER][.corp].lacework.net + // subtract the account and cluster name, also detect if the + // domain is internal (dev, qa, preprod, etc) + rx, err = regexp.Compile(`\.lacework\.net.*`) + if err == nil { + domainSplit := rx.Split(url, -1) + if len(domainSplit) > 1 { + url = domainSplit[0] + } else { + return domain{}, errors.New("domain not supported") + } + } + + domainInfo := strings.Split(url, ".") + switch len(domainInfo) { + case 1: + return domain{Account: domainInfo[0]}, nil + case 2: + return domain{ + Account: domainInfo[0], + Cluster: domainInfo[1], + }, nil + case 3: + if domainInfo[2] != "corp" { + return domain{}, errors.New("unable to detect if domain is internal") + } + return domain{ + Account: domainInfo[0], + Cluster: domainInfo[1], + Internal: true, + }, nil + default: + return domain{}, errors.New("unable to detect domain information") + } +} + +func (d *domain) String() string { + if d.Internal { + return fmt.Sprintf("%s.%s.corp", d.Account, d.Cluster) + } + + if d.Cluster != "" { + return fmt.Sprintf("%s.%s", d.Account, d.Cluster) + } + + return d.Account +} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/aws/aws.go b/vendor/github.com/lacework/go-sdk/lwgenerate/aws/aws.go new file mode 100644 index 000000000..e9ea8a5f9 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/aws/aws.go @@ -0,0 +1,1406 @@ +// A package that generates Lacework deployment code for Amazon Web Services. +package aws + +import ( + "encoding/json" + "fmt" + "regexp" + "slices" + "strings" + + "github.com/google/uuid" + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/pkg/errors" + + "github.com/lacework/go-sdk/lwgenerate" +) + +type ExistingIamRoleDetails struct { + // Existing IAM Role ARN + Arn string + + // Existing IAM Role Name + Name string + + // Existing IAM Role External Id + ExternalId string +} + +func (e *ExistingIamRoleDetails) IsEmpty() bool { + if e == nil { + return true + } + return e.Arn == "" && e.Name == "" && e.ExternalId == "" +} + +func (e *ExistingIamRoleDetails) IsPartial() bool { + // If nil, return false + if e == nil { + return false + } + + // If all values are empty, return false + if e.Arn == "" && e.Name == "" && e.ExternalId == "" { + return false + } + + // If all values are populated, return false + if e.Arn != "" && e.Name != "" && e.ExternalId != "" { + return false + } + + return true +} + +// NewExistingIamRoleDetails Create new existing IAM role details +func NewExistingIamRoleDetails(name string, arn string, externalId string) *ExistingIamRoleDetails { + return &ExistingIamRoleDetails{ + Arn: arn, + Name: name, + ExternalId: externalId, + } +} + +type OrgAccountMapping struct { + DefaultLaceworkAccount string `json:"default_lacework_account"` + Mapping []OrgAccountMap `json:"mapping"` +} + +func (orgMap *OrgAccountMapping) IsEmpty() bool { + return len(orgMap.Mapping) == 0 && orgMap.DefaultLaceworkAccount == "" +} + +func (orgMap *OrgAccountMapping) ToMap() (map[string]any, error) { + var mappings []map[string]any + mappingsJsonString, err := json.Marshal(orgMap.Mapping) + if err != nil { + return nil, err + } + err = json.Unmarshal(mappingsJsonString, &mappings) + if err != nil { + return nil, err + } + + orgMap.Mapping = []OrgAccountMap{} + + result := make(map[string]any) + jsonString, err := json.Marshal(orgMap) + if err != nil { + return nil, err + } + + err = json.Unmarshal(jsonString, &result) + if err != nil { + return nil, err + } + + result["mapping"] = mappings + return result, nil +} + +type OrgAccountMap struct { + LaceworkAccount string `json:"lacework_account"` + AwsAccounts []string `json:"aws_accounts"` +} + +type AwsSubAccount struct { + // The name of the AwsProfile to use (in AWS configuration) + AwsProfile string + + // The AwsRegion this profile should use if any resources are created + AwsRegion string + + // The Alias of the provider block + Alias string +} + +// Create a new AWS sub account +// +// A subaccount consists of the profile name (which needs to match the executing machines aws configuration) and a +// region for any new resources to be created in +func NewAwsSubAccount(profile string, region string, alias ...string) AwsSubAccount { + subaccount := AwsSubAccount{AwsProfile: profile, AwsRegion: region} + if len(alias) > 0 { + subaccount.Alias = alias[0] + } + return subaccount +} + +type GenerateAwsTfConfigurationArgs struct { + // Should we enable AWS organization integration? + AwsOrganization bool + + // Should we configure Agentless integration in LW? + Agentless bool + + // Agentless management AWS account ID + AgentlessManagementAccountID string + + // Agentless monitored AWS account IDs, OUs, or the organization root. + AgentlessMonitoredAccountIDs []string + + // Agentless monitored AWS accounts + AgentlessMonitoredAccounts []AwsSubAccount + + // Agentless scanning AWS accounts + AgentlessScanningAccounts []AwsSubAccount + + // Is the AWS organization using Control Tower? + ControlTower bool + + // AWS Control Tower Audit account + ControlTowerAuditAccount *AwsSubAccount + + // AWS Control Tower Log Archive account + ControlTowerLogArchiveAccount *AwsSubAccount + + // AWS Control Tower custom KMS key ARN + ControlTowerKmsKeyArn string + + // Should we configure Cloudtrail integration in LW? + Cloudtrail bool + + // Optional name for CloudTrail + CloudtrailName string + + // Should we configure AWS organization mappings? + AwsOrganizationMappings bool + + // Cloudtrail organization account mappings + OrgAccountMappings OrgAccountMapping + + // OrgAccountMapping json used for flag input + OrgAccountMappingsJson string + + // Use exisiting CloudTrail + CloudtrailUseExistingTrail bool + + // Use exisiting CloudTrail SNS topic + CloudtrailUseExistingSNSTopic bool + + // Should we configure CSPM integration in LW? + Config bool + + // Optional name for config + ConfigName string + + // Config additional AWS accounts + ConfigAdditionalAccounts []AwsSubAccount + + // Config Lacework account + ConfigOrgLWAccount string + + // Config Lacework sub-account + ConfigOrgLWSubaccount string + + // Config Lacework access key ID + ConfigOrgLWAccessKeyId string + + // Config Lacework secret key + ConfigOrgLWSecretKey string + + // Config organization ID + ConfigOrgId string + + // Config organization unit + ConfigOrgUnits []string + + // Config resource prefix + ConfigOrgCfResourcePrefix string + + // Custom outputs + CustomOutputs []lwgenerate.HclOutput + + // Supply an AWS region for where to find the cloudtrail resources + // TODO @ipcrm future: support split regions for resources (s3 one place, sns another, etc) + AwsRegion string + + // Supply an AWS Profile name for the main account, only asked if configuring multiple + AwsProfile string + + // Supply an AWS Assume Role for the main account + AwsAssumeRole string + + // Existing S3 Bucket ARN (Required when using existing cloudtrail) + ExistingCloudtrailBucketArn string + + // Optionally supply existing IAM role details + ExistingIamRole *ExistingIamRoleDetails + + // Existing SNS Topic + ExistingSnsTopicArn string + + // Consolidated Trail + ConsolidatedCloudtrail bool + + // Should we force destroy the bucket if it has stuff in it? (only relevant on new Cloudtrail creation) + // DEPRECATED + ForceDestroyS3Bucket bool + + // Enable encryption of bucket if it is created + BucketEncryptionEnabled bool + + // Indicates that the Bucket Encryption flag has been actively set + // this is needed to show this it was set actively to false, rather + // than default value for bool + BucketEncryptionEnabledSet bool + + // Optional name of bucket if creating a new one + BucketName string + + // Arn of the KMS encryption key for S3, required when bucket encryption in enabled + BucketSseKeyArn string + + // Enable S3 bucket notification + S3BucketNotification bool + + // SNS Topic name if creating one and not using an existing one + SnsTopicName string + + // Enable encryption of SNS if it is created + SnsTopicEncryptionEnabled bool + + // Indicates that the SNS Encryption flag has been actively set + // this is needed to show this it was set actively to false, rather + // than default value for bool + SnsEncryptionEnabledSet bool + + // Arn of the KMS encryption key for SNS, required when SNS encryption in enabled + SnsTopicEncryptionKeyArn string + + // SSQ Queue name if creating one and not using an existing one + SqsQueueName string + + // Enable encryption of SQS if it is created + SqsEncryptionEnabled bool + + // Indicates that the SQS Encryption flag has been actively set + // this is needed to show this it was set actively to false, rather + // than default value for bool + SqsEncryptionEnabledSet bool + + // Arn of the KMS encryption key for SQS, required when SQS encryption in enabled + SqsEncryptionKeyArn string + + // For AWS Subaccounts in consolidated CT setups + // TODO @ipcrm future: what about many individual ct/config integrations together? + SubAccounts []AwsSubAccount + + // Lacework Profile to use + LaceworkProfile string + + // The Lacework AWS Root Account ID + LaceworkAccountID string + + // Lacework Organization + LaceworkOrganizationLevel bool + + // Use random Cloudtrail name + UseCloudTrailRandomName bool + + // Default AWS Provider Tags + ProviderDefaultTags map[string]interface{} + + // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc + ExtraBlocksRootTerraform []*hclwrite.Block + + // ExtraProviderArguments allows adding more arguments to the provider block as needed (custom use cases) + ExtraProviderArguments map[string]interface{} + + // ExtraBlocks allows adding more hclwrite.Block to the root terraform document (advanced use cases) + ExtraBlocks []*hclwrite.Block +} + +func (args *GenerateAwsTfConfigurationArgs) IsEmpty() bool { + if args.AwsProfile == "" && args.AwsRegion == "" && !args.Agentless && !args.Config && !args.Cloudtrail { + return true + } + return false +} + +// Ensure all combinations of inputs our valid for supported spec +func (args *GenerateAwsTfConfigurationArgs) Validate() error { + if !args.Agentless && !args.Cloudtrail && !args.Config { + return errors.New("Agentless, CloudTrail or Config integration must be enabled") + } + + if args.AwsRegion == "" { + return errors.New("Main AWS account region must be set") + } + + if args.ExistingIamRole.IsPartial() { + return errors.New("when using an existing IAM role, existing role ARN, name, and external ID all must be set") + } + + if args.AwsOrganization { + if args.Agentless { + if args.AgentlessManagementAccountID == "" { + return errors.New("must specify a management account ID for Agentless organization integration") + } + if len(args.AgentlessMonitoredAccountIDs) == 0 { + return errors.New("must specify monitored account ID list for Agentless organization integration") + } + if len(args.AgentlessMonitoredAccounts) == 0 { + // profile/region is required for single accounts + for _, accountID := range args.AgentlessMonitoredAccountIDs { + regex, _ := regexp.Compile(`^\d{12}$`) + if regex.MatchString(accountID) { + return errors.New("must specify profile/region for single monitored accounts" + + " for Agentless organization integration") + } + } + } + if len(args.AgentlessScanningAccounts) == 0 { + return errors.New("must specify scanning accounts for Agentless organization integration") + } + } + + if args.ControlTower && args.Cloudtrail { + if args.ControlTowerAuditAccount == nil { + return errors.New("must specify audit account for CloudTrail Control Tower integration") + } + if args.ControlTowerLogArchiveAccount == nil { + return errors.New("must specify log archive account for CloudTrail Control Tower integration") + } + if args.ExistingCloudtrailBucketArn == "" { + return errors.New("must specify S3 bucket ARN for CloudTrail Control Tower integration") + } + if args.ExistingSnsTopicArn == "" { + return errors.New("must specify SNS topic ARN for CloudTrail Control Tower integration") + } + } + } + + if args.Cloudtrail { + if args.CloudtrailUseExistingTrail { + if args.CloudtrailName == "" { + return errors.New("must specify the existing trail name when integrating with existing CloudTrail") + } + if args.ExistingCloudtrailBucketArn == "" { + return errors.New( + fmt.Sprintf( + "must specify the S3 bucket ARN associated with the trail %s "+ + " when integrating with existing CloudTrail", args.CloudtrailName, + ), + ) + } + if args.ExistingSnsTopicArn == "" && !args.S3BucketNotification { + return errors.New( + fmt.Sprintf( + "must either specify the SNS topic ARN associated with the trail %s "+ + "or enable S3 notification when integrating with existing CloudTrail", args.CloudtrailName, + ), + ) + } + } + } + + return nil +} + +type AwsTerraformModifier func(c *GenerateAwsTfConfigurationArgs) + +type AwsGenerateCommandExtraState struct { + CloudtrailAdvanced bool + Output string + AwsSubAccounts []string + AgentlessMonitoredAccounts []string + AgentlessScanningAccounts []string + ControlTowerAuditAccount string + ControlTowerLogArchiveAccount string + TerraformApply bool +} + +func (a *AwsGenerateCommandExtraState) IsEmpty() bool { + return a.Output == "" && len(a.AwsSubAccounts) == 0 && !a.TerraformApply +} + +// NewTerraform returns an instance of the GenerateAwsTfConfigurationArgs struct with the provided region and enabled +// settings (config/cloudtrail). +// +// Note: Additional configuration details may be set using modifiers of the AwsTerraformModifier type +// +// Basic usage: Initialize a new AwsTerraformModifier struct, with a non-default AWS profile set. Then use generate to +// +// create a string output of the required HCL. +// +// hcl, err := aws.NewTerraform("us-east-1", true, true, +// aws.WithAwsProfile("mycorp-profile")).Generate() +func NewTerraform( + enableAwsOrganization bool, + enableAgentless bool, + enableConfig bool, + enableCloudtrail bool, + mods ...AwsTerraformModifier, +) *GenerateAwsTfConfigurationArgs { + config := &GenerateAwsTfConfigurationArgs{ + AwsOrganization: enableAwsOrganization, + Agentless: enableAgentless, + Cloudtrail: enableCloudtrail, + Config: enableConfig, + } + for _, m := range mods { + m(config) + } + return config +} + +// WithProviderDefaultTags adds default_tags to the provider configuration for AWS (if tags are present) +func WithProviderDefaultTags(tags map[string]interface{}) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ProviderDefaultTags = tags + } +} + +// WithAwsProfile Set the AWS Profile to utilize for the main AWS provider +func WithAwsProfile(name string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.AwsProfile = name + } +} + +// WithAwsRegion Set the AWS region to utilize for the main AWS provider +func WithAwsRegion(region string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.AwsRegion = region + } +} + +// WithAwsAssumeRole Set the AWS Assume Role to utilize for the main AWS provider +func WithAwsAssumeRole(assumeRole string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.AwsAssumeRole = assumeRole + } +} + +// WithLaceworkProfile Set the Lacework Profile to utilize when integrating +func WithLaceworkProfile(name string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.LaceworkProfile = name + } +} + +// WithLaceworkAccountID Set the Lacework AWS root account ID to use +func WithLaceworkAccountID(accountID string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.LaceworkAccountID = accountID + } +} + +// WithAgentlessManagementAccountID Set Agentless management account ID +func WithAgentlessManagementAccountID(accountID string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.AgentlessManagementAccountID = accountID + } +} + +// WithAgentlessMonitoredAccountIDs Set Agentless monitored account IDs +func WithAgentlessMonitoredAccountIDs(accountIDs []string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.AgentlessMonitoredAccountIDs = accountIDs + } +} + +// WithAgentlessMonitoredAccounts Set Agentless monitored accounts +func WithAgentlessMonitoredAccounts(accounts ...AwsSubAccount) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.AgentlessMonitoredAccounts = accounts + } +} + +// WithAgentlessScanningAccounts Set Agentless scanning accounts +func WithAgentlessScanningAccounts(accounts ...AwsSubAccount) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.AgentlessScanningAccounts = accounts + } +} + +// WithConfigAdditionalAccounts Set Config additional accounts +func WithConfigAdditionalAccounts(accounts ...AwsSubAccount) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ConfigAdditionalAccounts = accounts + } +} + +// WithConfigOrgLWAccount Set Config org LW account +func WithConfigOrgLWAccount(account string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ConfigOrgLWAccount = account + } +} + +// WithConfigOrgLWSubaccount Set Config org LW sub-account +func WithConfigOrgLWSubaccount(subaccount string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ConfigOrgLWSubaccount = subaccount + } +} + +// WithConfigOrgLWAccessKeyId Set Config org LW access key ID +func WithConfigOrgLWAccessKeyId(accessKeyId string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ConfigOrgLWAccessKeyId = accessKeyId + } +} + +// WithConfigOrgLWSecretKey Set Config org LW secret key +func WithConfigOrgLWSecretKey(secretKey string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ConfigOrgLWSecretKey = secretKey + } +} + +// WithConfigOrgId Set Config org ID +func WithConfigOrgId(orgId string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ConfigOrgId = orgId + } +} + +// WithConfigOrgUnits Set Config org units +func WithConfigOrgUnits(orgUnits []string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ConfigOrgUnits = orgUnits + } +} + +// WithConfigOutputs Set Custom Terraform Outputs +func WithCustomOutputs(outputs []lwgenerate.HclOutput) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.CustomOutputs = outputs + } +} + +// WithConfigOrgCfResourcePrefix Set Config org resource prefix +func WithConfigOrgCfResourcePrefix(resourcePrefix string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ConfigOrgCfResourcePrefix = resourcePrefix + } +} + +// WithControlTower Set ControlTower +func WithControlTower(controlTower bool) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ControlTower = controlTower + } +} + +// WithControlTowerAuditAccount Set ControlTower audit account +func WithControlTowerAuditAccount(auditAccount *AwsSubAccount) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ControlTowerAuditAccount = auditAccount + } +} + +// WithControlTowerLogArchiveAccount Set ControlTower log archive account +func WithControlTowerLogArchiveAccount(LogArchiveAccount *AwsSubAccount) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ControlTowerLogArchiveAccount = LogArchiveAccount + } +} + +// WithUseCloudTrailRandomName CloudTrail random name +func WithUseCloudTrailRandomName(useCloudTrailRandomName bool) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.UseCloudTrailRandomName = useCloudTrailRandomName + } +} + +// WithControlTowerKmsKeyArn Set ControlTower custom KMS key ARN +func WithControlTowerKmsKeyArn(kmsKeyArn string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ControlTowerKmsKeyArn = kmsKeyArn + } +} + +// WithCloudtrailUseExistingTrail Use the existing Cloudtrail S3 bucket +func WithCloudtrailUseExistingTrail(useExistingS3 bool) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.CloudtrailUseExistingTrail = useExistingS3 + } +} + +// WithCloudtrailUseExistingSNSTopic Use the existing Cloudtrail SNS topic +func WithCloudtrailUseExistingSNSTopic(useExistingSNSTopic bool) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.CloudtrailUseExistingSNSTopic = useExistingSNSTopic + } +} + +// WithExistingCloudtrailBucketArn Set the bucket ARN of an existing Cloudtrail setup +func WithExistingCloudtrailBucketArn(arn string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ExistingCloudtrailBucketArn = arn + } +} + +// WithExistingSnsTopicArn Set the SNS Topic ARN of an existing Cloudtrail setup +func WithExistingSnsTopicArn(arn string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ExistingSnsTopicArn = arn + } +} + +// WithConsolidatedCloudtrail Enable Consolidated Cloudtrail use +func WithConsolidatedCloudtrail(consolidatedCloudtrail bool) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ConsolidatedCloudtrail = consolidatedCloudtrail + } +} + +// WithExistingIamRole Set an existing IAM role configuration to use with the created Terraform code +func WithExistingIamRole(iamDetails *ExistingIamRoleDetails) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ExistingIamRole = iamDetails + } +} + +// WithSubaccounts Supply additional AWS Profiles to integrate +func WithSubaccounts(subaccounts ...AwsSubAccount) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.SubAccounts = subaccounts + } +} + +// WithCloudtrailName add optional name for CloudTrail integration +func WithCloudtrailName(cloudtrailName string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.CloudtrailName = cloudtrailName + } +} + +// WithOrgAccountMappings add optional name for Organization account mappings +// Sets lacework org level to true +func WithOrgAccountMappings(mapping OrgAccountMapping) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.OrgAccountMappings = mapping + if !mapping.IsEmpty() { + c.LaceworkOrganizationLevel = true + } + } +} + +// WithBucketName add bucket name for CloudTrail integration +func WithBucketName(bucketName string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.BucketName = bucketName + } +} + +// WithBucketEncryptionEnabled Enable encryption on a newly created bucket +func WithBucketEncryptionEnabled(enableBucketEncryption bool) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.BucketEncryptionEnabled = enableBucketEncryption + c.BucketEncryptionEnabledSet = true + } +} + +// WithBucketSSEKeyArn Set existing KMS encryption key arn for bucket +func WithBucketSSEKeyArn(bucketSseKeyArn string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.BucketSseKeyArn = bucketSseKeyArn + } +} + +// WithSnsTopicName Set SNS Topic Name if creating new one +func WithSnsTopicName(snsTopicName string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.SnsTopicName = snsTopicName + } +} + +// WithSnsTopicEncryptionEnabled Enable encryption on SNS Topic when created +func WithSnsTopicEncryptionEnabled(snsTopicEncryptionEnabled bool) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.SnsTopicEncryptionEnabled = snsTopicEncryptionEnabled + c.SnsEncryptionEnabledSet = true + } +} + +// WithSnsTopicEncryptionKeyArn Set existing KMS encryption key arn for SNS topic +func WithSnsTopicEncryptionKeyArn(snsTopicEncryptionKeyArn string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.SnsTopicEncryptionKeyArn = snsTopicEncryptionKeyArn + } +} + +// WithSqsQueueName Set SQS Queue Name if creating new one +func WithSqsQueueName(sqsQueueName string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.SqsQueueName = sqsQueueName + } +} + +// WithSqsEncryptionEnabled Enable encryption on SQS queue when created +func WithSqsEncryptionEnabled(sqsEncryptionEnabled bool) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.SqsEncryptionEnabled = sqsEncryptionEnabled + c.SqsEncryptionEnabledSet = true + } +} + +// WithSqsEncryptionKeyArn Set existing KMS encryption key arn for SQS queue +func WithSqsEncryptionKeyArn(ssqEncryptionKeyArn string) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.SqsEncryptionKeyArn = ssqEncryptionKeyArn + } +} + +func WithS3BucketNotification(s3BucketNotifiaction bool) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.S3BucketNotification = s3BucketNotifiaction + } +} + +// WithExtraRootBlocks allows adding generic hcl blocks to the root `terraform{}` block +// this enables custom use cases +func WithExtraRootBlocks(blocks []*hclwrite.Block) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ExtraBlocksRootTerraform = blocks + } +} + +// WithExtraProviderArguments enables adding additional arguments into the `aws` provider block +// this enables custom use cases +func WithExtraProviderArguments(arguments map[string]interface{}) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ExtraProviderArguments = arguments + } +} + +// WithExtraBlocks enables adding additional arbitrary blocks to the root hcl document +func WithExtraBlocks(blocks []*hclwrite.Block) AwsTerraformModifier { + return func(c *GenerateAwsTfConfigurationArgs) { + c.ExtraBlocks = blocks + } +} + +// Generate new Terraform code based on the supplied args. +func (args *GenerateAwsTfConfigurationArgs) Generate() (string, error) { + // Validate inputs + if err := args.Validate(); err != nil { + return "", errors.Wrap(err, "invalid inputs") + } + + // Create blocks + requiredProviders, err := createRequiredProviders(args.ExtraBlocksRootTerraform) + if err != nil { + return "", errors.Wrap(err, "failed to generate required providers") + } + + awsProvider, err := createAwsProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate aws provider") + } + + laceworkProvider, err := createLaceworkProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate lacework provider") + } + + configModule, err := createConfig(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate aws config module") + } + + cloudTrailModule, err := createCloudtrail(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate aws cloudtrail module") + } + + agentlessModule, err := createAgentless(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate aws agentless global module") + } + + outputBlocks := []*hclwrite.Block{} + for _, output := range args.CustomOutputs { + outputBlock, err := output.ToBlock() + if err != nil { + return "", errors.Wrap(err, "failed to add custom output") + } + outputBlocks = append(outputBlocks, outputBlock) + } + + // Render + hclBlocks := lwgenerate.CreateHclStringOutput( + lwgenerate.CombineHclBlocks( + requiredProviders, + awsProvider, + laceworkProvider, + configModule, + cloudTrailModule, + agentlessModule, + outputBlocks, + args.ExtraBlocks, + ), + ) + return hclBlocks, nil +} + +func createRequiredProviders(extraBlocks []*hclwrite.Block) (*hclwrite.Block, error) { + return lwgenerate.CreateRequiredProvidersWithCustomBlocks( + extraBlocks, + lwgenerate.NewRequiredProvider("lacework", + lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), + lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion))) +} + +func createAwsProvider(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + + attributes := map[string]interface{}{} + + // set custom args before the required ones below to ensure expected behavior (i.e., no overrides) + for k, v := range args.ExtraProviderArguments { + attributes[k] = v + } + + // required defaults + attributes["alias"] = "main" + attributes["region"] = args.AwsRegion + if args.AwsProfile != "" { + attributes["profile"] = args.AwsProfile + } + + modifiers := []lwgenerate.HclProviderModifier{ + lwgenerate.HclProviderWithAttributes(attributes), + } + + if len(args.ProviderDefaultTags) != 0 { + defaultTagsBlock, err := lwgenerate.HclCreateGenericBlock( + "default_tags", + nil, + map[string]interface{}{"tags": args.ProviderDefaultTags}, + ) + if err != nil { + return nil, err + } + modifiers = append(modifiers, lwgenerate.HclProviderWithGenericBlocks(defaultTagsBlock)) + } + + if args.AwsAssumeRole != "" { + assumeRoleBlock, err := lwgenerate.HclCreateGenericBlock( + "assume_role", + nil, + map[string]interface{}{"role_arn": args.AwsAssumeRole}, + ) + if err != nil { + return nil, err + } + modifiers = append(modifiers, lwgenerate.HclProviderWithGenericBlocks(assumeRoleBlock)) + } + + provider, err := lwgenerate.NewProvider("aws", modifiers...).ToBlock() + if err != nil { + return nil, err + } + + blocks = append(blocks, provider) + + accounts := []AwsSubAccount{} + accounts = append(accounts, args.AgentlessMonitoredAccounts...) + accounts = append(accounts, args.AgentlessScanningAccounts...) + accounts = append(accounts, args.ConfigAdditionalAccounts...) + if args.ControlTower { + accounts = append(accounts, *args.ControlTowerAuditAccount) + accounts = append(accounts, *args.ControlTowerLogArchiveAccount) + } + seenAccounts := []string{} + + for _, account := range accounts { + alias := account.AwsRegion + if account.Alias != "" { + alias = account.Alias + } else if account.AwsProfile != "" { + alias = fmt.Sprintf("%s-%s", account.AwsProfile, account.AwsRegion) + } + // Skip duplicate account + if slices.Contains(seenAccounts, alias) { + continue + } + seenAccounts = append(seenAccounts, alias) + + attributes := map[string]interface{}{} + // set `access_key`, `secret_key` and `token` for single-account multiple-region Agentless + if args.Agentless { + for k, v := range args.ExtraProviderArguments { + attributes[k] = v + } + } + attributes["alias"] = alias + attributes["region"] = account.AwsRegion + if args.AwsProfile != "" { + attributes["profile"] = account.AwsProfile + } + + providerBlock, err := lwgenerate.NewProvider( + "aws", + lwgenerate.HclProviderWithAttributes(attributes), + ).ToBlock() + if err != nil { + return nil, err + } + + blocks = append(blocks, providerBlock) + } + + return blocks, nil +} + +func createLaceworkProvider(args *GenerateAwsTfConfigurationArgs) (*hclwrite.Block, error) { + lwProviderAttributes := map[string]any{} + + if args.LaceworkProfile != "" { + lwProviderAttributes["profile"] = args.LaceworkProfile + } + + if args.LaceworkOrganizationLevel { + lwProviderAttributes["organization"] = true + } + + if len(lwProviderAttributes) > 0 { + return lwgenerate.NewProvider( + "lacework", lwgenerate.HclProviderWithAttributes(lwProviderAttributes), + ).ToBlock() + } + return nil, nil +} + +func createConfig(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, error) { + if !args.Config { + return nil, nil + } + + blocks := []*hclwrite.Block{} + + if args.AwsOrganization { + block, err := lwgenerate.NewModule( + "aws_org_configuration", + lwgenerate.AwsConfigOrgSource, + lwgenerate.HclModuleWithVersion(lwgenerate.AwsConfigOrgVersion), + lwgenerate.HclModuleWithProviderDetails(map[string]string{"aws": "aws.main"}), + lwgenerate.HclModuleWithAttributes( + map[string]interface{}{ + "lacework_account": args.ConfigOrgLWAccount, + "lacework_subaccount": args.ConfigOrgLWSubaccount, + "lacework_access_key_id": args.ConfigOrgLWAccessKeyId, + "lacework_secret_key": args.ConfigOrgLWSecretKey, + "organization_id": args.ConfigOrgId, + "organization_unit": args.ConfigOrgUnits, + "cf_resource_prefix": args.ConfigOrgCfResourcePrefix, + }, + ), + ).ToBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, block) + return blocks, nil + } + + moduleDetails := []lwgenerate.HclModuleModifier{ + lwgenerate.HclModuleWithVersion(lwgenerate.AwsConfigVersion), + lwgenerate.HclModuleWithProviderDetails(map[string]string{"aws": "aws.main"}), + } + if args.LaceworkAccountID != "" { + moduleDetails = append( + moduleDetails, + lwgenerate.HclModuleWithAttributes( + map[string]interface{}{"lacework_aws_account_id": args.LaceworkAccountID}, + ), + ) + } + + moduleBlock, err := lwgenerate.NewModule( + "aws_config", + lwgenerate.AwsConfigSource, + moduleDetails..., + ).ToBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, moduleBlock) + + for _, account := range args.ConfigAdditionalAccounts { + configModule, err := lwgenerate.NewModule( + fmt.Sprintf("aws_config_%s", account.Alias), + lwgenerate.AwsConfigSource, + lwgenerate.HclModuleWithVersion(lwgenerate.AwsConfigVersion), + lwgenerate.HclModuleWithProviderDetails(map[string]string{ + "aws": fmt.Sprintf("aws.%s", account.Alias), + }), + ).ToBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, configModule) + } + + return blocks, nil +} + +func createCloudtrail(args *GenerateAwsTfConfigurationArgs) (*hclwrite.Block, error) { + if !args.Cloudtrail { + return nil, nil + } + + source := lwgenerate.AwsCloudTrailSource + version := lwgenerate.AwsCloudTrailVersion + if args.ControlTower { + source = lwgenerate.AwsCloudTrailControlTowerSource + version = lwgenerate.AwsCloudTrailControlTowerVersion + } + attributes := map[string]interface{}{} + providerDetails := map[string]string{"aws": "aws.main"} + + if args.LaceworkAccountID != "" { + attributes["lacework_aws_account_id"] = args.LaceworkAccountID + } + + if args.AwsOrganization && args.ControlTower { + attributes["s3_bucket_arn"] = args.ExistingCloudtrailBucketArn + attributes["sns_topic_arn"] = args.ExistingSnsTopicArn + if args.ControlTowerKmsKeyArn != "" { + attributes["kms_key_arn"] = args.ControlTowerKmsKeyArn + } + providerDetails = map[string]string{ + "aws.audit": fmt.Sprintf("aws.%s", args.ControlTowerAuditAccount.Alias), + "aws.log_archive": fmt.Sprintf("aws.%s", args.ControlTowerLogArchiveAccount.Alias), + } + } else { + if args.ConsolidatedCloudtrail { + attributes["consolidated_trail"] = true + } + + if args.UseCloudTrailRandomName { + uid := uuid.New().String()[:8] + attributes["cloudtrail_name"] = fmt.Sprintf("lacework-cloudtrail-%s", uid) + } + + // S3 Bucket attributes + if args.CloudtrailUseExistingTrail { + attributes["use_existing_cloudtrail"] = true + attributes["cloudtrail_name"] = args.CloudtrailName + attributes["bucket_arn"] = args.ExistingCloudtrailBucketArn + } else { + if args.BucketName != "" { + attributes["bucket_name"] = args.BucketName + } + } + if args.BucketEncryptionEnabledSet { + if args.BucketEncryptionEnabled { + if args.BucketSseKeyArn != "" { + attributes["bucket_sse_key_arn"] = args.BucketSseKeyArn + } + } else { + attributes["bucket_encryption_enabled"] = false + } + } + if args.S3BucketNotification { + attributes["use_s3_bucket_notification"] = true + } + // SNS Attributes + if args.CloudtrailUseExistingSNSTopic { + attributes["use_existing_sns_topic"] = true + if args.ExistingSnsTopicArn != "" { + attributes["sns_topic_arn"] = args.ExistingSnsTopicArn + } + } else { + if args.SnsTopicName != "" { + attributes["sns_topic_name"] = args.SnsTopicName + } + if args.SnsEncryptionEnabledSet { + if args.SnsTopicEncryptionEnabled { + if args.SnsTopicEncryptionKeyArn != "" { + attributes["sns_topic_encryption_key_arn"] = args.SnsTopicEncryptionKeyArn + } + } else { + attributes["sns_topic_encryption_enabled "] = false + } + } + } + // SQS Attributes + if args.SqsQueueName != "" { + attributes["sqs_queue_name"] = args.SqsQueueName + } + if args.SqsEncryptionEnabledSet { + if args.SqsEncryptionEnabled { + if args.SqsEncryptionKeyArn != "" { + attributes["sqs_encryption_key_arn"] = args.SqsEncryptionKeyArn + } + } else { + attributes["sqs_encryption_enabled "] = false + } + } + } + + // Aws Organization CloudTrail + if args.AwsOrganization { + if !args.ControlTower { + attributes["is_organization_trail"] = true + } + if !args.OrgAccountMappings.IsEmpty() { + orgAccountMappings, err := args.OrgAccountMappings.ToMap() + if err != nil { + return nil, errors.Wrap(err, "unable to parse 'org_account_mappings'") + } + attributes["org_account_mappings"] = []map[string]any{orgAccountMappings} + } + } + + if args.ExistingIamRole.IsEmpty() && args.Config && !args.AwsOrganization { + attributes["use_existing_iam_role"] = true + attributes["iam_role_name"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "aws_config", "iam_role_name"}) + attributes["iam_role_arn"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "aws_config", "iam_role_arn"}) + attributes["iam_role_external_id"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "aws_config", "external_id"}) + } + + if !args.ExistingIamRole.IsEmpty() { + attributes["use_existing_iam_role"] = true + attributes["iam_role_name"] = args.ExistingIamRole.Name + attributes["iam_role_arn"] = args.ExistingIamRole.Arn + attributes["iam_role_external_id"] = args.ExistingIamRole.ExternalId + } + + return lwgenerate.NewModule( + "main_cloudtrail", + source, + lwgenerate.HclModuleWithVersion(version), + lwgenerate.HclModuleWithProviderDetails(providerDetails), + lwgenerate.HclModuleWithAttributes(attributes), + ).ToBlock() +} + +func createAgentless(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, error) { + if !args.Agentless { + return nil, nil + } + + blocks := []*hclwrite.Block{} + + if args.AwsOrganization { + // Create Agenetless integration for organization + + // Add management module + managementModule, err := lwgenerate.NewModule( + "lacework_aws_agentless_management_scanning_role", + lwgenerate.AwsAgentlessSource, + lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), + lwgenerate.HclModuleWithProviderDetails(map[string]string{ + "aws": "aws.main", + }), + lwgenerate.HclModuleWithAttributes(map[string]interface{}{ + "snapshot_role": true, + "global_module_reference": lwgenerate.CreateSimpleTraversal( + []string{"module", "lacework_aws_agentless_scanning_global"}, + ), + }), + ).ToBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, managementModule) + + // Add global scanning module + monitoredAccountIDs := []string{} + for _, accountID := range args.AgentlessMonitoredAccountIDs { + monitoredAccountIDs = append(monitoredAccountIDs, fmt.Sprintf("\"%s\"", accountID)) + } + globalModule, err := lwgenerate.NewModule( + "lacework_aws_agentless_scanning_global", + lwgenerate.AwsAgentlessSource, + lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), + lwgenerate.HclModuleWithAttributes(map[string]interface{}{ + "global": true, + "regional": true, + "organization": lwgenerate.CreateMapTraversalTokens(map[string]string{ + "management_account": fmt.Sprintf("\"%s\"", args.AgentlessManagementAccountID), + "monitored_accounts": fmt.Sprintf("[%s]", strings.Join(monitoredAccountIDs, ", ")), + }), + }), + lwgenerate.HclModuleWithProviderDetails( + map[string]string{"aws": fmt.Sprintf("aws.%s", args.AgentlessScanningAccounts[0].Alias)}, + ), + ).ToBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, globalModule) + + // Add regional scanning modules + for _, account := range args.AgentlessScanningAccounts[1:] { + regionModule, err := lwgenerate.NewModule( + fmt.Sprintf("lacework_aws_agentless_scanning_region_%s", account.Alias), + lwgenerate.AwsAgentlessSource, + lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), + lwgenerate.HclModuleWithProviderDetails(map[string]string{ + "aws": fmt.Sprintf("aws.%s", account.Alias), + }), + lwgenerate.HclModuleWithAttributes( + map[string]interface{}{ + "regional": true, + "global_module_reference": lwgenerate.CreateSimpleTraversal( + []string{"module", "lacework_aws_agentless_scanning_global"}, + ), + }, + ), + ).ToBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, regionModule) + } + + // Add monitored modules + for _, monitoredAccount := range args.AgentlessMonitoredAccounts { + monitoredModule, err := lwgenerate.NewModule( + fmt.Sprintf("lacework_aws_agentless_monitored_scanning_role_%s", monitoredAccount.Alias), + lwgenerate.AwsAgentlessSource, + lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), + lwgenerate.HclModuleWithProviderDetails(map[string]string{ + "aws": fmt.Sprintf("aws.%s", monitoredAccount.Alias), + }), + lwgenerate.HclModuleWithAttributes( + map[string]interface{}{ + "snapshot_role": true, + "global_module_reference": lwgenerate.CreateSimpleTraversal( + []string{"module", "lacework_aws_agentless_scanning_global"}, + ), + }, + ), + ).ToBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, monitoredModule) + } + + autoDeploymentBlock, err := lwgenerate.HclCreateGenericBlock( + "auto_deployment", + nil, + map[string]interface{}{"enabled": true, "retain_stacks_on_account_removal": false}, + ) + if err != nil { + return nil, err + } + lifecycleBlock, err := lwgenerate.HclCreateGenericBlock( + "lifecycle", + nil, + map[string]interface{}{ + "ignore_changes": lwgenerate.CreateSimpleTraversal([]string{"[administration_role_arn]"}), + }, + ) + if err != nil { + return nil, err + } + + stacksetResource, err := lwgenerate.NewResource( + "aws_cloudformation_stack_set", + "snapshot_role", + lwgenerate.HclResourceWithAttributesAndProviderDetails( + map[string]interface{}{ + "capabilities": lwgenerate.CreateSimpleTraversal([]string{"[\"CAPABILITY_NAMED_IAM\"]"}), + "description": "Lacework AWS Agentless Workload Scanning Organization Roles", + "name": "lacework-agentless-scanning-stackset", + "permission_model": "SERVICE_MANAGED", + "template_url": "https://agentless-workload-scanner.s3.amazonaws.com" + + "/cloudformation-lacework/latest/snapshot-role.json", + "parameters": lwgenerate.CreateMapTraversalTokens(map[string]string{ + "ExternalId": "module.lacework_aws_agentless_scanning_global.external_id", + "ECSTaskRoleArn": "module.lacework_aws_agentless_scanning_global.agentless_scan_ecs_task_role_arn", + "ResourceNamePrefix": "module.lacework_aws_agentless_scanning_global.prefix", + "ResourceNameSuffix": "module.lacework_aws_agentless_scanning_global.suffix", + }), + }, + []string{"aws.main"}, + ), + lwgenerate.HclResourceWithGenericBlocks(autoDeploymentBlock, lifecycleBlock), + ).ToResourceBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, stacksetResource) + + // Get OU IDs for the organizational_unit_ids attribute + OUIDs := []string{} + for _, accountID := range args.AgentlessMonitoredAccountIDs { + if strings.HasPrefix(accountID, "ou-") || strings.HasPrefix(accountID, "r-") { + OUIDs = append(OUIDs, fmt.Sprintf("\"%s\"", accountID)) + } + } + + deploymentTargetsBlock, err := lwgenerate.HclCreateGenericBlock( + "deployment_targets", + nil, + map[string]interface{}{"organizational_unit_ids": lwgenerate.CreateSimpleTraversal( + []string{fmt.Sprintf("[%s]", strings.Join(OUIDs, ","))}, + )}, + ) + if err != nil { + return nil, err + } + stacksetInstanceResource, err := lwgenerate.NewResource( + "aws_cloudformation_stack_set_instance", + "snapshot_role", + lwgenerate.HclResourceWithAttributesAndProviderDetails( + map[string]interface{}{ + "stack_set_name": lwgenerate.CreateSimpleTraversal( + []string{"aws_cloudformation_stack_set", "snapshot_role", "name"}, + ), + }, + []string{"aws.main"}, + ), + lwgenerate.HclResourceWithGenericBlocks(deploymentTargetsBlock), + ).ToResourceBlock() + + if err != nil { + return nil, err + } + + blocks = append(blocks, stacksetInstanceResource) + } else { + // Create Agenetless integration for single account + globalModule, err := lwgenerate.NewModule( + "lacework_aws_agentless_scanning_global", + lwgenerate.AwsAgentlessSource, + lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), + lwgenerate.HclModuleWithAttributes(map[string]interface{}{"global": true, "regional": true}), + lwgenerate.HclModuleWithProviderDetails( + map[string]string{"aws": "aws.main"}, + ), + ).ToBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, globalModule) + + for _, account := range args.AgentlessScanningAccounts { + regionModule, err := lwgenerate.NewModule( + fmt.Sprintf("lacework_aws_agentless_scanning_region_%s", account.Alias), + lwgenerate.AwsAgentlessSource, + lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), + lwgenerate.HclModuleWithProviderDetails(map[string]string{ + "aws": fmt.Sprintf("aws.%s", account.Alias), + }), + lwgenerate.HclModuleWithAttributes( + map[string]interface{}{ + "regional": true, + "global_module_reference": lwgenerate.CreateSimpleTraversal( + []string{"module", "lacework_aws_agentless_scanning_global"}, + ), + }, + ), + ).ToBlock() + if err != nil { + return nil, err + } + blocks = append(blocks, regionModule) + } + } + + return blocks, nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/aws_controltower/aws_controltower.go b/vendor/github.com/lacework/go-sdk/lwgenerate/aws_controltower/aws_controltower.go new file mode 100644 index 000000000..9a91b3f3a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/aws_controltower/aws_controltower.go @@ -0,0 +1,477 @@ +package aws_controltower + +import ( + "encoding/json" + + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/lacework/go-sdk/lwgenerate" + "github.com/pkg/errors" +) + +type GenerateAwsControlTowerTfConfigurationArgs struct { + // For AWS Subaccounts in consolidated CT setups + SubAccounts []AwsSubAccount + + // ARN for the S3 bucket for consolidated CloudTrail logging + S3BucketArn string + + // The SNS topic ARN + SNSTopicArn string + + // The Aws profile of the log archive account + LogArchiveProfile string + + // The Aws region of the log archive account + LogArchiveRegion string + + // The Aws profile of the audit account + AuditProfile string + + // The Aws region of the audit account + AuditRegion string + + // The audit account flag input in the format profile:region + AuditAccount string + + // The log archive account flag input in the format profile:region + LogArchiveAccount string + + // A name for the cross account policy + CrossAccountPolicyName string + + // Whether cloudtrail log file integrity validation is enabled + EnableLogFileValidation bool + + // The length of the external ID to generate. Max length is 1224. Ignored when use_existing_iam_role is set to true + ExternalIdLength int + + // The IAM role ARN is required when setting use_existing_iam_role to true + IamRoleArn string + + // The external ID configured inside the IAM role is required when setting use_existing_iam_role to true + IamRoleExternalID string + + // The IAM role name. Required to match with iam_role_arn if use_existing_iam_role is set to true + IamRoleName string + + //The Lacework AWS account that the IAM role will grant access + LaceworkAwsAccountID string + + // The name of the integration in Lacework. + LaceworkIntegrationName string + + // The prefix that will be used at the beginning of every generated resource + Prefix string + + // The SQS queue name + SqsQueueName string + + // A map/dictionary of Tags to be assigned to created resources + Tags map[string]string + + // Set this to true to use an existing IAM role from the log_archive AWS Account + UseExistingIamRole bool + + // Amount of time to wait before the next resource is provisioned + WaitTime int + + // The KMS key arn, if Control Tower was deployed with custom KMS key + KmsKeyArn string + + // Mapping of AWS accounts to Lacework accounts within a Lacework organization + OrgAccountMappings OrgAccountMapping + + // OrgAccountMapping json used for flag input + OrgAccountMappingsJson string + + // Lacework Profile to use + LaceworkProfile string + + // Lacework Organization + LaceworkOrganizationLevel bool + + // The Lacework AWS Root Account ID + LaceworkAccountID string +} + +type OrgAccountMapping struct { + DefaultLaceworkAccount string `json:"default_lacework_account"` + Mapping []OrgAccountMap `json:"mapping"` +} + +type OrgAccountMap struct { + LaceworkAccount string `json:"lacework_account"` + AwsAccounts []string `json:"aws_accounts"` +} + +func (args GenerateAwsControlTowerTfConfigurationArgs) GetSubAccounts() []AwsSubAccount { + return args.SubAccounts +} + +func (args *GenerateAwsControlTowerTfConfigurationArgs) GetLaceworkProfile() string { + return args.LaceworkProfile +} + +func (args *GenerateAwsControlTowerTfConfigurationArgs) Generate() (string, error) { + // Validate inputs + if err := args.validate(); err != nil { + return "", errors.Wrap(err, "invalid inputs") + } + + // Create blocks + requiredProviders, err := createRequiredProviders() + if err != nil { + return "", errors.Wrap(err, "failed to generate required providers") + } + + awsProvider, err := createAwsProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate aws provider") + } + + laceworkProvider, err := createLaceworkProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate lacework provider") + } + + // controlTower + controlTowerModule, err := createCloudTrailControlTower(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate aws controlTower module") + } + + // Render + hclBlocks := lwgenerate.CreateHclStringOutput( + lwgenerate.CombineHclBlocks( + requiredProviders, + awsProvider, + laceworkProvider, + controlTowerModule, + ), + ) + return hclBlocks, nil +} + +type AwsSubAccount struct { + // The name of the AwsProfile to use (in AWS configuration) + AwsProfile string + + // The AwsRegion this profile should use if any resources are created + AwsRegion string + + // The Alias of the provider block + Alias string +} + +func NewAwsSubAccount(profile string, region string, alias ...string) AwsSubAccount { + subaccount := AwsSubAccount{AwsProfile: profile, AwsRegion: region} + if len(alias) > 0 { + subaccount.Alias = alias[0] + } + return subaccount +} + +func (args GenerateAwsControlTowerTfConfigurationArgs) validate() error { + // Validate s3 bucket arn has been set + if args.S3BucketArn == "" { + return errors.New("s3 bucket arn must be set") + } + // Validate sns topic arn has been set + if args.SNSTopicArn == "" { + return errors.New("sns topic arn must be set") + } + // Validate log and audit accounts archive + if len(args.SubAccounts) == 0 { + return errors.New("log archive and audit accounts must be set") + } + + // Validate existing role IAM values, if set + if args.UseExistingIamRole { + if args.IamRoleArn == "" || + args.IamRoleName == "" || + args.IamRoleExternalID == "" { + return errors.New("when using an existing IAM role, existing role ARN, name, and external ID all must be set") + } + } + + return nil +} + +type AwsControlTowerTerraformModifier func(c *GenerateAwsControlTowerTfConfigurationArgs) + +// NewTerraform returns an instance of the GenerateAwsControlTowerTfConfigurationArgs struct. +// +// Note: Additional configuration details may be set using modifiers of the AwsControlTowerTerraformModifier type +// +// Basic usage: Initialize a new AwsControlTowerTerraformModifier struct, with a non-default AWS profile set. Then +// use generate to create a string output of the required HCL. +// +// hcl, err := aws_controltower.NewTerraform("us-east-1") +// .WithAwsProfile("mycorp-profile")).Generate() +func NewTerraform(s3BucketArn string, snsTopicArn string, + mods ...AwsControlTowerTerraformModifier) *GenerateAwsControlTowerTfConfigurationArgs { + config := &GenerateAwsControlTowerTfConfigurationArgs{ + S3BucketArn: s3BucketArn, + SNSTopicArn: snsTopicArn, + } + for _, m := range mods { + m(config) + } + return config +} + +func WithCrossAccountPolicyName(name string) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.CrossAccountPolicyName = name + } +} + +func WithLaceworkAccountID(account string) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.LaceworkAccountID = account + } +} + +func WithLaceworkProfile(profile string) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.LaceworkProfile = profile + } +} + +func WithEnableLogFileValidation() AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.EnableLogFileValidation = true + } +} + +func WithExternalIdLength(length int) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.ExternalIdLength = length + } +} + +func WithWaitTime(waitTime int) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.WaitTime = waitTime + } +} + +func WithTags(tags map[string]string) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.Tags = tags + } +} + +func WithKmsKeyArn(arn string) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.KmsKeyArn = arn + } +} + +func WithExisitingIamRole(arn string, name string, externalID string) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.IamRoleArn = arn + c.IamRoleExternalID = externalID + c.IamRoleName = name + c.UseExistingIamRole = true + } +} + +func WithPrefix(prefix string) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.Prefix = prefix + } +} + +func WithSqsQueueName(name string) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.SqsQueueName = name + } +} + +func WithOrgAccountMappings(mapping OrgAccountMapping) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.OrgAccountMappings = mapping + } +} + +func WithLaceworkIntegrationName(name string) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.LaceworkIntegrationName = name + } +} + +func WithLaceworkOrgLevel() AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.LaceworkOrganizationLevel = true + } +} + +func WithSubaccounts(subaccounts ...AwsSubAccount) AwsControlTowerTerraformModifier { + return func(c *GenerateAwsControlTowerTfConfigurationArgs) { + c.SubAccounts = subaccounts + } +} + +func createCloudTrailControlTower(args *GenerateAwsControlTowerTfConfigurationArgs) (*hclwrite.Block, error) { + attributes := map[string]interface{}{} + modDetails := []lwgenerate.HclModuleModifier{ + lwgenerate.HclModuleWithVersion(lwgenerate.AwsCloudTrailControlTowerVersion)} + + //required args + if args.S3BucketArn != "" { + attributes["s3_bucket_arn"] = args.S3BucketArn + } + if args.SNSTopicArn != "" { + attributes["sns_topic_arn"] = args.SNSTopicArn + } + + // optional args + if args.LaceworkAccountID != "" { + attributes["lacework_aws_account_id"] = args.LaceworkAccountID + } + + if args.CrossAccountPolicyName != "" { + attributes["cross_account_policy_name"] = args.CrossAccountPolicyName + } + if args.EnableLogFileValidation { + attributes["enable_log_file_validation"] = args.EnableLogFileValidation + } + if args.ExternalIdLength != 0 { + attributes["external_id_length"] = args.ExternalIdLength + } + if args.WaitTime != 0 { + attributes["wait_time"] = args.WaitTime + } + if len(args.Tags) != 0 { + attributes["tags"] = args.Tags + } + if args.SqsQueueName != "" { + attributes["sqs_queue_name"] = args.SqsQueueName + } + if args.Prefix != "" { + attributes["prefix"] = args.Prefix + } + if !args.OrgAccountMappings.IsEmpty() { + orgAccountMappings, err := args.OrgAccountMappings.ToMap() + if err != nil { + return nil, errors.Wrap(err, "unable to parse 'org_account_mappings'") + } + attributes["org_account_mappings"] = []map[string]any{orgAccountMappings} + } + if args.LaceworkIntegrationName != "" { + attributes["lacework_integration_name"] = args.LaceworkIntegrationName + } + if args.KmsKeyArn != "" { + attributes["kms_key_arn"] = args.KmsKeyArn + } + + // existing iam role + if args.UseExistingIamRole { + attributes["use_existing_iam_role"] = true + attributes["iam_role_arn"] = args.IamRoleArn + attributes["iam_role_name"] = args.IamRoleName + attributes["iam_role_external_id"] = args.IamRoleExternalID + } + + modDetails = append(modDetails, lwgenerate.HclModuleWithProviderDetails( + map[string]string{"aws.audit": "aws.audit", "aws.log_archive": "aws.log_archive"})) + + modDetails = append(modDetails, + lwgenerate.HclModuleWithAttributes(attributes), + ) + + return lwgenerate.NewModule( + "lacework_aws_controltower", + lwgenerate.AwsCloudTrailControlTowerSource, + modDetails..., + ).ToBlock() + +} + +func createRequiredProviders() (*hclwrite.Block, error) { + return lwgenerate.CreateRequiredProviders( + lwgenerate.NewRequiredProvider("lacework", + lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), + lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion))) +} + +func createLaceworkProvider(args *GenerateAwsControlTowerTfConfigurationArgs) (*hclwrite.Block, error) { + lwProviderAttributes := map[string]any{} + + if args.LaceworkProfile != "" { + lwProviderAttributes["profile"] = args.LaceworkProfile + } + + if args.LaceworkOrganizationLevel { + lwProviderAttributes["organization"] = true + } + + if len(lwProviderAttributes) > 0 { + return lwgenerate.NewProvider( + "lacework", lwgenerate.HclProviderWithAttributes(lwProviderAttributes)).ToBlock() + } + + return nil, nil +} + +func createAwsProvider(args *GenerateAwsControlTowerTfConfigurationArgs) ([]*hclwrite.Block, error) { + var blocks []*hclwrite.Block + if len(args.SubAccounts) > 0 { + for _, subaccount := range args.SubAccounts { + alias := subaccount.AwsProfile + if subaccount.Alias != "" { + alias = subaccount.Alias + } + attrs := map[string]interface{}{ + "alias": alias, + "profile": subaccount.AwsProfile, + "region": subaccount.AwsRegion, + } + providerBlock, err := lwgenerate.NewProvider( + "aws", + lwgenerate.HclProviderWithAttributes(attrs), + ).ToBlock() + + if err != nil { + return nil, err + } + + blocks = append(blocks, providerBlock) + } + } + + return blocks, nil +} + +func (orgMap *OrgAccountMapping) IsEmpty() bool { + return len(orgMap.Mapping) == 0 && orgMap.DefaultLaceworkAccount == "" +} + +func (orgMap *OrgAccountMapping) ToMap() (map[string]any, error) { + var mappings []map[string]any + mappingsJsonString, err := json.Marshal(orgMap.Mapping) + if err != nil { + return nil, err + } + err = json.Unmarshal(mappingsJsonString, &mappings) + if err != nil { + return nil, err + } + + orgMap.Mapping = []OrgAccountMap{} + + result := make(map[string]any) + jsonString, err := json.Marshal(orgMap) + if err != nil { + return nil, err + } + + err = json.Unmarshal(jsonString, &result) + if err != nil { + return nil, err + } + + result["mapping"] = mappings + return result, nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/aws_eks_audit/aws_eks_audit.go b/vendor/github.com/lacework/go-sdk/lwgenerate/aws_eks_audit/aws_eks_audit.go new file mode 100644 index 000000000..b9a0cf384 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/aws_eks_audit/aws_eks_audit.go @@ -0,0 +1,697 @@ +package aws_eks_audit + +import ( + "fmt" + "sort" + + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/lacework/go-sdk/lwgenerate" + "github.com/pkg/errors" + "github.com/zclconf/go-cty/cty" +) + +type ExistingCrossAccountIamRoleDetails struct { + // Existing IAM Role ARN + Arn string + + // Existing IAM Role External ID + ExternalId string +} + +func (e *ExistingCrossAccountIamRoleDetails) IsPartial() bool { + // If nil, return false + if e == nil { + return false + } + + // If all values are empty, return false + if e.Arn == "" && e.ExternalId == "" { + return false + } + + // If all values are populated, return false + if e.Arn != "" && e.ExternalId != "" { + return false + } + + return true +} + +// NewExistingCrossAccountIamRoleDetails Create new existing IAM role details +func NewExistingCrossAccountIamRoleDetails(arn string, externalId string) *ExistingCrossAccountIamRoleDetails { + return &ExistingCrossAccountIamRoleDetails{ + Arn: arn, + ExternalId: externalId, + } +} + +type GenerateAwsEksAuditTfConfigurationArgs struct { + + // Use Existing Required Providers + // disable writing required_providers block + UseExistingRequiredProviders bool + + // Add prefix to provider alias names + ProviderAliasPrefix string + + // Supply an AWS Profile name + AwsProfile string + + // Should we require MFA for object deletion? + BucketEnableMfaDelete bool + + // Should we enable bucket encryption? + BucketEnableEncryption bool + + // Should we force destroy the bucket if it has stuff in it? + // DEPRECATED + BucketForceDestroy bool + + // The lifetime, in days, of the bucket objects. The value must be a non-zero positive integer + BucketLifecycleExpirationDays int + + // The encryption algorithm to use for S3 bucket server-side encryption + BucketSseAlgorithm string + + // Should we use an existing KMS key for the bucket? + ExistingBucketKmsKey bool + + // The ARN of the KMS encryption key to be used for S3 + // (Required when bucket_sse_algorithm is aws:kms and using an existing kms key) + BucketSseKeyArn string + + // Should we enable bucket versioning? + BucketVersioning bool + + // The name of the AWS EKS Audit Log integration in Lacework. Defaults to "TF AWS EKS Audit Log" + EksAuditIntegrationName string + + // Optionally supply existing cloudwatch IAM role ARN + ExistingCloudWatchIamRoleArn string + + // Optionally supply existing cross account IAM role details + ExistingCrossAccountIamRole *ExistingCrossAccountIamRoleDetails + + // Should we allow the user to configure an existing Firehose IAM role? + ExistingFirehoseIam bool + + // Optionally supply existing firehose role ARN if ExistingFirehoseIam is true + ExistingFirehoseIamRoleArn string + + // The Cloudwatch Log Subscription Filter pattern + FilterPattern string + + // Should encryption be enabled on the created firehose? Defaults to true. + FirehoseEncryptionEnabled bool + + // The ARN of an existing KMS encryption key to be used for the Kinesis Firehose + FirehoseEncryptionKeyArn string + + // The waiting period, specified in number of days. Defaults to 30. + KmsKeyDeletionDays int + + // Whether the KMS key is a multi-region or regional key + KmsKeyMultiRegion bool + + // Enable KMS automatic key rotation + KmsKeyRotation bool + + // The prefix that will be used at the beginning of every generated resource. Defaults to "lw-eks-al" + Prefix string + + // Parsed version of RegionClusterMap + RegionClusterMap map[string]string + + // Parsed version of RegionClusterMap + ParsedRegionClusterMap map[string][]string + + // Parsed Regions list + ParsedRegionsList []string + + // Should encryption be enabled for the sns topic? Defaults to true + SnsTopicEncryptionEnabled bool + + // The ARN of an existing KMS encryption key to be used for the SNS topic + SnsTopicEncryptionKeyArn string + + // Lacework Profile to use + LaceworkProfile string + + // The Lacework AWS Root Account ID + LaceworkAccountID string + + // Should we use an existing customer supplied bucket? Defaults to false + UseExistinglBucket bool + + // Existing S3 Bucket ARN (Required when using existing bucket) + ExistinglBucketArn string +} + +// Ensure all combinations of inputs our valid for supported spec +func (args *GenerateAwsEksAuditTfConfigurationArgs) validate() error { + + // Validate that at least one region with clusters was set + if len(args.ParsedRegionClusterMap) == 0 { + return errors.New("At least one region with a list of clusters must be set") + } + + // Validate, at least 1 cluster must be supplied per region + for _, clusters := range args.ParsedRegionClusterMap { + if len(clusters) == 0 { + return errors.New("At least one cluster must be supplied per region") + } + } + + // Validate existing role IAM values, if set + if args.ExistingCrossAccountIamRole != nil { + if args.ExistingCrossAccountIamRole.Arn == "" || + args.ExistingCrossAccountIamRole.ExternalId == "" { + return errors.New( + "when using an existing cross account IAM role, existing role ARN and external ID all must be set", + ) + } + } + + return nil +} + +type AwsEksAuditTerraformModifier func(c *GenerateAwsEksAuditTfConfigurationArgs) + +// NewTerraform returns an instance of the GenerateAwsEksAuditTfConfigurationArgs struct. +// +// Note: Additional configuration details may be set using modifiers of the AwsEksAuditTerraformModifier type +// +// Basic usage: Initialize a new AwsEksAuditTerraformModifier struct, with a non-default AWS profile set. Then +// use generate to create a string output of the required HCL. +// +// hcl, err := aws.NewTerraform({"us-east-1": ["cluster1", "cluster2"], "us-east-2": ["cluster3"]} +// aws.WithAwsProfile("mycorp-profile")).Generate() +func NewTerraform(mods ...AwsEksAuditTerraformModifier) *GenerateAwsEksAuditTfConfigurationArgs { + config := &GenerateAwsEksAuditTfConfigurationArgs{ + BucketVersioning: true, + BucketEnableEncryption: true, + FirehoseEncryptionEnabled: true, + KmsKeyMultiRegion: true, + KmsKeyRotation: true, + SnsTopicEncryptionEnabled: true, + } + for _, m := range mods { + m(config) + } + return config +} + +// WithLaceworkAccountID Set the Lacework AWS root account ID to use +func WithLaceworkAccountID(accountID string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.LaceworkAccountID = accountID + } +} + +// WithAwsProfile Set the AWS Profile to utilize when integrating +func WithAwsProfile(name string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.AwsProfile = name + } +} + +// EnableBucketMfaDelete Set the S3 MfaDelete parameter to true for newly created buckets +func EnableBucketMfaDelete() AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.BucketEnableMfaDelete = true + } +} + +// EnableBucketEncryption Set the S3 Encryption parameter to true for newly created buckets +func EnableBucketEncryption(enable bool) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.BucketEnableEncryption = enable + } +} + +// WithBucketLifecycleExpirationDays Set the S3 Lifecycle Expiration Days parameter for newly created buckets +func WithBucketLifecycleExpirationDays(days int) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.BucketLifecycleExpirationDays = days + } +} + +// WithBucketSseAlgorithm Set the encryption algorithm to use for S3 bucket server-side encryption +func WithBucketSseAlgorithm(algorithm string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.BucketSseAlgorithm = algorithm + } +} + +// WithBucketSseKeyArn Set the ARN of the KMS encryption key to be used for S3 +// (Required when bucket_sse_algorithm is aws:kms and using an existing aws_kms_key) +func WithBucketSseKeyArn(arn string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.BucketSseKeyArn = arn + } +} + +// EnableBucketVersioning Set the S3 Bucket versioning parameter to true for newly created buckets +func EnableBucketVersioning(enable bool) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.BucketVersioning = enable + } +} + +// WithEksAuditIntegrationName Set the name of the EKS audit integration +func WithEksAuditIntegrationName(name string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.EksAuditIntegrationName = name + } +} + +// WithExistingCloudWatchIamRoleArn Set an existing cloudwatch IAM role ARN +func WithExistingCloudWatchIamRoleArn(arn string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.ExistingCloudWatchIamRoleArn = arn + } +} + +// WithExistingCrossAccountIamRole Set an existing cross account IAM role configuration to use with +// the created Terraform code +func WithExistingCrossAccountIamRole(iamDetails *ExistingCrossAccountIamRoleDetails) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.ExistingCrossAccountIamRole = iamDetails + } +} + +// WithExistingFirehoseIamRoleArn Set an existing firehose IAM role ARN +func WithExistingFirehoseIamRoleArn(arn string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.ExistingFirehoseIamRoleArn = arn + } +} + +// WithFilterPattern Set the filter pattern for the Cloudwatch subscription filter +func WithFilterPattern(pattern string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.FilterPattern = pattern + } +} + +// EnableFirehoseEncryption Set the firehose encryption parameter to true for newly created firehose +func EnableFirehoseEncryption(enable bool) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.FirehoseEncryptionEnabled = enable + } +} + +// WithFirehoseEncryptionKeyArn Set the ARN of an existing KMS encryption key to be used +// with the Kinesis Firehose +func WithFirehoseEncryptionKeyArn(arn string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.FirehoseEncryptionKeyArn = arn + } +} + +// WithKmsKeyDeletionDays Set the KMS deletion waiting period, specified in number of days +func WithKmsKeyDeletionDays(days int) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.KmsKeyDeletionDays = days + } +} + +// EnableKmsKeyMultiRegion Set whether the KMS key is a multi-region or regional key +func EnableKmsKeyMultiRegion(enable bool) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.KmsKeyMultiRegion = enable + } +} + +// EnableKmsKeyRotation Set KMS automatic key rotation to true +func EnableKmsKeyRotation(enable bool) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.KmsKeyRotation = enable + } +} + +// WithPrefix Set the prefix that will be used at the beginning of every generated resource +func WithPrefix(prefix string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.Prefix = prefix + } +} + +// WithExistingRequiredProviders disables writing required_providers in output +func WithExistingRequiredProviders() AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.UseExistingRequiredProviders = true + } +} + +// WithProviderAliasPrefix set the prefix to prepend to provider alias names +func WithProviderAliasPrefix(prefix string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.ProviderAliasPrefix = prefix + } +} + +// WithParsedRegionClusterMap Set the region cluster map. +// This is a list of clusters per AWS region +func WithParsedRegionClusterMap(regionClusterMap map[string][]string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.ParsedRegionClusterMap = regionClusterMap + } +} + +// EnableSnsTopicEncryption Set whether encryption should be enabled for the sns topic +func EnableSnsTopicEncryption(enable bool) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.SnsTopicEncryptionEnabled = enable + } +} + +// WithSnsTopicEncryptionKeyArn Set the ARN of an existing KMS encryption key to be used +// with the SNS Topic +func WithSnsTopicEncryptionKeyArn(arn string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.SnsTopicEncryptionKeyArn = arn + } +} + +// WithLaceworkProfile Set the Lacework Profile to utilize when integrating +func WithLaceworkProfile(name string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.LaceworkProfile = name + } +} + +// WithExistingBucketArn Set the Lacework Profile to utilize when integrating +func WithExistingBucketArn(name string) AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.ExistinglBucketArn = name + } +} + +// EnableUseExistingBucket Set the S3 ForceDestroy parameter to true for newly created buckets +func EnableUseExistingBucket() AwsEksAuditTerraformModifier { + return func(c *GenerateAwsEksAuditTfConfigurationArgs) { + c.UseExistinglBucket = true + } +} + +// Generate new Terraform code based on the supplied args. +func (args *GenerateAwsEksAuditTfConfigurationArgs) Generate() (string, error) { + // Validate inputs + if err := args.validate(); err != nil { + return "", errors.Wrap(err, "invalid inputs") + } + + // Create blocks + requiredProviders, err := createRequiredProviders(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate required providers") + } + + populateParsedRegionsList(args) + + awsProvider, err := createAwsProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate aws provider") + } + + laceworkProvider, err := createLaceworkProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate lacework provider") + } + + eksAuditModule, err := createEksAudit(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate aws eks audit module & resources") + } + + // Render + hclBlocks := lwgenerate.CreateHclStringOutput( + lwgenerate.CombineHclBlocks( + requiredProviders, + awsProvider, + laceworkProvider, + eksAuditModule), + ) + return hclBlocks, nil +} + +func createRequiredProviders(args *GenerateAwsEksAuditTfConfigurationArgs) (*hclwrite.Block, error) { + if args.UseExistingRequiredProviders { + return nil, nil + } + return lwgenerate.CreateRequiredProviders( + lwgenerate.NewRequiredProvider("lacework", + lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), + lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion))) +} + +func populateParsedRegionsList(args *GenerateAwsEksAuditTfConfigurationArgs) { + for region := range args.ParsedRegionClusterMap { + // append each region to args.ParsedRegionsList this will be used to sort the keys + // of the map and ensure ordering + args.ParsedRegionsList = append(args.ParsedRegionsList, region) + + // This sorted list will be used to ensure order when retrieving from the ParsedRegionClusterMap + sort.Strings(args.ParsedRegionsList) + } +} +func createProviderAlias(args *GenerateAwsEksAuditTfConfigurationArgs, baseName string) string { + aliasName := baseName + if args.ProviderAliasPrefix != "" { + aliasName = fmt.Sprintf("%s-%s", args.ProviderAliasPrefix, baseName) + } + + return aliasName +} + +func createAwsProvider(args *GenerateAwsEksAuditTfConfigurationArgs) ([]*hclwrite.Block, error) { + var blocks []*hclwrite.Block + for i := range args.ParsedRegionsList { + region := args.ParsedRegionsList[i] + + attrs := map[string]interface{}{ + "alias": createProviderAlias(args, region), + "region": region, + } + + if args.AwsProfile != "" { + attrs["profile"] = args.AwsProfile + } + + providerBlock, err := lwgenerate.NewProvider( + "aws", + lwgenerate.HclProviderWithAttributes(attrs), + ).ToBlock() + + if err != nil { + return nil, err + } + + blocks = append(blocks, providerBlock) + } + + // set kms key multi region to false if only 1 region is supplied + if len(args.ParsedRegionsList) == 1 { + args.KmsKeyMultiRegion = false + } + return blocks, nil +} + +func createLaceworkProvider(args *GenerateAwsEksAuditTfConfigurationArgs) (*hclwrite.Block, error) { + if args.LaceworkProfile != "" { + return lwgenerate.NewProvider( + "lacework", + lwgenerate.HclProviderWithAttributes(map[string]interface{}{"profile": args.LaceworkProfile}), + ).ToBlock() + } + return nil, nil +} + +func createEksAudit(args *GenerateAwsEksAuditTfConfigurationArgs) ([]*hclwrite.Block, error) { + var blocks []*hclwrite.Block + moduleAttrs := map[string]interface{}{} + resourceAttrs := map[string]interface{}{} + moduleDetails := []lwgenerate.HclModuleModifier{lwgenerate.HclModuleWithVersion(lwgenerate.AwsEksAuditVersion)} + + if args.LaceworkAccountID != "" { + moduleAttrs["lacework_aws_account_id"] = args.LaceworkAccountID + } + + if args.UseExistinglBucket { + moduleAttrs["use_existing_bucket"] = true + moduleAttrs["bucket_arn"] = args.ExistinglBucketArn + } + + if args.BucketEnableMfaDelete && args.BucketVersioning { + moduleAttrs["bucket_enable_mfa_delete"] = true + } + + if !args.BucketEnableEncryption { + moduleAttrs["bucket_encryption_enabled"] = args.BucketEnableEncryption + } + + if args.BucketLifecycleExpirationDays > 0 { + moduleAttrs["bucket_lifecycle_expiration_days"] = args.BucketLifecycleExpirationDays + } + + if args.BucketSseAlgorithm != "" && args.BucketEnableEncryption { + moduleAttrs["bucket_sse_algorithm"] = args.BucketSseAlgorithm + } + + if !args.BucketVersioning { + moduleAttrs["bucket_versioning_enabled"] = args.BucketVersioning + } + + if args.BucketSseKeyArn != "" && args.BucketEnableEncryption { + moduleAttrs["bucket_key_arn"] = args.BucketSseKeyArn + } + + if args.ExistingCloudWatchIamRoleArn != "" { + moduleAttrs["use_existing_cloudwatch_iam_role"] = true + moduleAttrs["cloudwatch_iam_role_arn"] = args.ExistingCloudWatchIamRoleArn + } + + if args.ExistingCrossAccountIamRole != nil { + moduleAttrs["use_existing_cross_account_iam_role"] = true + moduleAttrs["iam_role_arn"] = args.ExistingCrossAccountIamRole.Arn + moduleAttrs["iam_role_external_id"] = args.ExistingCrossAccountIamRole.ExternalId + } + + if args.ExistingFirehoseIamRoleArn != "" { + moduleAttrs["use_existing_firehose_iam_role"] = true + moduleAttrs["firehose_iam_role_arn"] = args.ExistingFirehoseIamRoleArn + } + + if args.FilterPattern != "" { + moduleAttrs["filter_pattern"] = args.FilterPattern + } + + if !args.FirehoseEncryptionEnabled { + moduleAttrs["kinesis_firehose_encryption_enabled"] = args.FirehoseEncryptionEnabled + } + + if args.FirehoseEncryptionKeyArn != "" && args.FirehoseEncryptionEnabled { + moduleAttrs["kinesis_firehose_key_arn"] = args.FirehoseEncryptionKeyArn + } + + if args.KmsKeyDeletionDays >= 7 && args.KmsKeyDeletionDays <= 30 { + moduleAttrs["kms_key_deletion_days"] = args.KmsKeyDeletionDays + } + + if !args.KmsKeyMultiRegion { + moduleAttrs["kms_key_multi_region"] = args.KmsKeyMultiRegion + } + + if !args.KmsKeyRotation { + moduleAttrs["kms_key_rotation"] = args.KmsKeyRotation + } + + if !args.SnsTopicEncryptionEnabled { + moduleAttrs["sns_topic_encryption_enabled"] = args.SnsTopicEncryptionEnabled + } + + if args.SnsTopicEncryptionKeyArn != "" && args.SnsTopicEncryptionEnabled { + moduleAttrs["sns_topic_key_arn"] = args.SnsTopicEncryptionKeyArn + } + + if len(args.ParsedRegionsList) > 1 { + // set no_cw_subscription_filter if we have more than 1 region in the ParsedRegionClusterMap + moduleAttrs["no_cw_subscription_filter"] = true + + // Add aws_cloudwatch_log_subscription_filter(s) resource per region + for i := range args.ParsedRegionsList { + region := args.ParsedRegionsList[i] + clusters := args.ParsedRegionClusterMap[region] + + // create hcl tokens for each cluster and create a token array to be added to our hcl + //tuple. (we are unable to add the for loop inside the call to TokensForTuple) + var tokens []hclwrite.Tokens + for _, cluster := range clusters { + tokens = append(tokens, hclwrite.TokensForValue(cty.StringVal(cluster))) + } + + // the for_each input must be in the following format toset(["", ""]) + // In order to achieve this we can use TokensForTuple to build the tuple `[]` + // then TokensForFunctionCall to wrap this with our call to the `toset()` function + resourceAttrs["for_each"] = hclwrite.TokensForFunctionCall( + "toset", + hclwrite.TokensForTuple(tokens), + ) + // Using hclwrite.Tokens as $ is not supported as part of string expression. + // Adding a single "$" would result in "$$" + resourceAttrs["name"] = hclwrite.Tokens{ + {Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`)}, + {Type: hclsyntax.TokenIdent, Bytes: []byte(`${module.aws_eks_audit_log.filter_prefix}-${each.value}`)}, + {Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)}, + } + resourceAttrs["log_group_name"] = hclwrite.Tokens{ + {Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`)}, + {Type: hclsyntax.TokenIdent, Bytes: []byte(`/aws/eks/${each.value}/cluster`)}, + {Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)}, + } + + resourceAttrs["role_arn"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "aws_eks_audit_log", "cloudwatch_iam_role_arn"}) + resourceAttrs["filter_pattern"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "aws_eks_audit_log", "filter_pattern"}) + resourceAttrs["destination_arn"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "aws_eks_audit_log", "firehose_arn"}) + + // the depends_on input must be in the following format [""] + // In order to achieve this we can use TokensForTuple to build the tuple `[]` + resourceAttrs["depends_on"] = hclwrite.TokensForTuple([]hclwrite.Tokens{ + hclwrite.TokensForTraversal( + lwgenerate.CreateSimpleTraversal([]string{"module", "aws_eks_audit_log"}), + ), + }) + + lwCwSubscriptionFilter, err := lwgenerate.NewResource( + "aws_cloudwatch_log_subscription_filter", + fmt.Sprintf( + "lw_cw_subscription_filter_%s", + region), + lwgenerate.HclResourceWithAttributesAndProviderDetails( + resourceAttrs, + []string{fmt.Sprintf("aws.%s", createProviderAlias(args, region))}, + ), + ).ToResourceBlock() + + if err != nil { + return nil, err + } + + blocks = append(blocks, lwCwSubscriptionFilter) + } + } else if len(args.ParsedRegionsList) > 0 { + // set no_cw_subscription_filter to false if we have only 1 region in the ParsedRegionClusterMap + moduleAttrs["no_cw_subscription_filter"] = false + for i := range args.ParsedRegionsList { + region := args.ParsedRegionsList[i] + clusters := args.ParsedRegionClusterMap[region] + moduleAttrs["cluster_names"] = clusters + } + } + + moduleAttrs["cloudwatch_regions"] = args.ParsedRegionsList + + moduleDetails = append(moduleDetails, + lwgenerate.HclModuleWithProviderDetails( + map[string]string{"aws": fmt.Sprintf("aws.%s", createProviderAlias(args, args.ParsedRegionsList[0]))}), + lwgenerate.HclModuleWithAttributes(moduleAttrs), + ) + + modDetails, err := lwgenerate.NewModule( + "aws_eks_audit_log", + lwgenerate.AwsEksAuditSource, + moduleDetails..., + ).ToBlock() + + if err != nil { + return nil, err + } + blocks = append(blocks, modDetails) + + return blocks, nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/azure/azure.go b/vendor/github.com/lacework/go-sdk/lwgenerate/azure/azure.go new file mode 100644 index 000000000..e1fb2f987 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/azure/azure.go @@ -0,0 +1,671 @@ +// A package that generates Lacework deployment code for Azure cloud. +package azure + +import ( + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/lacework/go-sdk/lwgenerate" + "github.com/pkg/errors" +) + +type GenerateAzureTfConfigurationArgs struct { + // Should we configure Activity Log integration in LW? + ActivityLog bool + + // Should we add Config integration in LW? + Config bool + + // Should we create an Entra ID integration in LW? + EntraIdActivityLog bool + + // Should we create an Active Directory integration + CreateAdIntegration bool + + // If Config is true, give the user the opportunity to name their integration. Defaults to "TF Config" + ConfigIntegrationName string + + // If ActivityLog is true, give the user the opportunity to name their integration. Defaults to "TF activity log" + ActivityLogIntegrationName string + + // If EntraIdIntegration is true, give the user the opportunity to name their integration. + // Defaults to "TF Entra ID activity log" + EntraIdIntegrationName string + + // Active Directory application Id + AdApplicationId string + + // Active Directory password + AdApplicationPassword string + + // Active Directory Enterprise app object id + AdServicePrincipalId string + + // Should we use the management group, rather than subscription + ManagementGroup bool + + // Management Group ID to set + ManagementGroupId string + + // List of subscription Ids + SubscriptionIds []string + + // Subscription ID configured in azurerm provider block + SubscriptionID string + + // Grant read access to ALL subscriptions + AllSubscriptions bool + + // Storage Account name + StorageAccountName string + + // Storage Account Resource Group + StorageAccountResourceGroup string + + // Should we use existing storage account + ExistingStorageAccount bool + + // Azure region where the storage account for logging resides + StorageLocation string + + LaceworkProfile string + + // Azure region where the event hub for logging will reside + EventHubLocation string + + // Number of partitions in the Event Hub for logging + EventHubPartitionCount int + + // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc + ExtraBlocksRootTerraform []*hclwrite.Block + + // ExtraAZRMArguments allows adding more arguments to the provider block as needed (custom use cases) + ExtraAZRMArguments map[string]interface{} + + // ExtraAZReadArguments allows adding more arguments to the provider block as needed (custom use cases) + ExtraAZReadArguments map[string]interface{} + + // ExtraBlocks allows adding more hclwrite.Block to the root terraform document (advanced use cases) + ExtraBlocks []*hclwrite.Block + + // Custom outputs + CustomOutputs []lwgenerate.HclOutput +} + +// Ensure all combinations of inputs are valid for supported spec +func (args *GenerateAzureTfConfigurationArgs) validate() error { + // Validate one of config or activity log was enabled; otherwise error out + if !args.ActivityLog && !args.Config && !args.EntraIdActivityLog { + return errors.New("audit log or config integration must be enabled") + } + + // Validate that active directory settings are correct + if !args.CreateAdIntegration && (args.AdApplicationId == "" || + args.AdServicePrincipalId == "" || args.AdApplicationPassword == "") { + return errors.New("Active directory details must be set") + } + + // Validate the Mangement Group + if args.ManagementGroup && args.ManagementGroupId == "" { + return errors.New("When Group Management is enabled, then Group Id must be configured") + } + + // Validate Storage Account + if args.ExistingStorageAccount && (args.StorageAccountName == "" || args.StorageAccountResourceGroup == "") { + return errors.New("When using existing storage account, storage account details must be configured") + } + + return nil +} + +type AzureTerraformModifier func(c *GenerateAzureTfConfigurationArgs) + +// NewTerraform returns an instance of the GenerateAzureTfConfigurationArgs struct with the provided enabled +// settings (config/activity log). +// +// Note: Additional configuration details may be set using modifiers of the AzureTerraformModifier type +func NewTerraform( + enableConfig bool, enableActivityLog bool, enableEntraIdActivityLog, createAdIntegration bool, + mods ...AzureTerraformModifier, +) *GenerateAzureTfConfigurationArgs { + config := &GenerateAzureTfConfigurationArgs{ + ActivityLog: enableActivityLog, + Config: enableConfig, + EntraIdActivityLog: enableEntraIdActivityLog, + CreateAdIntegration: createAdIntegration, + } + for _, m := range mods { + m(config) + } + return config +} + +// WithConfigIntegrationName Set the Config Integration name to be displayed on the Lacework UI +func WithConfigIntegrationName(name string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.ConfigIntegrationName = name + } +} + +// WithConfigOutputs Set Custom Terraform Outputs +func WithCustomOutputs(outputs []lwgenerate.HclOutput) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.CustomOutputs = outputs + } +} + +// WithExtraRootBlocks allows adding generic hcl blocks to the root `terraform{}` block +// this enables custom use cases +func WithExtraRootBlocks(blocks []*hclwrite.Block) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.ExtraBlocksRootTerraform = blocks + } +} + +// WithExtraAZRMArguments enables adding additional arguments into the `azurerm` provider block +// this enables custom use cases +func WithExtraAZRMArguments(arguments map[string]interface{}) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.ExtraAZRMArguments = arguments + } +} + +// WithExtraAZReadArguments enables adding additional arguments into the `azuread` provider block +// this enables custom use cases +func WithExtraAZReadArguments(arguments map[string]interface{}) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.ExtraAZReadArguments = arguments + } +} + +// WithExtraBlocks enables adding additional arbitrary blocks to the root hcl document +func WithExtraBlocks(blocks []*hclwrite.Block) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.ExtraBlocks = blocks + } +} + +// WithActivityLogIntegrationName Set the Activity Log Integration name to be displayed on the Lacework UI +func WithActivityLogIntegrationName(name string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.ActivityLogIntegrationName = name + } +} + +// WithEntraIdActivityLogIntegrationName Set the Entra ID Activity Log Integration name +// to be displayed on the Lacework UI +func WithEntraIdActivityLogIntegrationName(name string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.EntraIdIntegrationName = name + } +} + +// WithAdApplicationId Set Active Directory application id +func WithAdApplicationId(AdApplicationId string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.AdApplicationId = AdApplicationId + } +} + +// WithAdApplicationPassword Set the Active Directory password +func WithAdApplicationPassword(AdApplicationPassword string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.AdApplicationPassword = AdApplicationPassword + } +} + +// WithAdServicePrincipalId Set Active Directory principal id +func WithAdServicePrincipalId(AdServicePrincipalId string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.AdServicePrincipalId = AdServicePrincipalId + } +} + +// WithManagementGroup Enable the Management Group to allow AD to be reader on management group +// rather then subscription +func WithManagementGroup(enableManagentGroup bool) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.ManagementGroup = enableManagentGroup + } +} + +// WithManagementGroupId The Group Id to add reader permissions +func WithManagementGroupId(managementGroupId string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.ManagementGroupId = managementGroupId + } +} + +// WithSubscriptionIds List of subscriptions to to enable logging +func WithSubscriptionIds(subscriptionIds []string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.SubscriptionIds = subscriptionIds + } +} + +// WithAllSubscriptions Grant read access to ALL subscriptions within +// the selected Tenant (overrides 'subscription_ids') +func WithAllSubscriptions(allSubscriptions bool) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.AllSubscriptions = allSubscriptions + } +} + +// WithExistingStorageAccount Use an existing Storage Account +func WithExistingStorageAccount(existingStorageAccount bool) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.ExistingStorageAccount = existingStorageAccount + } +} + +// WithStorageAccountName The name of the Storage Account +func WithStorageAccountName(storageAccountName string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.StorageAccountName = storageAccountName + } +} + +// WithStorageAccountResourceGroup The Resource Group for the existing Storage Account +func WithStorageAccountResourceGroup(storageAccountResourceGroup string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.StorageAccountResourceGroup = storageAccountResourceGroup + } +} + +// WithStorageLocation The Azure region where storage account for logging is +func WithStorageLocation(location string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.StorageLocation = location + } +} + +// WithEventHubLocation The Azure region where the event hub for logging resides +func WithEventHubLocation(location string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.EventHubLocation = location + } +} + +// WitthEventHubPartitionCount The number of partitions in the Event Hub for logging +func WithEventHubPartitionCount(partitionCount int) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.EventHubPartitionCount = partitionCount + } +} + +func WithLaceworkProfile(name string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.LaceworkProfile = name + } +} + +func WithSubscriptionID(subcriptionID string) AzureTerraformModifier { + return func(c *GenerateAzureTfConfigurationArgs) { + c.SubscriptionID = subcriptionID + } +} + +// Generate new Terraform code based on the supplied args. +func (args *GenerateAzureTfConfigurationArgs) Generate() (string, error) { + // Validate inputs + if err := args.validate(); err != nil { + return "", errors.Wrap(err, "invalid inputs") + } + + // Create blocks + requiredProviders, err := createRequiredProviders(args.ExtraBlocksRootTerraform) + if err != nil { + return "", errors.Wrap(err, "failed to generate required providers") + } + + laceworkProvider, err := createLaceworkProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate lacework provider") + } + + azureADProvider, err := createAzureADProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate AD provider") + } + + azureRMProvider, err := createAzureRMProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate AM provider") + } + + laceworkADProvider, err := createLaceworkAzureADModule(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate lacework Azure AD provider") + } + + configModule, err := createConfig(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate azure config module") + } + + activityLogModule, err := createActivityLog(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate azure activity log module") + } + + entraIdActivityLogModule, err := createEntraIdActivityLog(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate azure Entra ID activity log module") + } + + outputBlocks := []*hclwrite.Block{} + for _, output := range args.CustomOutputs { + outputBlock, err := output.ToBlock() + if err != nil { + return "", errors.Wrap(err, "failed to add custom output") + } + outputBlocks = append(outputBlocks, outputBlock) + } + + // Render + hclBlocks := lwgenerate.CreateHclStringOutput( + lwgenerate.CombineHclBlocks( + requiredProviders, + laceworkProvider, + azureADProvider, + azureRMProvider, + laceworkADProvider, + configModule, + activityLogModule, + entraIdActivityLogModule, + outputBlocks, + args.ExtraBlocks), + ) + return hclBlocks, nil +} + +func createRequiredProviders(extraBlocks []*hclwrite.Block) (*hclwrite.Block, error) { + return lwgenerate.CreateRequiredProvidersWithCustomBlocks( + extraBlocks, + lwgenerate.NewRequiredProvider( + "lacework", + lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), + lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion), + ), + ) +} + +func createLaceworkProvider(args *GenerateAzureTfConfigurationArgs) (*hclwrite.Block, error) { + if args.LaceworkProfile != "" { + return lwgenerate.NewProvider( + "lacework", + lwgenerate.HclProviderWithAttributes(map[string]interface{}{"profile": args.LaceworkProfile}), + ).ToBlock() + } + return nil, nil +} + +func createAzureADProvider(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + attrs := map[string]interface{}{} + + // set custom args before the required ones below to ensure expected behavior (i.e., no overrides) + for k, v := range args.ExtraAZReadArguments { + attrs[k] = v + } + + provider, err := lwgenerate.NewProvider( + "azuread", + lwgenerate.HclProviderWithAttributes(attrs), + ).ToBlock() + + if err != nil { + return nil, err + } + + blocks = append(blocks, provider) + return blocks, nil +} + +// In this we need to create a provider block with a features +// configuration but with nothing set, this is as per the +// Azure examples and is of the format +// +// provider "azurerm" { +// features = {} +// } +func createAzureRMProvider(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + attrs := map[string]interface{}{} + featureAttrs := map[string]interface{}{} + + // set custom args before the required ones below to ensure expected behavior (i.e., no overrides) + for k, v := range args.ExtraAZRMArguments { + attrs[k] = v + } + + if args.SubscriptionID != "" { + attrs["subscription_id"] = args.SubscriptionID + } + + provider, err := lwgenerate.NewProvider( + "azurerm", + lwgenerate.HclProviderWithAttributes(attrs), + ).ToBlock() + + if err != nil { + return nil, err + } + // Create the features block + featuresBlock, err := lwgenerate.HclCreateGenericBlock("features", []string{}, featureAttrs) + provider.Body().AppendBlock(featuresBlock) + + if err != nil { + return nil, err + } + + blocks = append(blocks, provider) + return blocks, nil +} + +func createLaceworkAzureADModule(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + + if args.CreateAdIntegration { + provider, err := lwgenerate.NewModule( + "az_ad_application", + lwgenerate.LWAzureADSource, + lwgenerate.HclModuleWithVersion(lwgenerate.LWAzureADVersion), + ).ToBlock() + + if err != nil { + return nil, err + } + + blocks = append(blocks, provider) + } + return blocks, nil +} + +func createConfig(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + if args.Config { + attributes := map[string]interface{}{} + moduleDetails := []lwgenerate.HclModuleModifier{} + + if args.ConfigIntegrationName != "" { + attributes["lacework_integration_name"] = args.ConfigIntegrationName + } + + // Check if we have created an Active Directory app + if args.CreateAdIntegration { + attributes["use_existing_ad_application"] = true + attributes["application_id"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "application_id"}) + attributes["application_password"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "application_password"}) + attributes["service_principal_id"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "service_principal_id"}) + } else { + attributes["use_existing_ad_application"] = true + attributes["application_id"] = args.AdApplicationId + attributes["application_password"] = args.AdApplicationPassword + attributes["service_principal_id"] = args.AdServicePrincipalId + } + + // Only set subscription ids if all subscriptions flag is not set + if !args.AllSubscriptions { + if len(args.SubscriptionIds) > 0 { + attributes["subscription_ids"] = args.SubscriptionIds + } + } else { + // Set Subscription information + attributes["all_subscriptions"] = args.AllSubscriptions + } + + // Set Management Group details + if args.ManagementGroup { + attributes["use_management_group"] = args.ManagementGroup + attributes["management_group_id"] = args.ManagementGroupId + } + + moduleDetails = append(moduleDetails, + lwgenerate.HclModuleWithAttributes(attributes), + ) + + moduleBlock, err := lwgenerate.NewModule( + "az_config", + lwgenerate.LWAzureConfigSource, + append(moduleDetails, lwgenerate.HclModuleWithVersion(lwgenerate.LWAzureConfigVersion))..., + ).ToBlock() + + if err != nil { + return nil, err + } + blocks = append(blocks, moduleBlock) + } + + return blocks, nil +} + +func createActivityLog(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + if args.ActivityLog { + attributes := map[string]interface{}{} + moduleDetails := []lwgenerate.HclModuleModifier{} + + if args.ActivityLogIntegrationName != "" { + attributes["lacework_integration_name"] = args.ActivityLogIntegrationName + } + + // Check if we have created an Active Directory integration + if args.CreateAdIntegration { + attributes["use_existing_ad_application"] = true + attributes["application_id"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "application_id"}) + attributes["application_password"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "application_password"}) + attributes["service_principal_id"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "service_principal_id"}) + } else { + attributes["use_existing_ad_application"] = true + attributes["application_id"] = args.AdApplicationId + attributes["application_password"] = args.AdApplicationPassword + attributes["service_principal_id"] = args.AdServicePrincipalId + } + + // Only set subscription ids if all subscriptions flag is not set + if !args.AllSubscriptions { + if len(args.SubscriptionIds) > 0 { + attributes["subscription_ids"] = args.SubscriptionIds + } + } else { + // Set Subscription information + attributes["all_subscriptions"] = args.AllSubscriptions + } + + // Set storage account name, if set + if args.StorageAccountName != "" { + attributes["storage_account_name"] = args.StorageAccountName + } + + // Set storage info if existing storage flag is set + if args.ExistingStorageAccount { + attributes["use_existing_storage_account"] = args.ExistingStorageAccount + attributes["storage_account_resource_group"] = args.StorageAccountResourceGroup + } + + // if a new storage account is being created (i.e., ExistingStorageAccount is false), enable infrastructure + // encryption + if !args.ExistingStorageAccount { + attributes["infrastructure_encryption_enabled"] = true + } + + // Set the location if needed + if args.StorageLocation != "" { + attributes["location"] = args.StorageLocation + } + + moduleDetails = append(moduleDetails, + lwgenerate.HclModuleWithAttributes(attributes), + ) + + moduleBlock, err := lwgenerate.NewModule( + "az_activity_log", + lwgenerate.LWAzureActivityLogSource, + append(moduleDetails, lwgenerate.HclModuleWithVersion(lwgenerate.LWAzureActivityLogVersion))..., + ).ToBlock() + + if err != nil { + return nil, err + } + blocks = append(blocks, moduleBlock) + + } + return blocks, nil +} + +func createEntraIdActivityLog(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + if args.EntraIdActivityLog { + attributes := map[string]interface{}{} + moduleDetails := []lwgenerate.HclModuleModifier{} + + if args.EntraIdIntegrationName != "" { + attributes["lacework_integration_name"] = args.EntraIdIntegrationName + } + + // Check if we have created an Active Directory integration + if args.CreateAdIntegration { + attributes["use_existing_ad_application"] = false + attributes["application_id"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "application_id"}) + attributes["application_password"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "application_password"}) + attributes["service_principal_id"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "az_ad_application", "service_principal_id"}) + } else { + attributes["use_existing_ad_application"] = true + attributes["application_id"] = args.AdApplicationId + attributes["application_password"] = args.AdApplicationPassword + attributes["service_principal_id"] = args.AdServicePrincipalId + } + + if args.EventHubLocation != "" { + attributes["location"] = args.EventHubLocation + } + + if args.EventHubPartitionCount > 0 { + attributes["num_partitions"] = args.EventHubPartitionCount + } + + moduleDetails = append(moduleDetails, + lwgenerate.HclModuleWithAttributes(attributes), + ) + + moduleBlock, err := lwgenerate.NewModule( + "microsoft-entra-id-activity-log", + lwgenerate.LWAzureEntraIdActivityLogSource, + append(moduleDetails, lwgenerate.HclModuleWithVersion(lwgenerate.LWAzureEntraIdActivityLogVersion))..., + ).ToBlock() + + if err != nil { + return nil, err + } + blocks = append(blocks, moduleBlock) + } + return blocks, nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/constants.go b/vendor/github.com/lacework/go-sdk/lwgenerate/constants.go new file mode 100644 index 000000000..633dd5d5c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/constants.go @@ -0,0 +1,43 @@ +package lwgenerate + +// TODO update +const ( + LaceworkProviderSource = "lacework/lacework" + LaceworkProviderVersion = "~> 1.0" + + AwsAgentlessSource = "lacework/agentless-scanning/aws" + AwsAgentlessVersion = "~> 0.6" + AwsConfigSource = "lacework/config/aws" + AwsConfigVersion = "~> 0.5" + AwsConfigOrgSource = "lacework/org-configuration/aws" + AwsConfigOrgVersion = "~> 1.0" + AwsCloudTrailSource = "lacework/cloudtrail/aws" + AwsCloudTrailVersion = "~> 2.7" + AwsCloudTrailControlTowerSource = "lacework/cloudtrail-controltower/aws" + AwsCloudTrailControlTowerVersion = "~> 0.3" + AwsEksAuditSource = "lacework/eks-audit-log/aws" + AwsEksAuditVersion = "~> 1.0" + + LWAzureConfigSource = "lacework/config/azure" + LWAzureConfigVersion = "~> 2.0" + LWAzureActivityLogSource = "lacework/activity-log/azure" + LWAzureActivityLogVersion = "~> 2.0" + LWAzureEntraIdActivityLogSource = "lacework/microsoft-entra-id-activity-log/azure" + LWAzureEntraIdActivityLogVersion = "~> 0.2" + LWAzureADSource = "lacework/ad-application/azure" + LWAzureADVersion = "~> 1.0" + + GcpAgentlessSource = "lacework/agentless-scanning/gcp" + GcpAgentlessVersion = "~> 2.0" + GcpConfigSource = "lacework/config/gcp" + GcpConfigVersion = "~> 3.0" + GcpAuditLogSource = "lacework/audit-log/gcp" + GcpAuditLogVersion = "~> 3.4" + GcpGKEAuditLogSource = "lacework/gke-audit-log/gcp" + GcpGKEAuditLogVersion = "~> 0.3" + GcpPubSubAuditLog = "lacework/pub-sub-audit-log/gcp" + GcpPubSubAuditLogVersion = "~> 0.2" + + OciConfigSource = "lacework/config/oci" + OciConfigVersion = "~> 0.2" +) diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gcp.go b/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gcp.go new file mode 100644 index 000000000..791b83ec0 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gcp.go @@ -0,0 +1,932 @@ +// A package that generates Lacework deployment code for Google cloud. +package gcp + +import ( + "fmt" + "sort" + + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/lacework/go-sdk/internal/unique" + "github.com/lacework/go-sdk/lwgenerate" + "github.com/pkg/errors" +) + +type ExistingServiceAccountDetails struct { + // Existing Service Account Name + Name string + + // Existing Service Account private key in JSON format, base64 encoded + PrivateKey string +} + +// NewExistingServiceAccountDetails Create new existing Service Account details +func NewExistingServiceAccountDetails(name string, privateKey string) *ExistingServiceAccountDetails { + return &ExistingServiceAccountDetails{ + Name: name, + PrivateKey: privateKey, + } +} + +func (e *ExistingServiceAccountDetails) IsPartial() bool { + // If nil, return false + if e == nil { + return false + } + + // If all values are empty, return false + if e.Name == "" && e.PrivateKey == "" { + return false + } + + // If all values are populated, return false + if e.Name != "" && e.PrivateKey != "" { + return false + } + + return true +} + +type GenerateGcpTfConfigurationArgs struct { + // Should we configure Agentless integration in LW? + Agentless bool + + // Should we configure AuditLog integration in LW? + AuditLog bool + + // Should we use the Pub Sub Audit Log or use the Bucket based one + UsePubSubAudit bool + + // Should we configure CSPM integration in LW? + Configuration bool + + // A list of GCP project IDs to monitor for Agentless integration + ProjectFilterList []string + + // A list of regions to deploy for Agentless integration + Regions []string + + // Path to service account credentials to be used by Terraform + ServiceAccountCredentials string + + // Should we configure an Organization wide integration? + OrganizationIntegration bool + + // Supply a GCP Organization ID, only asked if OrganizationIntegration is True + GcpOrganizationId string + + // Supply a GCP Project ID, to host the new resources + GcpProjectId string + + // Optionally supply existing Service Account Details + ExistingServiceAccount *ExistingServiceAccountDetails + + // If Configuration is true, give the user the opportunity to name their integration. Defaults to "TF Config" + ConfigurationIntegrationName string + + // Set of labels which will be added to the resources managed by the module + AuditLogLabels map[string]string + + // Set of labels which will be added to the audit log bucket + BucketLabels map[string]string + + // Set of labels which will be added to the subscription + PubSubSubscriptionLabels map[string]string + + // Set of labels which will be added to the topic + PubSubTopicLabels map[string]string + + CustomBucketName string + + // Supply a GCP region for the new bucket. EU/US/ASIA + BucketRegion string + + // Existing Bucket Name + ExistingLogBucketName string + + // Existing Sink Name + ExistingLogSinkName string + + // Should we force destroy the bucket if it has stuff in it? (only relevant on new Audit Log creation) + // DEPRECATED + EnableForceDestroyBucket bool + + // Boolean for enabling Uniform Bucket Level Access on the audit log bucket. Defaults to False + EnableUBLA bool + + // Number of days to keep audit logs in Lacework GCS bucket before deleting. + // If left empty the TF will default to -1 + LogBucketLifecycleRuleAge int + + // If AuditLog is true, give the user the opportunity to name their integration. Defaults to "TF audit_log" + AuditLogIntegrationName string + + // Lacework Profile to use + LaceworkProfile string + + FoldersToInclude []string + + FoldersToExclude []string + + IncludeRootProjects bool + + CustomFilter string + + GoogleWorkspaceFilter bool + + K8sFilter bool + + Prefix string + + WaitTime string + + Projects []string + + // Default GCP Provider labels + ProviderDefaultLabels map[string]interface{} + + // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc + ExtraBlocksRootTerraform []*hclwrite.Block + + // ExtraProviderArguments allows adding more arguments to the provider block as needed (custom use cases) + ExtraProviderArguments map[string]interface{} + + // ExtraBlocks allows adding more hclwrite.Block to the root terraform document (advanced use cases) + ExtraBlocks []*hclwrite.Block + + // Custom outputs + CustomOutputs []lwgenerate.HclOutput +} + +// Ensure all combinations of inputs are valid for supported spec +func (args *GenerateGcpTfConfigurationArgs) validate() error { + // Validate one of agentless, config or audit log was enabled; otherwise error out + if !args.Agentless && !args.AuditLog && !args.Configuration { + return errors.New("agentless, audit log or configuration integration must be enabled") + } + + if args.Agentless && len(args.Regions) == 0 { + return errors.New("regions must be provided for Agentless Integration") + } + + // Validate if this is an organization integration, verify that the organization id has been provided + if args.OrganizationIntegration && args.GcpOrganizationId == "" { + return errors.New("an Organization ID must be provided for an Organization Integration") + } + + // Validate existing Service Account values, if set + if args.ExistingServiceAccount != nil { + if args.ExistingServiceAccount.Name == "" || + args.ExistingServiceAccount.PrivateKey == "" { + return errors.New("when using an existing Service Account, existing name, and base64 " + + "encoded JSON Private Key fields all must be set") + } + } + + return nil +} + +type GcpTerraformModifier func(c *GenerateGcpTfConfigurationArgs) + +// NewTerraform returns an instance of the GenerateGcpTfConfigurationArgs struct with the provided enabled +// settings (configuration/audit log). +// +// Note: Additional configuration details may be set using modifiers of the GcpTerraformModifier type +// +// Basic usage: Initialize a new GcpTerraformModifier struct, with GCP service account credentials. Then use generate to +// +// create a string output of the required HCL. +// +// hcl, err := gcp.NewTerraform(true, true, true, true, +// gcp.WithGcpServiceAccountCredentials("/path/to/sa/credentials.json")).Generate() +func NewTerraform( + enableAgentless, enableConfig bool, enableAuditLog bool, enablePubSubAudit bool, mods ...GcpTerraformModifier, +) *GenerateGcpTfConfigurationArgs { + config := &GenerateGcpTfConfigurationArgs{ + Agentless: enableAgentless, + AuditLog: enableAuditLog, + UsePubSubAudit: enablePubSubAudit, + Configuration: enableConfig, + IncludeRootProjects: true, + EnableUBLA: true, + GoogleWorkspaceFilter: true, + K8sFilter: true, + } + // default LogBucketLifecycleRuleAge to -1. This helps us determine if the var has been set by the end user + config.LogBucketLifecycleRuleAge = -1 + for _, m := range mods { + m(config) + } + return config +} + +// WithUsePubSubAudit Set wether we use pub sub with the audit log rather than bucket based +func WithUsePubSubAudit(usePubSub bool) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.UsePubSubAudit = usePubSub + } +} + +// WithGcpServiceAccountCredentials Set the path for the GCP Service Account to be utilized by the GCP provider +func WithGcpServiceAccountCredentials(path string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ServiceAccountCredentials = path + } +} + +// WithProviderDefaultLabels adds default_labels to the provider configuration for GCP (if labels are present) +func WithProviderDefaultLabels(labels map[string]interface{}) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ProviderDefaultLabels = labels + } +} + +// WithConfigOutputs Set Custom Terraform Outputs +func WithCustomOutputs(outputs []lwgenerate.HclOutput) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.CustomOutputs = outputs + } +} + +// WithExtraRootBlocks allows adding generic hcl blocks to the root `terraform{}` block +// this enables custom use cases +func WithExtraRootBlocks(blocks []*hclwrite.Block) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ExtraBlocksRootTerraform = blocks + } +} + +// WithExtraProviderArguments enables adding additional arguments into the `gcp` provider block +// this enables custom use cases +func WithExtraProviderArguments(arguments map[string]interface{}) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ExtraProviderArguments = arguments + } +} + +// WithExtraBlocks enables adding additional arbitrary blocks to the root hcl document +func WithExtraBlocks(blocks []*hclwrite.Block) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ExtraBlocks = blocks + } +} + +// WithLaceworkProfile Set the Lacework Profile to utilize when integrating +func WithLaceworkProfile(name string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.LaceworkProfile = name + } +} + +// WithOrganizationIntegration Set whether we configure as an Organization wide integration +func WithOrganizationIntegration(enabled bool) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.OrganizationIntegration = enabled + } +} + +// WithOrganizationId Set the Lacework organization ID to integrate with for an organization integration +func WithOrganizationId(id string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.GcpOrganizationId = id + } +} + +// WithProjectId Set the Lacework project ID that new resources should be created in +// (required for both project & org integration) +func WithProjectId(id string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.GcpProjectId = id + } +} + +// WithExistingServiceAccount Set an existing Service Account to be used by the Lacework Integration +func WithExistingServiceAccount(serviceAccountDetails *ExistingServiceAccountDetails) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ExistingServiceAccount = serviceAccountDetails + } +} + +// WithConfigurationIntegrationName Set the Config Integration name to be displayed on the Lacework UI +func WithConfigurationIntegrationName(name string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ConfigurationIntegrationName = name + } +} + +// WithAuditLogLabels set labels to be applied to ALL newly created Audit Log resources +func WithAuditLogLabels(labels map[string]string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.AuditLogLabels = labels + } +} + +// WithBucketLabels set labels to be applied to the newly created Audit Log Bucket +func WithBucketLabels(labels map[string]string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.BucketLabels = labels + } +} + +// WithPubSubSubscriptionLabels set labels to be applied to the newly created Audit Log PubSub +func WithPubSubSubscriptionLabels(labels map[string]string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.PubSubSubscriptionLabels = labels + } +} + +// WithPubSubTopicLabels set labels to be applied to the newly created Audit Log PubSub Topic +func WithPubSubTopicLabels(labels map[string]string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.PubSubTopicLabels = labels + } +} + +func WithCustomBucketName(name string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.CustomBucketName = name + } +} + +// WithBucketRegion Set the Region in which the Bucket should be created +func WithBucketRegion(region string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.BucketRegion = region + } +} + +// WithExistingLogBucketName Set the bucket Name of an existing Audit Log Bucket setup +func WithExistingLogBucketName(name string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ExistingLogBucketName = name + } +} + +// WithExistingLogSinkName Set the Topic ARN of an existing Audit Log setup +func WithExistingLogSinkName(name string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ExistingLogSinkName = name + } +} + +// WithEnableUBLA Enable force destroy of the bucket if it has stuff in it +func WithEnableUBLA(enable bool) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.EnableUBLA = enable + } +} + +// WithLogBucketLifecycleRuleAge Set the number of days to keep audit logs in Lacework GCS bucket before deleting +// Defaults to -1. Leave default to keep indefinitely. +func WithLogBucketLifecycleRuleAge(ruleAge int) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.LogBucketLifecycleRuleAge = ruleAge + } +} + +// WithAuditLogIntegrationName Set the Config Integration name to be displayed on the Lacework UI +func WithAuditLogIntegrationName(name string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.AuditLogIntegrationName = name + } +} + +func WithFoldersToInclude(folders []string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.FoldersToInclude = folders + } +} + +func WithFoldersToExclude(folders []string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.FoldersToExclude = folders + } +} + +func WithIncludeRootProjects(include bool) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.IncludeRootProjects = include + } +} + +func WithCustomFilter(filter string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.CustomFilter = filter + } +} + +func WithGoogleWorkspaceFilter(filter bool) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.GoogleWorkspaceFilter = filter + } +} + +func WithK8sFilter(filter bool) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.K8sFilter = filter + } +} + +func WithPrefix(prefix string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.Prefix = prefix + } +} + +func WithWaitTime(waitTime string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.WaitTime = waitTime + } +} + +func WithMultipleProject(projects []string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.Projects = projects + } +} + +func WithProjectFilterList(projectFilterList []string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.ProjectFilterList = projectFilterList + } +} + +func WithRegions(regions []string) GcpTerraformModifier { + return func(c *GenerateGcpTfConfigurationArgs) { + c.Regions = regions + } +} + +// Generate new Terraform code based on the supplied args. +func (args *GenerateGcpTfConfigurationArgs) Generate() (string, error) { + // Validate inputs + if err := args.validate(); err != nil { + return "", errors.Wrap(err, "invalid inputs") + } + + // Create blocks + requiredProviders, err := createRequiredProviders(false, args.ExtraBlocksRootTerraform) + if err != nil { + return "", errors.Wrap(err, "failed to generate required providers") + } + + gcpProvider, err := createGcpProvider(args.ExtraProviderArguments, + args.ServiceAccountCredentials, args.GcpProjectId, args.Regions, "", args.ProviderDefaultLabels) + if err != nil { + return "", errors.Wrap(err, "failed to generate gcp provider") + } + + laceworkProvider, err := createLaceworkProvider(args.LaceworkProfile) + if err != nil { + return "", errors.Wrap(err, "failed to generate lacework provider") + } + + agentlessModule, err := createAgentless(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate gcp agentless module") + } + + configurationModule, err := createConfiguration(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate gcp configuration module") + } + + auditLogModule, err := createAuditLog(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate gcp audit log module") + } + + outputBlocks := []*hclwrite.Block{} + for _, output := range args.CustomOutputs { + outputBlock, err := output.ToBlock() + if err != nil { + return "", errors.Wrap(err, "failed to add custom output") + } + outputBlocks = append(outputBlocks, outputBlock) + } + + // Render + hclBlocks := lwgenerate.CreateHclStringOutput( + lwgenerate.CombineHclBlocks( + requiredProviders, + gcpProvider, + laceworkProvider, + agentlessModule, + configurationModule, + auditLogModule, + outputBlocks, + args.ExtraBlocks, + ), + ) + return hclBlocks, nil +} + +func createRequiredProviders(useExistingRequiredProviders bool, + extraBlocks []*hclwrite.Block) (*hclwrite.Block, error) { + if useExistingRequiredProviders { + return nil, nil + } + return lwgenerate.CreateRequiredProvidersWithCustomBlocks( + extraBlocks, + lwgenerate.NewRequiredProvider( + "lacework", + lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), + lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion), + ), + ) +} + +func createLaceworkProvider(laceworkProfile string) (*hclwrite.Block, error) { + if laceworkProfile != "" { + return lwgenerate.NewProvider( + "lacework", + lwgenerate.HclProviderWithAttributes(map[string]interface{}{"profile": laceworkProfile}), + ).ToBlock() + } + return nil, nil +} + +func createGcpProvider( + extraProviderArguments map[string]interface{}, + serviceAccountCredentials string, + projectId string, + regionsArg []string, + alias string, + providerDefaultLabels map[string]interface{}, +) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + + regions := append([]string{}, regionsArg...) + if len(regions) == 0 { + regions = append(regions, "") + } + + for _, region := range regions { + attrs := map[string]interface{}{} + + // set custom args before the required ones below to ensure expected behavior (i.e., no overrides) + for k, v := range extraProviderArguments { + attrs[k] = v + } + if serviceAccountCredentials != "" { + attrs["credentials"] = serviceAccountCredentials + } + + if projectId != "" { + attrs["project"] = projectId + } + + if alias != "" { + attrs["alias"] = alias + } + + if region != "" { + attrs["alias"] = region + attrs["region"] = region + } + + if len(providerDefaultLabels) != 0 { + attrs["default_labels"] = providerDefaultLabels + } + + modifiers := []lwgenerate.HclProviderModifier{ + lwgenerate.HclProviderWithAttributes(attrs), + } + + provider, err := lwgenerate.NewProvider( + "google", + modifiers...).ToBlock() + if err != nil { + return nil, err + } + + blocks = append(blocks, provider) + } + + return blocks, nil +} + +func createAgentless(args *GenerateGcpTfConfigurationArgs) ([]*hclwrite.Block, error) { + if !args.Agentless { + return nil, nil + } + + blocks := []*hclwrite.Block{} + + for i, region := range args.Regions { + moduleName := "lacework_gcp_agentless_scanning_global" + moduleDetails := []lwgenerate.HclModuleModifier{ + lwgenerate.HclModuleWithVersion(lwgenerate.GcpAgentlessVersion), + } + + attributes := map[string]interface{}{"regional": true} + if i == 0 { + attributes["global"] = true + if len(args.ProjectFilterList) > 0 { + attributes["project_filter_list"] = args.ProjectFilterList + } + if args.OrganizationIntegration { + attributes["integration_type"] = "ORGANIZATION" + } + if len(args.GcpOrganizationId) > 0 { + attributes["organization_id"] = args.GcpOrganizationId + } + } + if i > 0 { + moduleName = "lacework_gcp_agentless_scanning_region_" + region + attributes["global_module_reference"] = lwgenerate.CreateSimpleTraversal( + []string{"module", "lacework_gcp_agentless_scanning_global"}, + ) + } + + moduleDetails = append( + moduleDetails, + lwgenerate.HclModuleWithProviderDetails( + map[string]string{"google": fmt.Sprintf("google.%s", region)}, + ), + ) + + moduleDetails = append( + moduleDetails, + lwgenerate.HclModuleWithAttributes(attributes), + ) + + module, err := lwgenerate.NewModule( + moduleName, + lwgenerate.GcpAgentlessSource, + moduleDetails..., + ).ToBlock() + if err != nil { + return nil, err + } + + blocks = append(blocks, module) + } + + return blocks, nil +} + +func createConfiguration(args *GenerateGcpTfConfigurationArgs) ([]*hclwrite.Block, error) { + blocks := []*hclwrite.Block{} + if args.Configuration { + attributes := map[string]interface{}{} + moduleDetails := []lwgenerate.HclModuleModifier{} + + // default to using the project level module + configurationModuleName := "gcp_project_level_config" + if args.OrganizationIntegration { + // if organization integration is true, override configModuleName to use the organization level module + configurationModuleName = "gcp_organization_level_config" + attributes["org_integration"] = args.OrganizationIntegration + attributes["organization_id"] = args.GcpOrganizationId + + if len(args.FoldersToInclude) > 0 { + set := unique.StringSlice(args.FoldersToInclude) + sort.Strings(set) + attributes["folders_to_include"] = set + } + + if len(args.FoldersToExclude) > 0 { + set := unique.StringSlice(args.FoldersToExclude) + sort.Strings(set) + attributes["folders_to_exclude"] = set + + // Default true in gcp-audit-log TF module + if !args.IncludeRootProjects { + attributes["include_root_projects"] = args.IncludeRootProjects + } + } + } + + if args.ExistingServiceAccount != nil { + attributes["use_existing_service_account"] = true + attributes["service_account_name"] = args.ExistingServiceAccount.Name + attributes["service_account_private_key"] = args.ExistingServiceAccount.PrivateKey + } + + if args.ConfigurationIntegrationName != "" { + attributes["lacework_integration_name"] = args.ConfigurationIntegrationName + } + + if args.Prefix != "" { + attributes["prefix"] = args.Prefix + } + + if args.WaitTime != "" { + attributes["wait_time"] = args.WaitTime + } + + if len(args.Projects) > 0 { + value := make(map[string]string, len(args.Projects)) + for _, p := range args.Projects { + value[p] = p + } + moduleDetails = append(moduleDetails, lwgenerate.HclModuleWithForEach("project_id", value)) + } + + moduleDetails = append(moduleDetails, + lwgenerate.HclModuleWithAttributes(attributes), + ) + + // Regions is required when Agentless integration is enabled + // Use the first region name as the alias for Google provider if multiple regions are provided + if args.Agentless && len(args.Regions) > 0 { + moduleDetails = append( + moduleDetails, + lwgenerate.HclModuleWithProviderDetails( + map[string]string{"google": fmt.Sprintf("google.%s", args.Regions[0])}, + ), + ) + } + + moduleBlock, err := lwgenerate.NewModule( + configurationModuleName, + lwgenerate.GcpConfigSource, + append(moduleDetails, lwgenerate.HclModuleWithVersion(lwgenerate.GcpConfigVersion))..., + ).ToBlock() + + if err != nil { + return nil, err + } + blocks = append(blocks, moduleBlock) + + } + + return blocks, nil +} + +func createAuditLog(args *GenerateGcpTfConfigurationArgs) (*hclwrite.Block, error) { + if args.AuditLog { + attributes := map[string]interface{}{} + + moduleDetails := []lwgenerate.HclModuleModifier{} + + if args.PubSubSubscriptionLabels != nil { + attributes["pubsub_subscription_labels"] = args.PubSubSubscriptionLabels + } + + if args.PubSubTopicLabels != nil { + attributes["pubsub_topic_labels"] = args.PubSubTopicLabels + } + + if args.ExistingLogBucketName != "" { + attributes["existing_bucket_name"] = args.ExistingLogBucketName + } else { + if args.LogBucketLifecycleRuleAge != -1 { + attributes["lifecycle_rule_age"] = args.LogBucketLifecycleRuleAge + } + + if args.AuditLogLabels != nil { + attributes["labels"] = args.AuditLogLabels + } + + if args.BucketLabels != nil { + attributes["bucket_labels"] = args.BucketLabels + } + + // Default true in gcp-audit-log TF module + if !args.EnableUBLA { + attributes["enable_ubla"] = args.EnableUBLA + } + + if args.CustomBucketName != "" { + attributes["custom_bucket_name"] = args.CustomBucketName + } + + if args.BucketRegion != "" { + attributes["bucket_region"] = args.BucketRegion + } + } + + if args.ExistingLogSinkName != "" { + attributes["existing_sink_name"] = args.ExistingLogSinkName + } + + // default to using the project level module + auditLogModuleName := "gcp_project_audit_log" + + configurationModuleName := "gcp_project_level_config" + if args.OrganizationIntegration { + // if organization integration is true, override configModuleName to use the organization level module + configurationModuleName = "gcp_organization_level_config" + auditLogModuleName = "gcp_organization_level_audit_log" + // Determine if this is the a pub-sub audit log + if args.UsePubSubAudit { + attributes["integration_type"] = "ORGANIZATION" + } else { + attributes["org_integration"] = args.OrganizationIntegration + } + attributes["organization_id"] = args.GcpOrganizationId + + if len(args.FoldersToInclude) > 0 { + set := unique.StringSlice(args.FoldersToInclude) + sort.Strings(set) + attributes["folders_to_include"] = set + } + + if len(args.FoldersToExclude) > 0 { + set := unique.StringSlice(args.FoldersToExclude) + sort.Strings(set) + attributes["folders_to_exclude"] = set + + // Default true in gcp-audit-log TF module + if !args.IncludeRootProjects { + attributes["include_root_projects"] = args.IncludeRootProjects + } + } + } + + if args.ExistingServiceAccount == nil && args.Configuration { + attributes["use_existing_service_account"] = true + + cfgModuleName := configurationModuleName + + if len(args.Projects) > 0 { + cfgModuleName = fmt.Sprintf("%s[each.key]", cfgModuleName) + } + + attributes["service_account_name"] = lwgenerate.CreateSimpleTraversal( + []string{"module", cfgModuleName, "service_account_name"}, + ) + attributes["service_account_private_key"] = lwgenerate.CreateSimpleTraversal( + []string{"module", cfgModuleName, "service_account_private_key"}, + ) + } + + if args.ExistingServiceAccount != nil { + attributes["use_existing_service_account"] = true + attributes["service_account_name"] = args.ExistingServiceAccount.Name + attributes["service_account_private_key"] = args.ExistingServiceAccount.PrivateKey + } + + if args.AuditLogIntegrationName != "" { + attributes["lacework_integration_name"] = args.AuditLogIntegrationName + } + + if args.CustomFilter != "" { + attributes["custom_filter"] = args.CustomFilter + } + + // Default true in gcp-audit-log TF module + if !args.GoogleWorkspaceFilter { + attributes["google_workspace_filter"] = args.GoogleWorkspaceFilter + } + + // Default true in gcp-audit-log TF module + if !args.K8sFilter { + attributes["k8s_filter"] = args.K8sFilter + } + + if args.Prefix != "" { + attributes["prefix"] = args.Prefix + } + + if args.WaitTime != "" { + attributes["wait_time"] = args.WaitTime + } + + if len(args.Projects) > 0 { + value := make(map[string]string) + for _, p := range args.Projects { + value[p] = p + } + moduleDetails = append(moduleDetails, lwgenerate.HclModuleWithForEach("project_id", value)) + } + + moduleDetails = append(moduleDetails, + lwgenerate.HclModuleWithAttributes(attributes), + ) + + // Regions is required when Agentless integration is enabled + // Use the first region name as the alias for Google provider if multiple regions are provided + if args.Agentless && len(args.Regions) > 0 { + moduleDetails = append( + moduleDetails, + lwgenerate.HclModuleWithProviderDetails( + map[string]string{"google": fmt.Sprintf("google.%s", args.Regions[0])}, + ), + ) + } + + return lwgenerate.NewModule( + auditLogModuleName, + getAuditLogModule(args.UsePubSubAudit), + append(moduleDetails, lwgenerate.HclModuleWithVersion(getAuditLogVersion(args.UsePubSubAudit)))..., + ).ToBlock() + } + + return nil, nil +} + +func getAuditLogModule(isPubSub bool) string { + if isPubSub { + return lwgenerate.GcpPubSubAuditLog + } + return lwgenerate.GcpAuditLogSource +} + +func getAuditLogVersion(isPubSub bool) string { + if isPubSub { + return lwgenerate.GcpPubSubAuditLogVersion + } + return lwgenerate.GcpAuditLogVersion +} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gke.go b/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gke.go new file mode 100644 index 000000000..0413b3ca9 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gke.go @@ -0,0 +1,256 @@ +package gcp + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/lacework/go-sdk/lwgenerate" + "github.com/pkg/errors" +) + +type GenerateGkeTfConfigurationArgs struct { + UseExistingRequiredProviders bool + GcpProviderAlias string + ExistingServiceAccount *ServiceAccount + ExistingSinkName string + IntegrationName string + Labels map[string]string + LaceworkProfile string + OrganizationId string + OrganizationIntegration bool + Prefix string + ProjectId string + PubSubSubscriptionLabels map[string]string + PubSubTopicLabels map[string]string + ServiceAccountCredentials string + WaitTime string + // Default GCP Provider labels + ProviderDefaultLabels map[string]interface{} + // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc + ExtraBlocksRootTerraform []*hclwrite.Block + // ExtraProviderArguments allows adding more arguments to the provider block as needed (custom use cases) + ExtraProviderArguments map[string]interface{} +} + +type Modifier func(c *GenerateGkeTfConfigurationArgs) + +func (args *GenerateGkeTfConfigurationArgs) Generate() (string, error) { + if err := args.validate(); err != nil { + return "", errors.Wrap(err, "invalid inputs") + } + + requiredProviders, err := createRequiredProviders(args.UseExistingRequiredProviders, args.ExtraBlocksRootTerraform) + if err != nil { + return "", errors.Wrap(err, "failed to generate required providers") + } + + gcpProvider, err := createGcpProvider( + args.ExtraProviderArguments, + args.ServiceAccountCredentials, + args.ProjectId, + []string{}, + args.GcpProviderAlias, + args.ProviderDefaultLabels, + ) + if err != nil { + return "", errors.Wrap(err, "failed to generate gcp provider") + } + + laceworkProvider, err := createLaceworkProvider(args.LaceworkProfile) + if err != nil { + return "", errors.Wrap(err, "failed to generate lacework provider") + } + + gkeAuditLogModule, err := createGKEAuditLog(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate GKE Audit Log module") + } + + hclBlocks := lwgenerate.CreateHclStringOutput( + lwgenerate.CombineHclBlocks( + requiredProviders, + gcpProvider, + laceworkProvider, + gkeAuditLogModule), + ) + return hclBlocks, nil +} + +func (args *GenerateGkeTfConfigurationArgs) validate() error { + if args.OrganizationIntegration && args.OrganizationId == "" { + return errors.New("an Organization ID must be provided for an Organization Integration") + } + + if !args.OrganizationIntegration && args.OrganizationId != "" { + return errors.New("to provide an Organization ID, Organization Integration must be true") + } + + if args.ExistingServiceAccount != nil { + if args.ExistingServiceAccount.Name == "" || + args.ExistingServiceAccount.PrivateKey == "" { + return errors.New( + "when using an existing Service Account, existing name, and base64 encoded " + + "JSON Private Key fields all must be set", + ) + } + } + + return nil +} + +func NewGkeTerraform(mods ...Modifier) *GenerateGkeTfConfigurationArgs { + config := &GenerateGkeTfConfigurationArgs{} + + for _, m := range mods { + m(config) + } + + return config +} + +func WithGkeExistingRequiredProviders() Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.UseExistingRequiredProviders = true + } +} + +func WithGkeGcpProviderAlias(alias string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.GcpProviderAlias = alias + } +} + +func WithGkeExistingServiceAccount(serviceAccount *ServiceAccount) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.ExistingServiceAccount = serviceAccount + } +} + +func WithGkeExistingSinkName(name string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.ExistingSinkName = name + } +} + +func WithGkeIntegrationName(name string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.IntegrationName = name + } +} + +func WithGkeLabels(labels map[string]string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.Labels = labels + } +} + +func WithGkeLaceworkProfile(name string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.LaceworkProfile = name + } +} + +func WithGkeOrganizationId(id string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.OrganizationId = id + } +} + +func WithGkeOrganizationIntegration(enabled bool) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.OrganizationIntegration = enabled + } +} + +func WithGkePrefix(prefix string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.Prefix = prefix + } +} + +func WithGkeProjectId(id string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.ProjectId = id + } +} + +func WithGkePubSubSubscriptionLabels(labels map[string]string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.PubSubSubscriptionLabels = labels + } +} + +func WithGkePubSubTopicLabels(labels map[string]string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.PubSubTopicLabels = labels + } +} + +func WithGkeServiceAccountCredentials(path string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.ServiceAccountCredentials = path + } +} + +func WithGkeWaitTime(waitTime string) Modifier { + return func(c *GenerateGkeTfConfigurationArgs) { + c.WaitTime = waitTime + } +} + +func createGKEAuditLog(args *GenerateGkeTfConfigurationArgs) (*hclwrite.Block, error) { + var level string + attributes := map[string]interface{}{} + + if args.OrganizationIntegration { + level = "organization" + attributes["integration_type"] = "ORGANIZATION" + attributes["organization_id"] = args.OrganizationId + + } else { + level = "project" + attributes["integration_type"] = "PROJECT" + } + + if args.ExistingSinkName != "" { + attributes["existing_sink_name"] = args.ExistingSinkName + } + + if args.ExistingServiceAccount != nil { + attributes["use_existing_service_account"] = true + attributes["service_account_name"] = args.ExistingServiceAccount.Name + attributes["service_account_private_key"] = args.ExistingServiceAccount.PrivateKey + } + + if args.IntegrationName != "" { + attributes["lacework_integration_name"] = args.IntegrationName + } + + if args.Prefix != "" { + attributes["prefix"] = args.Prefix + } + + if args.WaitTime != "" { + attributes["wait_time"] = args.WaitTime + } + + moduleDetails := []lwgenerate.HclModuleModifier{ + lwgenerate.HclModuleWithAttributes(attributes), + lwgenerate.HclModuleWithVersion(lwgenerate.GcpGKEAuditLogVersion), + } + + if args.GcpProviderAlias != "" { + moduleDetails = append( + moduleDetails, + lwgenerate.HclModuleWithProviderDetails( + map[string]string{"google": fmt.Sprintf("google.%s", args.GcpProviderAlias)}, + ), + ) + } + + return lwgenerate.NewModule( + fmt.Sprintf("gcp_%s_level_gke_audit_log", level), + lwgenerate.GcpGKEAuditLogSource, + moduleDetails..., + ).ToBlock() +} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/service_account.go b/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/service_account.go new file mode 100644 index 000000000..aba498180 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/service_account.go @@ -0,0 +1,110 @@ +package gcp + +import ( + "encoding/base64" + "encoding/json" + "io" + "os" + + "github.com/lacework/go-sdk/internal/file" + "github.com/pkg/errors" +) + +type ServiceAccount struct { + Name string + PrivateKey string +} + +func NewServiceAccount(name string, privateKey string) *ServiceAccount { + return &ServiceAccount{ + Name: name, + PrivateKey: privateKey, + } +} + +func (s *ServiceAccount) IsPartial() bool { + if s == nil { + return false + } + + if s.Name == "" && s.PrivateKey == "" { + return false + } + + if s.Name != "" && s.PrivateKey != "" { + return false + } + + return true +} + +func ValidateServiceAccountCredentialsFile(credFile string) error { + if file.FileExists(credFile) { + jsonFile, err := os.Open(credFile) // guardrails-disable-line + if err != nil { + return errors.Wrap(err, "issue opening credentials file") + } + defer jsonFile.Close() + + byteValue, err := io.ReadAll(jsonFile) + if err != nil { + return errors.Wrap(err, "unable to parse credentials file") + } + + var credFileContent map[string]interface{} + err = json.Unmarshal(byteValue, &credFileContent) + if err != nil { + return errors.Wrap(err, "unable to parse credentials file") + } + credFileContent, valid := ValidateSaCredFileContent(credFileContent) + if !valid { + return errors.New("invalid Service Account credentials file. " + + "The private_key and client_email fields MUST be present.") + } + } else { + return errors.New("provided credentials file does not exist") + } + return nil +} + +func ValidateSaCredFileContent(credFileContent map[string]interface{}) (map[string]interface{}, bool) { + if credFileContent["private_key"] != nil && credFileContent["client_email"] != nil { + privateKey, ok := credFileContent["private_key"].(string) + if !ok { + return credFileContent, false + } + err := ValidateStringIsBase64(privateKey) + if err != nil { + privateKey := base64.StdEncoding.EncodeToString([]byte(privateKey)) + credFileContent["private_key"] = privateKey + return credFileContent, true + } + } + return credFileContent, false +} + +func ValidateStringIsBase64(val interface{}) error { + switch value := val.(type) { + case string: + _, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return errors.New("provided private key is not base64 encoded") + } + default: + return errors.New("value must be a string") + } + + return nil +} + +func ValidateServiceAccountCredentials(val interface{}) error { + if value, ok := val.(string); ok { + if value == "" { + return nil + } else { + return ValidateServiceAccountCredentialsFile(value) + } + } + + return errors.New("value must be a string") +} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/hcl.go b/vendor/github.com/lacework/go-sdk/lwgenerate/hcl.go new file mode 100644 index 000000000..5c796ae70 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/hcl.go @@ -0,0 +1,662 @@ +// A package that generates Lacework deployment code for multiple cloud providers. +package lwgenerate + +import ( + "errors" + "fmt" + "sort" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/zclconf/go-cty/cty" +) + +type HclRequiredProvider struct { + name string + source string + version string +} + +func (p *HclRequiredProvider) Source() string { + return p.source +} + +func (p *HclRequiredProvider) Version() string { + return p.version +} + +func (p *HclRequiredProvider) Name() string { + return p.name +} + +type HclRequiredProviderModifier func(p *HclRequiredProvider) + +func HclRequiredProviderWithSource(source string) HclRequiredProviderModifier { + return func(p *HclRequiredProvider) { + p.source = source + } +} + +func HclRequiredProviderWithVersion(version string) HclRequiredProviderModifier { + return func(p *HclRequiredProvider) { + p.version = version + } +} + +func NewRequiredProvider(name string, mods ...HclRequiredProviderModifier) *HclRequiredProvider { + provider := &HclRequiredProvider{name: name} + for _, m := range mods { + m(provider) + } + return provider +} + +type HclProvider struct { + // Required, provider name + name string + + // Optional. Extra properties for this module. Can supply string, bool, int, or map[string]interface{} as values + attributes map[string]interface{} + + // optional. Generic blocks + blocks []*hclwrite.Block +} + +func (p *HclProvider) ToBlock() (*hclwrite.Block, error) { + block, err := HclCreateGenericBlock("provider", []string{p.name}, p.attributes) + if err != nil { + return nil, err + } + + if p.blocks != nil { + for _, b := range p.blocks { + block.Body().AppendNewline() + block.Body().AppendBlock(b) + } + } + + return block, nil +} + +type HclProviderModifier func(p *HclProvider) + +// NewProvider Create a new HCL Provider +func NewProvider(name string, mods ...HclProviderModifier) *HclProvider { + provider := &HclProvider{name: name} + for _, m := range mods { + m(provider) + } + return provider +} + +func HclProviderWithAttributes(attrs map[string]interface{}) HclProviderModifier { + return func(p *HclProvider) { + p.attributes = attrs + } +} + +// HclProviderWithGenericBlocks sets the generic blocks within the provider +func HclProviderWithGenericBlocks(blocks ...*hclwrite.Block) HclProviderModifier { + return func(p *HclProvider) { + p.blocks = blocks + } +} + +type ForEach struct { + key string + value map[string]string +} + +type HclOutput struct { + // required, name of the resultant output + name string + + // required, converted into a traversal + // e.g. []string{"a", "b", "c"} as input results in traversal having value a.b.c + value []string + + // optional + description string +} + +func (m *HclOutput) ToBlock() (*hclwrite.Block, error) { + if m.value == nil { + return nil, errors.New("value must be supplied") + } + + attributes := map[string]interface{}{ + "value": CreateSimpleTraversal(m.value), + } + + if m.description != "" { + attributes["description"] = m.description + } + + block, err := HclCreateGenericBlock( + "output", + []string{m.name}, + attributes, + ) + if err != nil { + return nil, err + } + + return block, nil +} + +// NewOutput Create a provider statement in the HCL output +func NewOutput(name string, value []string, description string) *HclOutput { + return &HclOutput{name: name, description: description, value: value} +} + +type HclModule struct { + // Required, module name + name string + + // Required, source for this module + source string + + // Required, version + version string + + // Optional. Extra properties for this module. Can supply string, bool, int, or map[string]interface{} as values + attributes map[string]interface{} + + // Optional. Provide a map of strings. Creates an instance of the module block for each item in the map, with the + // map keys assigned to the key field. + forEach *ForEach + + // Optional. Provider details to override defaults. These values must be supplied as strings, and raw values will be + // accepted. Unfortunately map[string]hcl.Traversal is not a format that is supported by hclwrite.SetAttributeValue + // today so we must work around it (https://github.com/hashicorp/hcl/issues/347). + providerDetails map[string]string +} + +type HclModuleModifier func(p *HclModule) + +// NewModule Create a provider statement in the HCL output +func NewModule(name string, source string, mods ...HclModuleModifier) *HclModule { + module := &HclModule{name: name, source: source} + for _, m := range mods { + m(module) + } + return module +} + +// HclModuleWithAttributes Used to set parameters within the module usage +func HclModuleWithAttributes(attrs map[string]interface{}) HclModuleModifier { + return func(p *HclModule) { + p.attributes = attrs + } +} + +// HclModuleWithVersion Used to set the version of a module source to use +func HclModuleWithVersion(version string) HclModuleModifier { + return func(p *HclModule) { + p.version = version + } +} + +// HclModuleWithProviderDetails Used to provide additional provider details to a given module. +// +// Note: The values supplied become traversals +// +// e.g. https://www.terraform.io/docs/language/modules/develop/providers.html#passing-providers-explicitly +func HclModuleWithProviderDetails(providerDetails map[string]string) HclModuleModifier { + return func(p *HclModule) { + p.providerDetails = providerDetails + } +} + +func HclModuleWithForEach(key string, value map[string]string) HclModuleModifier { + return func(p *HclModule) { + p.forEach = &ForEach{key, value} + } +} + +// ToBlock Create hclwrite.Block for module +func (m *HclModule) ToBlock() (*hclwrite.Block, error) { + if m.attributes == nil { + m.attributes = make(map[string]interface{}) + } + if m.source != "" { + m.attributes["source"] = m.source + + } + if m.version != "" { + m.attributes["version"] = m.version + } + block, err := HclCreateGenericBlock( + "module", + []string{m.name}, + m.attributes, + ) + if err != nil { + return nil, err + } + + if m.forEach != nil { + block.Body().AppendNewline() + + value, err := convertTypeToCty(m.forEach.value) + if err != nil { + return nil, err + } + block.Body().SetAttributeValue("for_each", value) + + block.Body().SetAttributeRaw(m.forEach.key, createForEachKey()) + } + + if m.providerDetails != nil { + block.Body().AppendNewline() + block.Body().SetAttributeRaw("providers", CreateMapTraversalTokens(m.providerDetails)) + } + + return block, nil +} + +// ToResourceBlock Create hclwrite.Block for resource +func (m *HclResource) ToResourceBlock() (*hclwrite.Block, error) { + if m.attributes == nil { + m.attributes = make(map[string]interface{}) + } + + block, err := HclCreateGenericBlock( + "resource", + []string{m.rType, m.name}, + m.attributes, + ) + if err != nil { + return nil, err + } + + if m.providerDetails != nil { + block.Body().AppendNewline() + block.Body().SetAttributeTraversal("provider", CreateSimpleTraversal(m.providerDetails)) + } + + if m.blocks != nil { + for _, b := range m.blocks { + block.Body().AppendNewline() + block.Body().AppendBlock(b) + } + } + + return block, nil +} + +type HclResource struct { + // Required, resourceType + rType string + + // Required, resource name + name string + + // Optional. Extra properties for this resource. Can supply string, bool, int, or map[string]interface{} as values + attributes map[string]interface{} + + // Optional. Provider details to override defaults. These values must be supplied as strings, and raw values will be + // accepted. Unfortunately map[string]hcl.Traversal is not a format that is supported by hclwrite.SetAttributeValue + // today so we must work around it (https://github.com/hashicorp/hcl/issues/347). + providerDetails []string + + // optional. Generic blocks + blocks []*hclwrite.Block +} + +type HclResourceModifier func(p *HclResource) + +// NewResource Create a provider statement in the HCL output +func NewResource(rType string, name string, mods ...HclResourceModifier) *HclResource { + resource := &HclResource{rType: rType, name: name} + for _, m := range mods { + m(resource) + } + return resource +} + +// HclResourceWithAttributesAndProviderDetails Used to set parameters within the resource usage +func HclResourceWithAttributesAndProviderDetails(attrs map[string]interface{}, + providerDetails []string) HclResourceModifier { + return func(p *HclResource) { + p.attributes = attrs + p.providerDetails = providerDetails + } +} + +// HclResourceWithGenericBlocks sets the generic blocks within the resource +func HclResourceWithGenericBlocks(blocks ...*hclwrite.Block) HclResourceModifier { + return func(p *HclResource) { + p.blocks = blocks + } +} + +// Convert standard value types to cty.Value +// +// All values used in hclwrite.Block(s) must be cty.Value or a cty.Traversal. This function performs that conversion +// for standard types (non-traversal) +func convertTypeToCty(value interface{}) (cty.Value, error) { + switch v := value.(type) { + case string: + return cty.StringVal(v), nil + case int: + return cty.NumberIntVal(int64(v)), nil + case bool: + return cty.BoolVal(v), nil + case map[string]string: + valueMap := map[string]cty.Value{} + for key, val := range v { + valueMap[key] = cty.StringVal(val) + } + return cty.MapVal(valueMap), nil + case map[string]interface{}: + valueMap := map[string]cty.Value{} + for key, val := range v { + convertedValue, err := convertTypeToCty(val) + if err != nil { + return cty.NilVal, err + } + valueMap[key] = convertedValue + } + return cty.MapVal(valueMap), nil + case []map[string]interface{}: + values := []cty.Value{} + for _, item := range v { + valueMap := map[string]cty.Value{} + for key, val := range item { + convertedValue, err := convertTypeToCty(val) + if err != nil { + return cty.NilVal, err + } + valueMap[key] = convertedValue + } + values = append(values, cty.ObjectVal(valueMap)) + } + return cty.ListVal(values), nil + case []string: + valueSlice := []cty.Value{} + for _, s := range v { + valueSlice = append(valueSlice, cty.StringVal(s)) + } + return cty.ListVal(valueSlice), nil + case []interface{}: + valueSlice := []cty.Value{} + for _, i := range v { + newVal, err := convertTypeToCty(i) + if err != nil { + return cty.Value{}, err + } + valueSlice = append(valueSlice, newVal) + } + return cty.TupleVal(valueSlice), nil + default: + return cty.NilVal, errors.New("unknown attribute value type") + } +} + +// Used to set block attribute values based on attribute value interface type +// +// hclwrite.Block attributes use cty.Value, hclwrite.Tokens or can be traversals, this function +// determines what type of value is being used and builds the block accordingly +func setBlockAttributeValue(block *hclwrite.Block, key string, val interface{}) error { + switch v := val.(type) { + case hcl.Traversal: + block.Body().SetAttributeTraversal(key, v) + case string, int, bool, []string, []interface{}: + value, err := convertTypeToCty(v) + if err != nil { + return err + } + block.Body().SetAttributeValue(key, value) + case []map[string]interface{}: + values := []cty.Value{} + for _, item := range v { + valueMap := map[string]cty.Value{} + for key, val := range item { + convertedValue, err := convertTypeToCty(val) + if err != nil { + return err + } + valueMap[key] = convertedValue + } + values = append(values, cty.ObjectVal(valueMap)) + } + + if !cty.CanListVal(values) { + return errors.New( + "setBlockAttributeValue: Values can not be coalesced into a single List due to inconsistent element types", + ) + } + block.Body().SetAttributeValue(key, cty.ListVal(values)) + case map[string]interface{}: + data := map[string]cty.Value{} + for attrKey, attrVal := range v { + value, err := convertTypeToCty(attrVal) + if err != nil { + return err + } + data[attrKey] = value + } + block.Body().SetAttributeValue(key, cty.ObjectVal(data)) + case map[string]string: + value, err := convertTypeToCty(v) + if err != nil { + return err + } + block.Body().SetAttributeValue(key, value) + case hclwrite.Tokens: + block.Body().SetAttributeRaw(key, v) + default: + return fmt.Errorf("setBlockAttributeValue: unknown type for key: %s", key) + } + + return nil +} + +// HclCreateGenericBlock Helper to create various types of new hclwrite.Block using generic inputs +func HclCreateGenericBlock(hcltype string, labels []string, attr map[string]interface{}) (*hclwrite.Block, error) { + block := hclwrite.NewBlock(hcltype, labels) + + // Source and version require some special handling, should go at the top of a block declaration + sourceFound := false + versionFound := false + + // We need/want to guarantee the ordering of the attributes, do that here + var keys []string + for k := range attr { + switch k { + case "source": + sourceFound = true + case "version": + versionFound = true + default: + keys = append(keys, k) + } + } + sort.Strings(keys) + + if sourceFound || versionFound { + var newKeys []string + if sourceFound { + newKeys = append(newKeys, "source") + } + if versionFound { + newKeys = append(newKeys, "version") + } + keys = append(newKeys, keys...) + } + + // Write block data + for _, key := range keys { + val := attr[key] + if err := setBlockAttributeValue(block, key, val); err != nil { + return nil, err + } + } + + return block, nil +} + +// Create tokens for map of traversals. Used as a workaround for writing complex types where the built-in +// SetAttributeValue won't work +func CreateMapTraversalTokens(input map[string]string) hclwrite.Tokens { + // Sort input + var keys []string + for k := range input { + keys = append(keys, k) + } + sort.Strings(keys) + + tokens := hclwrite.Tokens{ + {Type: hclsyntax.TokenOBrace, Bytes: []byte("{"), SpacesBefore: 1}, + {Type: hclsyntax.TokenNewline, Bytes: []byte("\n")}, + } + + for _, k := range keys { + tokens = append(tokens, []*hclwrite.Token{ + {Type: hclsyntax.TokenStringLit, Bytes: []byte(k)}, + {Type: hclsyntax.TokenEqual, Bytes: []byte("=")}, + {Type: hclsyntax.TokenStringLit, Bytes: []byte(" " + input[k]), SpacesBefore: 1}, + {Type: hclsyntax.TokenNewline, Bytes: []byte("\n")}, + }...) + } + + tokens = append(tokens, []*hclwrite.Token{ + {Type: hclsyntax.TokenNewline}, + {Type: hclsyntax.TokenCBrace, Bytes: []byte("}")}, + }...) + + return tokens +} + +// Create tokens for the for_each meta-argument +func createForEachKey() hclwrite.Tokens { + return hclwrite.Tokens{ + {Type: hclsyntax.TokenStringLit, Bytes: []byte(" each.key"), SpacesBefore: 1}, + } +} + +// CreateHclStringOutput Convert blocks to a string +func CreateHclStringOutput(blocks []*hclwrite.Block) string { + file := hclwrite.NewEmptyFile() + body := file.Body() + blockCount := len(blocks) - 1 + + for i, b := range blocks { + if b != nil { + body.AppendBlock(b) + + // If this is not the last block, add a new line to provide spacing + if i < blockCount { + body.AppendNewline() + } + } + } + return string(file.Bytes()) +} + +// rootTerraformBlock is a helper that creates the literal `terraform{}` hcl block +func rootTerraformBlock() (*hclwrite.Block, error) { + return HclCreateGenericBlock("terraform", nil, nil) +} + +// createRequiredProviders is a helper that creates the `required_providers` hcl block +func createRequiredProviders(providers ...*HclRequiredProvider) (*hclwrite.Block, error) { + providerDetails := map[string]interface{}{} + for _, provider := range providers { + details := map[string]interface{}{} + if provider.Source() != "" { + details["source"] = provider.Source() + } + if provider.Version() != "" { + details["version"] = provider.Version() + } + providerDetails[provider.Name()] = details + } + + requiredProviders, err := HclCreateGenericBlock("required_providers", nil, providerDetails) + if err != nil { + return nil, err + } + + return requiredProviders, nil +} + +// CreateRequiredProviders Create required providers block +func CreateRequiredProviders(providers ...*HclRequiredProvider) (*hclwrite.Block, error) { + block, err := rootTerraformBlock() + if err != nil { + return nil, err + } + + requiredProviders, err := createRequiredProviders(providers...) + if err != nil { + return nil, err + } + + block.Body().AppendBlock(requiredProviders) + return block, nil +} + +// CreateRequiredProviders Create required providers block +func CreateRequiredProvidersWithCustomBlocks( + blocks []*hclwrite.Block, + providers ...*HclRequiredProvider, +) (*hclwrite.Block, error) { + block, err := rootTerraformBlock() + if err != nil { + return nil, err + } + + requiredProviders, err := createRequiredProviders(providers...) + if err != nil { + return nil, err + } + + block.Body().AppendBlock(requiredProviders) + for _, customBlock := range blocks { + block.Body().AppendBlock(customBlock) + } + + return block, nil +} + +// CreateSimpleTraversal helper to create a hcl.Traversal in the order of supplied []string +// +// e.g. []string{"a", "b", "c"} as input results in traversal having value a.b.c +func CreateSimpleTraversal(input []string) hcl.Traversal { + var traverser []hcl.Traverser + + for i, val := range input { + if i == 0 { + traverser = append(traverser, hcl.TraverseRoot{Name: val}) + } else { + traverser = append(traverser, hcl.TraverseAttr{Name: val}) + } + } + return traverser +} + +// CombineHclBlocks Simple helper to combine multiple blocks (or slices of blocks) into a +// single slice to be rendered to string +func CombineHclBlocks(results ...interface{}) []*hclwrite.Block { + blocks := []*hclwrite.Block{} + // Combine all blocks into single flat slice + for _, result := range results { + switch v := result.(type) { + case *hclwrite.Block: + if v != nil { + blocks = append(blocks, v) + } + case []*hclwrite.Block: + if len(v) > 0 { + blocks = append(blocks, v...) + } + default: + continue + } + } + + return blocks +} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/oci/oci.go b/vendor/github.com/lacework/go-sdk/lwgenerate/oci/oci.go new file mode 100644 index 000000000..433e6e519 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwgenerate/oci/oci.go @@ -0,0 +1,177 @@ +package oci + +import ( + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/lacework/go-sdk/lwgenerate" + "github.com/pkg/errors" +) + +type GenerateOciTfConfigurationArgs struct { + // Should we configure CSPM integration in LW? + Config bool + + // Optional name for config + ConfigName string + + // Lacework profile to use + LaceworkProfile string + + // Tenant OCID + TenantOcid string + + // OCI user email + OciUserEmail string +} + +type OciTerraformModifier func(c *GenerateOciTfConfigurationArgs) + +const ( + LaceworkProviderVersion = ">= 1.9.0" +) + +// Set the Lacework profile to use for integration +func WithLaceworkProfile(name string) OciTerraformModifier { + return func(c *GenerateOciTfConfigurationArgs) { + c.LaceworkProfile = name + } +} + +// Set the name Lacework will use for the name +func WithConfigName(name string) OciTerraformModifier { + return func(c *GenerateOciTfConfigurationArgs) { + c.ConfigName = name + } +} + +// Set the OCID of the tenant to be integrated +func WithTenantOcid(ocid string) OciTerraformModifier { + return func(c *GenerateOciTfConfigurationArgs) { + c.TenantOcid = ocid + } +} + +// Set the email for the OCI user created for the integration +func WithUserEmail(email string) OciTerraformModifier { + return func(c *GenerateOciTfConfigurationArgs) { + c.OciUserEmail = email + } +} + +// NewTerraform returns an instance of the GenerateOciTfConfigurationArgs struct +// +// Note: Additional configuration details may be set using modifiers of the OciTerraformModifier type +// +// Basic usage: +// Initialize a new OciTerraformModifier struct then use generate to +// create a string output of the required HCL. +// +// hcl, err := aws.NewTerraform( +// true, +// oci.WithTenancyOcid("ocid1.tenancy...abc"), +// oci.WithUserEmail("a@b.c"), +// ).Generate() +func NewTerraform(enableConfig bool, mods ...OciTerraformModifier, +) *GenerateOciTfConfigurationArgs { + config := &GenerateOciTfConfigurationArgs{Config: enableConfig} + for _, m := range mods { + m(config) + } + return config +} + +// Generate new Terraform code based on the supplied args. +func (args *GenerateOciTfConfigurationArgs) Generate() (string, error) { + // Validate inputs + if err := args.validate(); err != nil { + return "", errors.Wrap(err, "invalid inputs") + } + + // Required providers block + requiredProviders, err := createRequiredProviders() + if err != nil { + return "", errors.Wrap(err, "failed to generate required providers") + } + + // provider lacework block + laceworkProvider, err := createLaceworkProvider(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate lacework provider") + } + + configModule, err := createConfig(args) + if err != nil { + return "", errors.Wrap(err, "failed to generate oci config module") + } + + // render HCL + hclBlocks := lwgenerate.CreateHclStringOutput( + lwgenerate.CombineHclBlocks( + requiredProviders, + laceworkProvider, + configModule, + ), + ) + return hclBlocks, nil +} + +// Ensure all combinations of inputs our valid for supported spec +func (args *GenerateOciTfConfigurationArgs) validate() error { + if !args.Config { + return errors.New("config integration must be enabled to continue") + } + + if args.TenantOcid == "" { + return errors.New("tenant OCID must be set") + } + + if args.OciUserEmail == "" { + return errors.New("OCI user email must be set") + } + + return nil +} + +func createRequiredProviders() (*hclwrite.Block, error) { + return lwgenerate.CreateRequiredProviders( + lwgenerate.NewRequiredProvider("lacework", + lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), + lwgenerate.HclRequiredProviderWithVersion(LaceworkProviderVersion), + ), + ) +} + +func createLaceworkProvider(args *GenerateOciTfConfigurationArgs) (*hclwrite.Block, error) { + if args.LaceworkProfile != "" { + return lwgenerate.NewProvider( + "lacework", + lwgenerate.HclProviderWithAttributes(map[string]interface{}{"profile": args.LaceworkProfile}), + ).ToBlock() + } + return nil, nil +} + +func createConfig(args *GenerateOciTfConfigurationArgs) (*hclwrite.Block, error) { + if !args.Config { + return nil, nil + } + + attributes := map[string]interface{}{} + + // Set the attributes + attributes["tenancy_id"] = args.TenantOcid + attributes["user_email"] = args.OciUserEmail + if args.ConfigName != "" { + attributes["integration_name"] = args.ConfigName + } + + // Create and return the module + modDetails := []lwgenerate.HclModuleModifier{ + lwgenerate.HclModuleWithVersion(lwgenerate.OciConfigVersion), + lwgenerate.HclModuleWithAttributes(attributes), + } + return lwgenerate.NewModule( + "oci_config", + lwgenerate.OciConfigSource, + modDetails..., + ).ToBlock() +} diff --git a/vendor/github.com/lacework/go-sdk/lwlogger/README.md b/vendor/github.com/lacework/go-sdk/lwlogger/README.md new file mode 100644 index 000000000..efb9e2b8c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwlogger/README.md @@ -0,0 +1,48 @@ +# Lacework Logger + +A wrapper Logger Go package for Lacework projects based of [zap](https://github.com/uber-go/zap). + +## Usage + +Download the library into your `$GOPATH`: + + $ go get github.com/lacework/go-sdk/lwlogger + +Import the library into your tool: + +```go +import "github.com/lacework/go-sdk/lwlogger" +``` + +## Environment Variables + +This package can be controlled via environment variables: + +| Environment Variable | Description | Default | Supported Options | +|----------------------|-------------|---------|-------------------| +|`LW_LOG`|Change the verbosity of the logs |`""`| `INFO` or `DEBUG` | +|`LW_LOG_FORMAT`|Controls the format of the logs|`JSON`| `JSON` or `CONSOLE` | +|`LW_LOG_DEV`|Switch the logger instance to development mode (extra verbose)|`false`| `true` or `false` | + +## Examples + +To create a new logger instance with the log level `INFO`, write an interesting +info message and another debug message. Note that only the info message will be +displayed: +```go +package main + +import "github.com/lacework/go-sdk/lwlogger" + +func main() { + lwL := lwlogger.New("INFO") + + lwL.Debug("this is a debug message, too long and only needed when debugging this app") + // This message wont be displayed + + lwL.Info("interesting info") + // Output: {"level":"info","ts":"[timestamp]","caller":"main.go:9","msg":"interesting info"} +} +``` + +Look at the [examples/](examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/lwlogger/logger.go b/vendor/github.com/lacework/go-sdk/lwlogger/logger.go new file mode 100644 index 000000000..58a143a84 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwlogger/logger.go @@ -0,0 +1,198 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A wrapper Logger package for Lacework projects based of zap logger. +package lwlogger + +import ( + "fmt" + "io" + "os" + "strings" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var ( + // LogLevelEnv represents the level that the logger is configured + LogLevelEnv = "LW_LOG" + SupportedLogLevels = [4]string{"", "ERROR", "INFO", "DEBUG"} + + // LogFormatEnv controls the format of the logger + LogFormatEnv = "LW_LOG_FORMAT" + DefaultLogFormat = "JSON" + SupportedLogFormats = [2]string{"JSON", "CONSOLE"} + + // LogDevelopmentModeEnv switches the logger to development mode + LogDevelopmentModeEnv = "LW_LOG_DEV" + + // LogToNativeLoggerEnv is used for those consumers like terraform that control + // the logs that are presented to the user, when this environment is turned + // on, the logger implementation will use the native Go logger 'log.Writer()' + LogToNativeLoggerEnv = "LW_LOG_NATIVE" +) + +// New initialize a new logger with the provided level and options +func New(level string, options ...zap.Option) *zap.Logger { + return NewWithFormat(level, "", options...) +} + +func NewWithFormat(level string, format string, options ...zap.Option) *zap.Logger { + if level == "" { + level = LogLevelFromEnvironment() + } + if format == "" { + format = logFormatFromEnv() + } + + zapConfig := zap.Config{ + Level: zapLogLevel(level), + Sampling: &zap.SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Development: inDevelopmentMode(), + Encoding: format, + EncoderConfig: laceworkEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } + + l, err := zapConfig.Build(options...) + if err != nil { + fmt.Printf("Error: unable to initialize logger: %v\n", err) + return zap.NewExample(options...) + } + + return l +} + +// NewWithWriter initialize a new logger with the provided level and options +// but redirecting the logs to the provider io.Writer +func NewWithWriter(level string, out io.Writer, options ...zap.Option) *zap.Logger { + if level == "" { + level = LogLevelFromEnvironment() + } + + var ( + writeSyncer = zapcore.AddSync(out) + core = zapcore.NewCore( + zapEncoderFromFormat(logFormatFromEnv()), + writeSyncer, + zapLogLevel(level), + ) + localOpts = []zap.Option{ + zap.ErrorOutput(writeSyncer), + zap.AddCaller(), + zap.WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewSamplerWithOptions(core, time.Second, 100, 100) + }), + } + ) + + return zap.New(core, options...).WithOptions(localOpts...) +} + +// Merges multiple loggers into one. A call to the merged logger will be +// forwarded to all the loggers +func Merge(loggers ...*zap.Logger) *zap.Logger { + cores := make([]zapcore.Core, len(loggers)) + for i, log := range loggers { + cores[i] = log.Core() + } + return zap.New(zapcore.NewTee(cores...)) +} + +func ValidLevel(level string) bool { + for _, l := range SupportedLogLevels { + if l == level { + return true + } + } + return false +} + +// LogLevelFromEnvironment checks the environment variable 'LW_LOG' +func LogLevelFromEnvironment() string { + switch os.Getenv(LogLevelEnv) { + case "info", "INFO": + return "INFO" + case "debug", "DEBUG": + return "DEBUG" + case "error", "ERROR": + return "ERROR" + default: + return "" + } +} + +func zapLogLevel(level string) zap.AtomicLevel { + switch level { + case "INFO": + return zap.NewAtomicLevelAt(zap.InfoLevel) + case "DEBUG": + return zap.NewAtomicLevelAt(zap.DebugLevel) + default: + return zap.NewAtomicLevelAt(zap.ErrorLevel) + } +} + +func inDevelopmentMode() bool { + return os.Getenv(LogDevelopmentModeEnv) == "true" +} + +func logFormatFromEnv() string { + switch os.Getenv(LogFormatEnv) { + case "console", "CONSOLE": + return "console" + case "json", "JSON": + return "json" + } + // @afiune the library require the format to be lowercase + return strings.ToLower(DefaultLogFormat) +} + +func zapEncoderFromFormat(format string) zapcore.Encoder { + switch format { + case "console": + return zapcore.NewConsoleEncoder(laceworkEncoderConfig()) + case "json": + return zapcore.NewJSONEncoder(laceworkEncoderConfig()) + default: + // @afiune we should never land here but just in case ;) + return zapcore.NewJSONEncoder(laceworkEncoderConfig()) + } +} + +func laceworkEncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + TimeKey: "ts", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.RFC3339TimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } +} diff --git a/vendor/github.com/lacework/go-sdk/lwrunner/awsrunner.go b/vendor/github.com/lacework/go-sdk/lwrunner/awsrunner.go new file mode 100644 index 000000000..ef514970c --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwrunner/awsrunner.go @@ -0,0 +1,422 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lwrunner + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect" + "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/aws/aws-sdk-go-v2/service/ssm" + ssmtypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" + "golang.org/x/crypto/ssh" +) + +type AWSRunner struct { + Runner Runner + Region string + AvailabilityZone string + InstanceID string + ImageName string +} + +func NewAWSRunner( + amiImageId, + userFromCLIArg, + host, + region, + availabilityZone, + instanceID string, + filterSSH bool, + callback ssh.HostKeyCallback, + cfg aws.Config) (*AWSRunner, error) { + // Look up the AMI name of the runner + imageName, err := getAMIName(amiImageId, region, cfg) + if err != nil { + return nil, err + } + + // Heuristically assign SSH username based on AMI name + var detectedUsername string + if filterSSH { + detectedUsername, err = getSSHUsername(userFromCLIArg, imageName) + if err != nil { + return nil, err + } + } else { + detectedUsername = "no_ssh_username_provided" + } + + defaultCallback, err := DefaultKnownHosts() + if err == nil && callback == nil { + callback = defaultCallback + } + + runner := New(detectedUsername, host, callback) + + return &AWSRunner{ + *runner, + region, + availabilityZone, + instanceID, + imageName, + }, nil +} + +func (run AWSRunner) SendAndUseIdentityFile(cfg aws.Config) error { + pubBytes, privBytes, err := GetKeyBytes() + if err != nil { + return err + } + + err = run.SendPublicKey(pubBytes, cfg) + if err != nil { + return err + } + + signer, err := ssh.ParsePrivateKey(privBytes) + if err != nil { + return err + } + run.Runner.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} + + return nil +} + +// Helper function to send a public key to a test instance. Uses +// EC2InstanceConnect. The AWS account used to run the tests must +// have EC2InstanceConnect permissions attached to its IAM role. +// First checks to make sure the instance is still running. +func (run AWSRunner) SendPublicKey(pubBytes []byte, cfg aws.Config) error { + // Send public key + cfg.Region = run.Region + svc := ec2instanceconnect.NewFromConfig(cfg) + + input := &ec2instanceconnect.SendSSHPublicKeyInput{ + AvailabilityZone: &run.AvailabilityZone, + InstanceId: &run.InstanceID, + InstanceOSUser: aws.String(run.Runner.User), + SSHPublicKey: aws.String(string(pubBytes)), + } + + _, err := svc.SendSSHPublicKey(context.Background(), input) + if err != nil { + return err + } + + return nil +} + +// AssociateInstanceProfileWithRunner associates a given instance profile with the +// receiving runner. First checks if there are any instance profiles already associated +// with the runner, and returns an error if so (since a runner can only have one instance +// profile associated with it). Then associates the instance profile with the runner. +// Returns the association ID or an error. +func (run AWSRunner) AssociateInstanceProfileWithRunner( + cfg aws.Config, instanceProfile types.InstanceProfile, +) (string, error) { + c := ec2.New(ec2.Options{ + Credentials: cfg.Credentials, + Region: run.Region, + }) + + // Check to see if there are any instance profiles already associated with the runner + describeOutput, err := c.DescribeIamInstanceProfileAssociations( + context.Background(), + &ec2.DescribeIamInstanceProfileAssociationsInput{ + Filters: []ec2types.Filter{ + { + Name: aws.String("instance-id"), + Values: []string{ + run.InstanceID, + }, + }, + }, + }, + ) + if err != nil { + return "", err + } + + associationID, err := run.isCorrectInstanceProfileAlreadyAssociated( + cfg, describeOutput.IamInstanceProfileAssociations, + ) + if err != nil { + return "", err + } + + if associationID != "" { // use the existing, correctly configured instance profile + return associationID, nil + } else { // associate our own instance profile + associateOutput, err := c.AssociateIamInstanceProfile( + context.Background(), + &ec2.AssociateIamInstanceProfileInput{ + IamInstanceProfile: &ec2types.IamInstanceProfileSpecification{ + Arn: instanceProfile.Arn, + }, + InstanceId: aws.String(run.InstanceID), + }, + ) + if err != nil { + return "", err + } + + return *associateOutput.IamInstanceProfileAssociation.AssociationId, nil + } +} + +// isCorrectInstanceProfileAlreadyAssociated takes a list of instance profile associations +// and checks if there is an instance profile associated and if this instance +// profile has the correct policy for SSM access. Returns `, nil` if so. Returns +// `"", nil` if there is no instance profile associated. Returns `"", ` if +// there is an incorrect instance profile associated, or if there was an error in +// executing this function. +func (run AWSRunner) isCorrectInstanceProfileAlreadyAssociated( + cfg aws.Config, associations []ec2types.IamInstanceProfileAssociation, +) (string, error) { + if len(associations) <= 0 { // no instance profile associated + return "", nil + } + instanceProfileName := strings.Split(*associations[0].IamInstanceProfile.Arn, "instance-profile/")[1] + + c := iam.New(iam.Options{ + Credentials: cfg.Credentials, + Region: cfg.Region, + }) + + getInstanceProfileOutput, err := c.GetInstanceProfile( + context.Background(), + &iam.GetInstanceProfileInput{ + InstanceProfileName: aws.String(instanceProfileName), + }, + ) + if err != nil { + return "", err + } + + // Check to see if the instance profile associated with the runner has the correct policy + + if len(getInstanceProfileOutput.InstanceProfile.Roles) <= 0 { // can only have max one role + return "", fmt.Errorf( + "runner %v already has an instance profile (%v) attached, does not have a role", + run, + getInstanceProfileOutput.InstanceProfile, + ) + } + + // Check which policies are associated with this instance profile's role + listAttachedRolePoliciesOutput, err := c.ListAttachedRolePolicies( + context.Background(), + &iam.ListAttachedRolePoliciesInput{ + RoleName: getInstanceProfileOutput.InstanceProfile.Roles[0].RoleName, + }, + ) + if err != nil { + return "", err + } + + for _, policy := range listAttachedRolePoliciesOutput.AttachedPolicies { + if *policy.PolicyArn == SSMInstancePolicy { + return *associations[0].AssociationId, nil // everything is configured correctly, we can return now + } + } + + // The runner has an instance profile attached, the instance profile has a role, + // and the role does not have the policy we need for SSM. We can't install on + // this instance, return an error + return "", fmt.Errorf( + "runner %v already has an instance profile (%v) attached, does not have policy %s", + run, + getInstanceProfileOutput.InstanceProfile, + SSMInstancePolicy, + ) +} + +func (run AWSRunner) DisassociateInstanceProfileFromRunner(cfg aws.Config, associationID string) error { + c := ec2.New(ec2.Options{ + Credentials: cfg.Credentials, + Region: run.Region, + }) + + _, err := c.DisassociateIamInstanceProfile( + context.Background(), + &ec2.DisassociateIamInstanceProfileInput{ + AssociationId: aws.String(associationID), + }, + ) + + return err +} + +const SSMInstancePolicy string = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + +// RunSSMCommandOnRemoteHost takes a shell command to install the agent on the runner +// the runner and executes it using SSM. `operation` must be one of the commands allowed +// by the SSM document. This function will not return until the command is in a terminal +// state, or until 2min have passed. +func (run AWSRunner) RunSSMCommandOnRemoteHost(cfg aws.Config, operation string) ( + ssm.GetCommandInvocationOutput, error, +) { + c := ssm.New(ssm.Options{ + Credentials: cfg.Credentials, + Region: run.Region, + }) + + sendCommandOutput, err := c.SendCommand( + context.Background(), + &ssm.SendCommandInput{ + DocumentName: aws.String("AWS-RunShellScript"), + Comment: aws.String("this command is for installing the Lacework Agent"), + InstanceIds: []string{ + run.InstanceID, + }, + Parameters: map[string][]string{ + "commands": { + operation, + }, + }, + }, + ) + if err != nil { + return ssm.GetCommandInvocationOutput{}, err + } + + var getCommandInvocationOutput *ssm.GetCommandInvocationOutput + + // Sleep while waiting for the command to execute + const durationTensOfSeconds = 12 + for i := 0; i < durationTensOfSeconds; i++ { + time.Sleep(10 * time.Second) + + getCommandInvocationOutput, err = c.GetCommandInvocation( + context.Background(), + &ssm.GetCommandInvocationInput{ + CommandId: sendCommandOutput.Command.CommandId, + InstanceId: aws.String(run.InstanceID), + }, + ) + if err != nil { + return ssm.GetCommandInvocationOutput{}, err + } + + // Check if the command has reached a "terminal state" + if getCommandInvocationOutput.Status == ssmtypes.CommandInvocationStatusSuccess || + getCommandInvocationOutput.Status == ssmtypes.CommandInvocationStatusCancelled || + getCommandInvocationOutput.Status == ssmtypes.CommandInvocationStatusTimedOut || + getCommandInvocationOutput.Status == ssmtypes.CommandInvocationStatusFailed { + return *getCommandInvocationOutput, nil + } + } + + return *getCommandInvocationOutput, fmt.Errorf( + "command %s did not finish in %dmin, final state %v, stdout %s, stderr %s", + *sendCommandOutput.Command.CommandId, + durationTensOfSeconds/6, + *getCommandInvocationOutput, + GetSSMCommandInvocationStdOut(*getCommandInvocationOutput), + GetSSMCommandInvocationStdErr(*getCommandInvocationOutput), + ) +} + +// getAMIName takes an AMI image ID, an AWS region name, and an AWS +// credential config as input and calls the AWS API to get the name +// of the AMI. Returns the AMI name or an error if unsuccessful. +func getAMIName(amiImageId, region string, cfg aws.Config) (string, error) { + cfg.Region = region + svc := ec2.NewFromConfig(cfg) + input := ec2.DescribeImagesInput{ + ImageIds: []string{ + amiImageId, + }, + } + result, err := svc.DescribeImages(context.Background(), &input) + if err != nil { + return "", err + } + if len(result.Images) != 1 { + return "", fmt.Errorf("expected to find only one AMI, instead found %v", result.Images) + } + + return *result.Images[0].Name, nil +} + +// getSSHUsername takes any username passed as a CLI arg, +// an AMI image name, a shell environment, and returns +// the username for SSHing into the AWS runner or the empty +// string and an error if the AMI is not supported. +// It first checks if `LW_SSH_USER` is set and returns it if so. +// Then it checks the AMI image name to heuristically determine the +// SSH username. +func getSSHUsername(userFromCLIArg, imageName string) (string, error) { + if userFromCLIArg != "" { // from CLI arg + return userFromCLIArg, nil + } + usernameLUT := getSSHUsernameLookupTable() + for _, matchFn := range usernameLUT { + if match, foundName := matchFn(imageName); match { + return foundName, nil + } + } + // No matching AMI found, return an error + return "", fmt.Errorf("no SSH username found for AMI %s, set as arg or shell env", imageName) +} + +// getSSHUsernameLookupTable returns a lookup table for heuristically +// determining SSH username based on AMI. +// The first row of the table it returns is a function that checks +// `LW_SSH_USER` in the shell environment. +func getSSHUsernameLookupTable() []func(string) (bool, string) { + return []func(string) (bool, string){ + // THIS ROW MUST BE FIRST IN THE TABLE + func(_ string) (bool, string) { return os.Getenv("LW_SSH_USER") != "", os.Getenv("LW_SSH_USER") }, + func(imageName string) (bool, string) { return strings.Contains(imageName, "ubuntu"), "ubuntu" }, + func(imageName string) (bool, string) { + return strings.Contains(imageName, "amazon_linux"), "ec2-user" + }, + func(imageName string) (bool, string) { return strings.Contains(imageName, "amzn2-ami"), "ec2-user" }, + } +} + +// GetSSMCommandInvocationStdOut is a helper function to safely deference the +// SSM struct we receive from the AWS API. +func GetSSMCommandInvocationStdOut(out ssm.GetCommandInvocationOutput) string { + if out.StandardOutputContent != nil { + return *out.StandardOutputContent + } else { + return "" + } +} + +// GetSSMCommandInvocationStdErr is a helper function to safely deference the +// SSM struct we receive from the AWS API. +func GetSSMCommandInvocationStdErr(out ssm.GetCommandInvocationOutput) string { + if out.StandardErrorContent != nil { + return *out.StandardErrorContent + } else { + return "" + } +} diff --git a/vendor/github.com/lacework/go-sdk/lwrunner/gcprunner.go b/vendor/github.com/lacework/go-sdk/lwrunner/gcprunner.go new file mode 100644 index 000000000..69b209e60 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwrunner/gcprunner.go @@ -0,0 +1,122 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lwrunner + +import ( + "context" + "fmt" + "time" + + oslogin "cloud.google.com/go/oslogin/apiv1" + osloginpb "cloud.google.com/go/oslogin/apiv1/osloginpb" + "golang.org/x/crypto/ssh" + osloginpb_common "google.golang.org/genproto/googleapis/cloud/oslogin/common" +) + +type GCPRunner struct { + Runner Runner + ParentUsername string + ProjectID string + AvailabilityZone string + InstanceID string +} + +func NewGCPRunner( + host, parentUsername, projectID, availabilityZone, instanceID string, callback ssh.HostKeyCallback, +) (*GCPRunner, error) { + defaultCallback, err := DefaultKnownHosts() + if err == nil && callback == nil { + callback = defaultCallback + } + + runner := New("", host, callback) // populate username during `SendAndUseIdentityFile()` + + return &GCPRunner{ + *runner, + parentUsername, + projectID, + availabilityZone, + instanceID, + }, nil +} + +func (run GCPRunner) SendAndUseIdentityFile() error { + pubBytes, privBytes, err := GetKeyBytes() + if err != nil { + return err + } + + err = run.SendPublicKey(pubBytes) + if err != nil { + return err + } + + signer, err := ssh.ParsePrivateKey(privBytes) + if err != nil { + return err + } + run.Runner.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} + + retries := 15 + for i := 0; i < retries; i += 1 { + if _, err = ssh.Dial("tcp", run.Runner.Address(), run.Runner.ClientConfig); err == nil { + return nil + } + time.Sleep(time.Second) + } + return fmt.Errorf("could not connect to host successfully after %d tries with err %v", retries, err) +} + +// SendPublicKey is a helper function to send a public key to a GCP account +// for OSLogin authentication. The account must have the "Compute OS Login" IAM role +// and "Service Account User" authorization for the GCE default service account. +// When the SSH key is sent, it will persist in the GCP account for 10min. +func (run GCPRunner) SendPublicKey(pubBytes []byte) error { + ctx := context.Background() + c, err := oslogin.NewClient(ctx) + if err != nil { + return err + } + defer c.Close() + + key := &osloginpb_common.SshPublicKey{ + Key: string(pubBytes), + ExpirationTimeUsec: time.Now().UnixMicro() + (10 * time.Minute.Microseconds()), // expiration time is 10min from now + } + + req := &osloginpb.ImportSshPublicKeyRequest{ + Parent: run.ParentUsername, + SshPublicKey: key, + ProjectId: run.ProjectID, + } + resp, err := c.ImportSshPublicKey(ctx, req) + if err != nil { + return err + } + + // Get login info from the OSLogin profile for our SSH login + posixAccounts := resp.LoginProfile.GetPosixAccounts() + for _, account := range posixAccounts { + if account.Primary { // there will only be one Primary account per profile + run.Runner.User = account.Username + } + } + + return nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwrunner/runner.go b/vendor/github.com/lacework/go-sdk/lwrunner/runner.go new file mode 100644 index 000000000..9e7fd82d6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwrunner/runner.go @@ -0,0 +1,227 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A runner package that executes commands on remote hosts. +package lwrunner + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "os" + "path" + + "github.com/lacework/go-sdk/internal/file" + homedir "github.com/mitchellh/go-homedir" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/knownhosts" +) + +type Runner struct { + Hostname string + Port int + *ssh.ClientConfig +} + +func New(user, host string, callback ssh.HostKeyCallback) *Runner { + if os.Getenv("LW_SSH_USER") != "" { + user = os.Getenv("LW_SSH_USER") + } + + defaultCallback, err := DefaultKnownHosts() + if err == nil && callback == nil { + callback = defaultCallback + } + + return &Runner{ + host, + 22, + &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{}, + HostKeyCallback: callback, + }, + } +} + +func (run Runner) UseIdentityFile(file string) error { + signer, err := newSignerFromFile(file) + if err != nil { + return err + } + run.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} + return nil +} + +func (run Runner) UsePassword(secret string) { + run.Auth = []ssh.AuthMethod{ssh.Password(secret)} +} + +func (run *Runner) Address() string { + return fmt.Sprintf("%s:%d", run.Hostname, run.Port) +} + +// Exec executes a command on the configured remote host +func (run *Runner) Exec(cmd string) (stdout bytes.Buffer, stderr bytes.Buffer, err error) { + conn, err := ssh.Dial("tcp", run.Address(), run.ClientConfig) + if err != nil { + return + } + + session, err := conn.NewSession() + if err != nil { + return + } + defer session.Close() + + session.Stdout = &stdout + session.Stderr = &stderr + err = session.Run(cmd) + return +} + +// DefaultKnownHosts returns a host key callback from default known hosts path +func DefaultKnownHosts() (ssh.HostKeyCallback, error) { + path, err := DefaultKnownHostsPath() + if err != nil { + return nil, err + } + + return knownhosts.New(path) +} + +// DefaultKnownHostsPath returns default user ~/.ssh/known_hosts file +func DefaultKnownHostsPath() (string, error) { + home, err := homedir.Dir() + if err != nil { + return "", err + } + + return path.Join(home, ".ssh", "known_hosts"), nil +} + +// AddKnownHost adds a host to the provided known hosts file, if no known hosts +// file is provided, it will fallback to default known_hosts file +func AddKnownHost(host string, remote net.Addr, key ssh.PublicKey, knownFile string) (err error) { + if knownFile == "" { + path, err := DefaultKnownHostsPath() + if err != nil { + return err + } + + knownFile = path + } + + if !file.FileExists(knownFile) { + if err := os.MkdirAll(path.Dir(knownFile), 0700); err != nil { + return err + } + } + + f, err := os.OpenFile(knownFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err != nil { + return err + } + + defer f.Close() + + var ( + remoteNormalized = knownhosts.Normalize(remote.String()) + hostNormalized = knownhosts.Normalize(host) + addresses = []string{remoteNormalized} + ) + + if hostNormalized != remoteNormalized { + addresses = append(addresses, hostNormalized) + } + + _, err = f.WriteString(knownhosts.Line(addresses, key) + "\n") + return err +} + +// CheckKnownHost checks if a host is in known hosts file, if no known hosts +// file is provided, it will fallback to default known_hosts file +func CheckKnownHost(host string, remote net.Addr, key ssh.PublicKey, knownFile string) (found bool, err error) { + var keyErr *knownhosts.KeyError + + // Fallback to default known_hosts file + if knownFile == "" { + path, err := DefaultKnownHostsPath() + if err != nil { + return false, err + } + + knownFile = path + } + + // get host key callback + callback, err := knownhosts.New(knownFile) + if err != nil { + return false, err + } + + // check if host already exists + err = callback(host, remote, key) + if err == nil { + // host is known (already exists) + return true, nil + } + + // if keyErr.Want is greater than 0 length, that means host is in file with different key + if errors.As(err, &keyErr) && len(keyErr.Want) > 0 { + return true, keyErr + } + + // if not, pass it back to the user + if err != nil { + return false, err + } + + // key is not trusted because it is not in the known hosts file + return false, nil +} + +func DefaultIdentityFilePath() (string, error) { + if os.Getenv("LW_SSH_IDENTITY_FILE") != "" { + return os.Getenv("LW_SSH_IDENTITY_FILE"), nil + } + + home, err := homedir.Dir() + if err != nil { + return "", err + } + + return path.Join(home, ".ssh", "id_rsa"), nil +} + +func newSignerFromFile(keyname string) (ssh.Signer, error) { + fp, err := os.Open(keyname) + if err != nil { + return nil, err + } + defer fp.Close() + + buf, err := io.ReadAll(fp) + if err != nil { + return nil, err + } + + return ssh.ParsePrivateKey(buf) +} diff --git a/vendor/github.com/lacework/go-sdk/lwrunner/ssh.go b/vendor/github.com/lacework/go-sdk/lwrunner/ssh.go new file mode 100644 index 000000000..e580aaf3d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwrunner/ssh.go @@ -0,0 +1,103 @@ +// +// Author:: Nicholas Schmeller () +// Copyright:: Copyright 2022, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lwrunner + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + + "golang.org/x/crypto/ssh" +) + +// Helper function to generate a random private key. This key will +// stay in memory and does not persist across test runs. +func GeneratePrivateKey(bitSize int) (*rsa.PrivateKey, error) { + // generate key + priv, err := rsa.GenerateKey(rand.Reader, bitSize) + if err != nil { + return nil, err + } + + // validate key + err = priv.Validate() + if err != nil { + return nil, err + } + + return priv, err +} + +// Generates SSH keys in EC2-readable format +// Returns public key bytes, private key bytes, error +func GetKeyBytes() ([]byte, []byte, error) { + // generate keys with bit size 4096 + priv, err := GeneratePrivateKey(4096) + if err != nil { + return nil, nil, err + } + + pubBytes, err := GetPublicKeyBytes(&priv.PublicKey) + if err != nil { + return nil, nil, err + } + + privBytes, err := EncodePrivateKeyToPEM(priv) + if err != nil { + return nil, nil, err + } + + return pubBytes, privBytes, err +} + +// Takes an rsa.PublicKey and returns bytes suitable for writing to .pub file +// Returns in the format "ssh-rsa ..." +func GetPublicKeyBytes(key *rsa.PublicKey) ([]byte, error) { + publicRsaKey, err := ssh.NewPublicKey(key) + if err != nil { + return nil, err + } + + pubKeyBytes := ssh.MarshalAuthorizedKey(publicRsaKey) + + return pubKeyBytes, err +} + +// Encodes private key from RSA struct to PEM formatted bytes +func EncodePrivateKeyToPEM(privateKey *rsa.PrivateKey) ([]byte, error) { + // Get ASN.1 DER format + privDER := x509.MarshalPKCS1PrivateKey(privateKey) + + // pem.Block + privBlock := pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: nil, + Bytes: privDER, + } + + // Private key in PEM format + privatePEM := pem.EncodeToMemory(&privBlock) + if privatePEM == nil { + return nil, errors.New("failed to encode private key to PEM") + } + + return privatePEM, nil +} diff --git a/vendor/github.com/lacework/go-sdk/lwseverity/severity.go b/vendor/github.com/lacework/go-sdk/lwseverity/severity.go new file mode 100644 index 000000000..6e6b77299 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwseverity/severity.go @@ -0,0 +1,172 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A helper package for Lacework severities +package lwseverity + +import ( + "fmt" + "sort" + "strings" +) + +type severity int + +func (s severity) GetSeverity() string { + return s.String() +} + +const ( + // Unknown severity + Unknown severity = iota + // Critical severity + Critical + // High severity + High + // Medium severity + Medium + // Low severity + Low + // Informational severity + Info +) + +var severities = map[severity]string{ + Unknown: "Unknown", + Critical: "Critical", + High: "High", + Medium: "Medium", + Low: "Low", + Info: "Info", +} + +// Get severity as a string type +func (s severity) String() string { + return severities[s] +} + +type validSeverities []severity + +// A list of valid Lacework severities (critical, high, medium, low, info) +var ValidSeverities = validSeverities{Critical, High, Medium, Low, Info} + +// Return a string representation of valid severities +// "critical, high, medium, low, info" +func (v validSeverities) String() string { + s := "" + + for _, severity := range v { + s += fmt.Sprintf("%s, ", strings.ToLower(severities[severity])) + } + + return strings.TrimRight(s, ", ") +} + +// Initialize a severity from string +func NewSeverity(s string) severity { + switch strings.ToLower(s) { + case "1", "critical": + return Critical + case "2", "high": + return High + case "3", "medium": + return Medium + case "4", "low": + return Low + case "5", "info": + return Info + default: + return Unknown + } +} + +type Severity interface { + GetSeverity() string +} + +// Normalize takes a string representation of Lacework severity and returns it's normalized +// integer and string values. +// +// Relation: +// - Critical Severity => 1, "Critical" +// - High Severity => 2, "High" +// - Medium Severity => 3, "Medium" +// - Low Severity => 4, "Low" +// - Informational Severity => 5, "Info" +// - Unknown Severity => 0, "Unknown" +func Normalize(s string) (int, string) { + severity := NewSeverity(s) + return int(severity), severity.String() +} + +// Take a string representation of Lacework severity and +// return whether it properly maps to a valid severity (not unknown) +func IsValid(s string) bool { + return NewSeverity(s) != Unknown +} + +// Returns true if the first severity not as critical as the second severity +// +// For instance: +// +// - "info" is not as crtical as "low" (true) +// - "medium" is as critical as "medium" (false) +// - "high" is more critical than "medium" (false) +// - "unknown" is more critical than "medium" (false) +// - "medium" is not as critical as "unknown" (true) +func NotAsCritical(first, second string) bool { + sevFirst, _ := Normalize(first) + sevSecond, _ := Normalize(second) + return sevFirst > sevSecond +} + +// Returns true if the threshold is proper and the severity is +// greater than or equal to the threshold +// +// For instance: +// +// - "medium" severity should be filtered for "high" threshold +// - "critical" severity should NOT be filtered for "high" threshold +// - "info" severity should NOT be filtered for "info" threshold +// - invalid (unknown) severity should NOT be filtered for * threshold +// - all severities should NOT be filtered for an invalid (unknown) threshold +func ShouldFilter(severity, threshold string) bool { + sevThreshold, _ := Normalize(threshold) + if sevThreshold == 0 { + return false + } + return NotAsCritical(severity, threshold) +} + +// Sort a slice of Severity interfaces from critical -> info +func SortSlice[S Severity](s []S) { + sort.SliceStable(s, func(i, j int) bool { + sevI, _ := Normalize(s[i].GetSeverity()) + sevJ, _ := Normalize(s[j].GetSeverity()) + return sevI < sevJ + }) +} + +// Sort a slice of Severity interfaces from info -> critical +func SortSliceA[S Severity](s []S) { + sort.SliceStable(s, func(i, j int) bool { + sevI, _ := Normalize(s[i].GetSeverity()) + sevJ, _ := Normalize(s[j].GetSeverity()) + return sevI > sevJ + }) +} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/README.md b/vendor/github.com/lacework/go-sdk/lwtime/README.md new file mode 100644 index 000000000..64a380119 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwtime/README.md @@ -0,0 +1,112 @@ +# Lacework Time Library + +A simple relative and natural time package. + +## Usage + +Download the library into your `$GOPATH`: + + $ go get github.com/lacework/go-sdk/lwtime + +Import the library: + +```go +import "github.com/lacework/go-sdk/lwtime" +``` + +## Relative Time Specifiers + +Relative times allow you to represent time values dynamically, using specifiers that represent an offset from the current time. For instance, a relative time of `-24h` produces a date/time that is 24 hours less the current time. Relative times can also snap to a particular time. For instance, a relative time of `@d` would represent the start of the current day. + +For example, to generate a time range (using a start and end time) that represents the previous day: +```go +package main + +import ( + "fmt" + "os" + + "github.com/lacework/go-sdk/lwtime" +) + +func main() { + start, err := lwtime.ParseRelative("-1d@d") + if err != nil { + fmt.Println("Unable to parse start time range: %s", err) + os.Exit(1) + } + end, err := lwtime.ParseRelative("@d") + if err != nil { + fmt.Println("Unable to parse end time range: %s", err) + os.Exit(1) + } + // Output: The time range is 2023-07-11 07:00:00 +0000 UTC to 2023-07-12 07:00:00 +0000 UTC + fmt.Printf("The time range is %s to %s\n", start.String(), end.String()) +} +``` + +A relative time has three components: +* A signed (+/-) integer +* A relative time unit +* A relative time snap + +Lacework supports the following relative time units: +* y - year +* mon - month +* w - week +* d - day +* h - hour +* m - minute +* s - second + +Additional considerations include: +* To represent the current time, you can specify either `now` or `+0s`. +* When specifying an integer and relative time unit, snaps are optional. +* When specifying a snap, the integer and relative time unit are optional. For instance, `@d` is actually interpreted as `+0s@d`. + + +## Natural Time Ranges + +Natural time ranges allow you to represent time range values using natural language. For instance, a natural time range of `yesterday` represents a relative start time of `-1d@d` and a relative end time of `@d`. + +For example, to generate a time range of this month: +```go +package main + +import ( + "fmt" + "os" + + "github.com/lacework/go-sdk/lwtime" +) + +func main() { + start, end, err := lwtime.ParseNatural("this month") + if err != nil { + fmt.Println("Unable to parse natural time: %s", err) + os.Exit(1) + } + // The time range is 2023-07-01 07:00:00 +0000 UTC to 2023-07-13 01:23:59.921851 +0000 UTC + fmt.Printf("The time range is %s to %s\n", start.String(), end.String()) +} +``` + +A natural time has three components: +* An adjective +* A positive number (only when using the last adjective) +* The full text representation of a relative time unit (i.e., year/years) + +Lacework supports the following adjectives (disambiguating previous and last by design): +* this/current +* previous +* last + +Additional considerations include: +* `last` implies "in the last". So last week reads as "in the last week" and represents a start time of `-1w` and an end time of `now`. +* `previous` always snaps. So "previous week" represents a start time of `-1w@w` and an end time of `@w`. +* `yesterday` is a valid natural time and is equivalent to previous day. +* `today` is a valid natural time and is equivalent to this day or current day. + +## Examples + +Look at the [_examples/](_examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/lwtime/epoch.go b/vendor/github.com/lacework/go-sdk/lwtime/epoch.go new file mode 100644 index 000000000..c9516f7d1 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwtime/epoch.go @@ -0,0 +1,39 @@ +package lwtime + +import ( + "fmt" + "strconv" + "time" +) + +// Epoch time type to parse the returned 13 digit time in milliseconds +type Epoch time.Time + +func (epoch *Epoch) UnmarshalJSON(b []byte) error { + ms, _ := strconv.Atoi(string(b)) + t := time.Unix(0, int64(ms)*int64(time.Millisecond)) + *epoch = Epoch(t) + return nil +} + +func (epoch Epoch) MarshalJSON() ([]byte, error) { + epochJson := fmt.Sprintf("%v", epoch.ToTime().UnixNano()/int64(time.Millisecond)) + return []byte(epochJson), nil +} + +func (epoch Epoch) ToTime() time.Time { + return time.Time(epoch) +} +func (epoch Epoch) Format(s string) string { + return epoch.ToTime().Format(s) +} +func (epoch Epoch) UTC() time.Time { + return epoch.ToTime().UTC() +} + +func (epoch *Epoch) String() string { + if epoch != nil { + return epoch.UTC().Format(time.RFC3339) + } + return "" +} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/epochstring.go b/vendor/github.com/lacework/go-sdk/lwtime/epochstring.go new file mode 100644 index 000000000..8abd65b06 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwtime/epochstring.go @@ -0,0 +1,33 @@ +package lwtime + +import ( + "strconv" + "strings" + "time" +) + +// EpochString time type to parse the returned 13 digit time in milliseconds. +// Used instead of Epoch type when unmarshalling a json response where epoch time is a string. +type EpochString time.Time + +func (epoch *EpochString) UnmarshalJSON(b []byte) error { + t := strings.Trim(string(b), `"`) + millis, _ := strconv.ParseInt(t, 10, 64) + seconds := time.Unix(millis/1000, 0) + *epoch = EpochString(seconds) + return nil +} + +func (epoch *EpochString) MarshalJSON() ([]byte, error) { + return epoch.ToTime().UTC().MarshalJSON() +} + +func (epoch EpochString) ToTime() time.Time { + return time.Time(epoch) +} +func (epoch EpochString) Format(s string) string { + return epoch.ToTime().Format(s) +} +func (epoch EpochString) UTC() time.Time { + return epoch.ToTime().UTC() +} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/nanotime.go b/vendor/github.com/lacework/go-sdk/lwtime/nanotime.go new file mode 100644 index 000000000..b6c97b027 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwtime/nanotime.go @@ -0,0 +1,33 @@ +package lwtime + +import "time" + +// NanoTime time type to parse the returned time with nano format +// +// Example: "2020-08-20T01:00:00+0000" +type NanoTime time.Time + +func (nano *NanoTime) UnmarshalJSON(b []byte) (err error) { + s := string(b) + t, err := time.Parse(time.RFC3339Nano, s[1:len(s)-1]) + if err != nil { + t, err = time.Parse("2006-01-02T15:04:05.999999999Z0700", s[1:len(s)-1]) + } + *nano = NanoTime(t) + return +} + +func (nano NanoTime) MarshalJSON() ([]byte, error) { + // @afiune we might have problems changing the location :( + return nano.ToTime().UTC().MarshalJSON() +} + +func (nano NanoTime) ToTime() time.Time { + return time.Time(nano) +} +func (nano NanoTime) Format(s string) string { + return nano.ToTime().Format(s) +} +func (nano NanoTime) UTC() time.Time { + return nano.ToTime().UTC() +} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/nattime.go b/vendor/github.com/lacework/go-sdk/lwtime/nattime.go new file mode 100644 index 000000000..4df5b6efd --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwtime/nattime.go @@ -0,0 +1,201 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A simple relative and natural time package. +package lwtime + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" +) + +type naturalAdjective string + +const ( + // both plural and singular regular expressions should contain 3 capture groups + naturalPluralRE = `^(last)\s(\d+)\s(years|months|weeks|days|hours|minutes|seconds)$` + naturalSingularRE = `^(this|current|previous|last)(\s)(year|month|week|day|hour|minute|second)$` + + Today naturalAdjective = "today" + Yesterday naturalAdjective = "yesterday" + This naturalAdjective = "this" + Current naturalAdjective = "current" + Previous naturalAdjective = "previous" + Last naturalAdjective = "last" +) + +func (nta naturalAdjective) isValid() bool { + switch naturalAdjective(strings.ToLower(string(nta))) { // inline lowercase conversion + case Today, Yesterday, This, Current, Previous, Last: + return true + } + return false +} + +type natural struct { + adjective naturalAdjective + num string + iNum int + unit relativeUnit +} + +func (nt *natural) loadRelativeUnit(u string) bool { + switch strings.ToLower(u) { + case "year", "years": + nt.unit = Year + case "month", "months": + nt.unit = Month + case "week", "weeks": + nt.unit = Week + case "day", "days": + nt.unit = Day + case "hour", "hours": + nt.unit = Hour + case "minute", "minutes": + nt.unit = Minute + case "second", "seconds": + nt.unit = Second + default: + return false + } + return true +} + +func newNatural(s string) (natural, error) { + s = strings.ToLower(s) + + // Today + if naturalAdjective(s) == Today { + return natural{ + adjective: This, + num: "1", iNum: 1, + unit: relativeUnit("d"), + }, nil + } + // Yesterday + if naturalAdjective(s) == Yesterday { + return natural{ + adjective: Previous, + num: "1", iNum: 1, + unit: relativeUnit("d"), + }, nil + } + + nt := natural{} + var nt_parts []string + // Singular + singularRE := regexp.MustCompile(naturalSingularRE) + if nt_parts = singularRE.FindStringSubmatch(s); s == "" || nt_parts == nil { + // Plural + pluralRE := regexp.MustCompile(naturalPluralRE) + if nt_parts = pluralRE.FindStringSubmatch(s); s == "" || nt_parts == nil { + return nt, errors.New(fmt.Sprintf("natural time (%s) is invalid", s)) + } + } + // Adjective + nt.adjective = naturalAdjective(nt_parts[1]) + if !nt.adjective.isValid() { + // this would indicate a code mismatch between enumerated adjectives and regex + return nt, errors.New(fmt.Sprintf("invalid adjective for natural time (%s)", s)) + } + // Num + nt.num = nt_parts[2] + var err error + nt.iNum, err = strconv.Atoi(nt.num) + if err != nil { + nt.num = "1" + nt.iNum = 1 + } + // Unit + if ok := nt.loadRelativeUnit(nt_parts[3]); !ok { + // this would indicate a code mismatch between relative units and regex + return nt, errors.New(fmt.Sprintf("invalid unit for natural time (%s)", s)) + } + return nt, nil +} + +func (nt natural) getRange(t time.Time) (start time.Time, end time.Time, err error) { + var relStart, relEnd string + baseErr := "unable to compute natural time range" + + // relatives + if relStart, relEnd, err = nt.getRelativeRange(); err != nil { + err = errors.Wrap(err, baseErr) + return + } + + // start time + if start, err = parseRelativeFromTime(relStart, t); err != nil { + err = errors.Wrap(err, baseErr) + return + } + + // end time + if end, err = parseRelativeFromTime(relEnd, t); err != nil { + err = errors.Wrap(err, baseErr) + return + } + + return +} + +func (nt natural) getRelativeRange() (relStart string, relEnd string, err error) { + // use natural adjective to determine relative start/end specifiers + switch nt.adjective { + case This, Current: + relStart = fmt.Sprintf("@%s", nt.unit) + relEnd = "now" + case Previous: + relStart = fmt.Sprintf("-1%s@%s", nt.unit, nt.unit) + relEnd = fmt.Sprintf("@%s", nt.unit) + case Last: + relStart = fmt.Sprintf("-%s%s", nt.num, nt.unit) + relEnd = "now" + default: + err = errors.New("invalid adjective for natural time") + } + return +} + +// ParseNatural parses the string representation of a Lacework natural time +// Start and End time objects are returned in UTC +// +// start, end, err := lwtime.ParseNatural("this year") +// if err != nil { +// ... +// } +func ParseNatural(n string) (time.Time, time.Time, error) { + // time.Now() is intentional here such that snaps work properly + // For instance snapping to @d should snap to the start of the local day + startLocal, endLocal, err := parseNaturalFromTime(n, time.Now()) + return startLocal.UTC(), endLocal.UTC(), err +} + +func parseNaturalFromTime(n string, fromTime time.Time) (time.Time, time.Time, error) { + natural, err := newNatural(n) + if err != nil { + return time.Time{}, time.Time{}, err + } + + return natural.getRange(fromTime) +} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/reltime.go b/vendor/github.com/lacework/go-sdk/lwtime/reltime.go new file mode 100644 index 000000000..3c6143b61 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwtime/reltime.go @@ -0,0 +1,257 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package lwtime + +import ( + "fmt" + "os" + "regexp" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" +) + +type relativeDate struct { + year int + month time.Month + day int +} + +// The potential difference between the clocks on the client +// and the Lacework API server +const clockOffset = "+0s" + +// Returns 'now' with the default or the provided clock offset +func nowClockOffset() string { + if os.Getenv("LW_CLOCK_OFFSET") != "" { + return os.Getenv("LW_CLOCK_OFFSET") + } + return clockOffset +} + +func mondays(year int) (mondays []relativeDate) { + // get the start of the year datetime + start := time.Date(year, 1, 1, 0, 0, 0, 0, time.Now().Location()) + // get 24 hours of duration + d, _ := time.ParseDuration("24h") + // set startYear for comparison as we iterate + startYear := start.Year() + // iterate until startYear deviates from start.Year() + for startYear == start.Year() { + // if we kave a monday, add it.... + if start.Weekday() == time.Monday { + year, month, day := start.Date() + mondays = append(mondays, relativeDate{year, month, day}) + } + // add our 24 hour duration + start = start.Add(d) + } + return mondays +} + +type relativeUnit string + +const ( + relativeRE = `^([+-])?(?:(\d+)(\w+))?(?:@(\w+))?$` + Year relativeUnit = "y" + Month relativeUnit = "mon" + Week relativeUnit = "w" + Day relativeUnit = "d" + Hour relativeUnit = "h" + Minute relativeUnit = "m" + Second relativeUnit = "s" + HoursInADay = 24 +) + +func (ru relativeUnit) isValid() bool { + switch relativeUnit(strings.ToLower(string(ru))) { // inline lowercase conversion + case Year, Month, Week, Day, Hour, Minute, Second, relativeUnit(""): + return true + } + return false +} + +func (ru relativeUnit) snapTime(inTime time.Time) (outTime time.Time, err error) { + // immediately short circuit if snap is invalid + if !ru.isValid() { + err = errors.New(fmt.Sprintf( + "snap (%s) is not a valid relative time unit", ru)) + return + } + + year, month, day := inTime.Date() + hour := inTime.Hour() + minute := inTime.Minute() + second := inTime.Second() + nano := inTime.Nanosecond() + + switch relativeUnit(strings.ToLower(string(ru))) { + case Week: + year, week := inTime.ISOWeek() + relDate := mondays(year)[week-1] + outTime = time.Date( + relDate.year, + relDate.month, + relDate.day, + 0, 0, 0, 0, inTime.Location(), + ) + return + case Year: + month = 1 + fallthrough + case Month: + day = 1 + fallthrough + case Day: + hour = 0 + fallthrough + case Hour: + minute = 0 + fallthrough + case Minute: + second = 0 + fallthrough + case Second: + nano = 0 + } + outTime = time.Date(year, month, day, hour, minute, second, nano, inTime.Location()) + return +} + +type relative struct { + num string + iNum int + unit relativeUnit + snap relativeUnit +} + +func newRelative(s string) (relative, error) { + var rel relative + var rel_parts []string + + // now is equivelant to LW_CLOCK_OFFSET (defaults to const clockOffset(+0s)) + // prevent corner conditions with Lacework's API server + if s == "now" { + s = nowClockOffset() + } + // regex + re := regexp.MustCompile(relativeRE) + if rel_parts = re.FindStringSubmatch(s); s == "" || rel_parts == nil { + return rel, errors.New(fmt.Sprintf("relative time specifier (%s) is invalid", s)) + } + // Num + if rel_parts[1] == "-" { + rel.num = rel_parts[1] + rel_parts[2] + } else { + rel.num = rel_parts[2] + } + var err error + rel.iNum, err = strconv.Atoi(rel.num) + if err != nil { + rel.num = "0" + rel.iNum = 0 + } + // Unit + rel.unit = relativeUnit(strings.ToLower(string(rel_parts[3]))) + if !rel.unit.isValid() { + return rel, errors.New(fmt.Sprintf("invalid unit for relative time specifier (%s)", s)) + } + // normalize years, weeks, and days in to hours + switch rel.unit { + case relativeUnit(""): + rel.unit = Second + case Week: + rel.iNum = rel.iNum * 7 * HoursInADay + rel.num = strconv.Itoa(rel.iNum) + rel.unit = Hour + case Day: + rel.iNum = rel.iNum * HoursInADay + rel.num = strconv.Itoa(rel.iNum) + rel.unit = Hour + } + // Snap + rel.snap = relativeUnit(strings.ToLower(string(rel_parts[4]))) + if !rel.snap.isValid() { + return rel, errors.New(fmt.Sprintf("invalid snap for relative time specifier (%s)", s)) + } + return rel, nil +} + +func (rel relative) time(inTime time.Time) (outTime time.Time, err error) { + baseErr := "unable to construct time object" + + switch rel.unit { + case Year: + outTime = inTime.AddDate(rel.iNum, 0, 0) + case Month: + outTime = inTime.AddDate(0, rel.iNum, 0) + case Day: + outTime = inTime.AddDate(0, 0, rel.iNum) + case Hour, Minute, Second: + var d time.Duration + d, err = time.ParseDuration(fmt.Sprintf("%s%s", rel.num, rel.unit)) + if err != nil { + return + } + outTime = inTime.Add(d) + default: + err = errors.Wrap( + errors.New(fmt.Sprintf("relative time unit (%s) is invalid", rel.unit)), + baseErr, + ) + return + } + if rel.snap != "" { + outTime, err = rel.snap.snapTime(outTime) + } + if err != nil { + err = errors.Wrap(err, baseErr) + return + } + if outTime.Unix() < 0 { + err = errors.Wrap(errors.New("time predates epoch"), baseErr) + return + } + return +} + +// ParseRelative parses the string representation of a Lacework relative time. +// Time object is returned in UTC. +// +// t, err := lwtime.ParseRelative("-1y@y") +// if err != nil { +// ... +// } +func ParseRelative(s string) (time.Time, error) { + // time.Now() is intentional here such that snaps work properly + // For instance snapping to @d should snap to the start of the local day + localTime, err := parseRelativeFromTime(s, time.Now()) + return localTime.UTC(), err +} + +func parseRelativeFromTime(s string, fromTime time.Time) (time.Time, error) { + relative, err := newRelative(s) + if err != nil { + return time.Time{}, err + } + + return relative.time(fromTime) +} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/rfc1123z.go b/vendor/github.com/lacework/go-sdk/lwtime/rfc1123z.go new file mode 100644 index 000000000..412ca62ae --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwtime/rfc1123z.go @@ -0,0 +1,29 @@ +package lwtime + +import ( + "strings" + "time" +) + +type RFC1123Z time.Time + +func (rfc *RFC1123Z) UnmarshalJSON(b []byte) (err error) { + t := strings.Trim(string(b), `"`) + res, _ := time.Parse(time.RFC1123Z, t) + *rfc = RFC1123Z(res) + return +} + +func (rfc *RFC1123Z) MarshalJSON() ([]byte, error) { + return rfc.ToTime().UTC().MarshalJSON() +} + +func (rfc RFC1123Z) ToTime() time.Time { + return time.Time(rfc) +} +func (rfc RFC1123Z) Format(s string) string { + return rfc.ToTime().Format(s) +} +func (rfc RFC1123Z) UTC() time.Time { + return rfc.ToTime().UTC() +} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/rfc3339.go b/vendor/github.com/lacework/go-sdk/lwtime/rfc3339.go new file mode 100644 index 000000000..a8744aa50 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwtime/rfc3339.go @@ -0,0 +1,5 @@ +package lwtime + +const ( + RFC3339Milli = "2006-01-02T15:04:05.000Z" +) diff --git a/vendor/github.com/lacework/go-sdk/lwupdater/README.md b/vendor/github.com/lacework/go-sdk/lwupdater/README.md new file mode 100644 index 000000000..8a70c5e5a --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwupdater/README.md @@ -0,0 +1,46 @@ +# Lacework Updater + +A Go library to check for available updates of Lacework projects. + +## Usage + +Download the library into your `$GOPATH`: + + $ go get github.com/lacework/go-sdk/lwupdater + +Import the library into your tool: + +```go +import "github.com/lacework/go-sdk/lwupdater" +``` + +## Examples + +This example checks for the latest release of this repository (https://github.com/lacework/go-sdk): +```go +package main + +import ( + "fmt" + + "github.com/lacework/go-sdk/lwupdater" +) + +func main() { + var ( + project = "go-sdk" + sdk, err = lwupdater.Check(project, "v0.1.0") + ) + + if err != nil { + fmt.Println("Unable to check for updates: %s", err) + } else { + // Output: The latest release of the go-sdk project is v0.1.7 + fmt.Printf("The latest release of the %s project is %s\n", + project, sdk.LatestVersion, + ) + } +} +``` + +Look at the [_examples/](_examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/lwupdater/updater.go b/vendor/github.com/lacework/go-sdk/lwupdater/updater.go new file mode 100644 index 000000000..c0510da3d --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/lwupdater/updater.go @@ -0,0 +1,223 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2020, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A library to check for available updates of Lacework projects. +package lwupdater + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/lacework/go-sdk/lwlogger" + "github.com/pkg/errors" +) + +const ( + // GithubOrganization is the default Github organization where + // Lacework stores their open source projects + GithubOrganization = "lacework" + + // DisableEnv controls the overall check for updates behavior, when + // this environment variable is set, we do not check for updates + DisableEnv = "LW_UPDATES_DISABLE" +) + +var log = lwlogger.New("") + +// Version is used to check project versions and store it into a cache file +// normally at the directory `~/.config/lacework`, to execute regular version checks +type Version struct { + Project string `json:"project"` + CurrentVersion string `json:"current_version"` + LatestVersion string `json:"latest_version"` + LastCheckTime time.Time `json:"last_check_time"` + Outdated bool `json:"outdated"` + ComponentsLastCheck map[string]time.Time `json:"components_last_check,omitempty"` +} + +// StoreCache stores version information into the provided path +func (cache *Version) StoreCache(path string) error { + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(cache); err != nil { + return err + } + + err := os.WriteFile(path, buf.Bytes(), 0644) + if err != nil { + return err + } + + return nil +} + +// StoreCache stores version information into the provided path +func (cache *Version) CheckComponentBefore(component string, checkTime time.Time) bool { + if cache.ComponentsLastCheck == nil { + cache.ComponentsLastCheck = make(map[string]time.Time) + } + + if lastCheck, ok := cache.ComponentsLastCheck[component]; !ok || lastCheck.Before(checkTime) { + return true + } + return false +} + +// Check verifies if the a project is outdated based of the current version +func Check(project, current string) (*Version, error) { + if disabled := os.Getenv(DisableEnv); disabled != "" { + return new(Version), nil + } + + release, err := getGitRelease(project, "latest") + if err != nil { + return new(Version), err + } + + outdated := false + if !strings.Contains(current, "dev") && current != release.TagName { + outdated = true + } + + return &Version{ + Project: project, + CurrentVersion: current, + LatestVersion: release.TagName, + LastCheckTime: time.Now(), + Outdated: outdated, + }, nil +} + +// LoadCache loads a version cache file from the provided path +func LoadCache(path string) (*Version, error) { + cacheJSON, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var versionCache = new(Version) + err = json.Unmarshal(cacheJSON, versionCache) + return versionCache, err +} + +// getGitRelease uses the git API to fetch the release information of a project. +// This function could hit request rate limits wich are roughly 60 every 30m, to +// check your current rate limits run: curl https://api.github.com/rate_limit +// If the GITHUB_TOKEN environment variable is set, then this function will use +// it to authenticate the API request, which grants higher rate limits. +func getGitRelease(project, version string) (*gitReleaseResponse, error) { + if project == "" { + return nil, errors.New("specify a valid project") + } + if version == "" { + version = "latest" + } + + var ( + c = http.Client{} + u = url.URL{ + Scheme: "https", + Host: "api.github.com", + Path: fmt.Sprintf( + "/repos/%s/%s/releases/latest", + GithubOrganization, project, + ), + } + ) + if version != "latest" { + u.Path = fmt.Sprintf("/repos/%s/%s/releases/tags/%s", + GithubOrganization, project, version) + } + + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, err + } + + // Set the user agent since it is required + // https://developer.github.com/v3/#user-agent-required + req.Header.Set("User-Agent", "lacework-updater") + + token := os.Getenv("GITHUB_TOKEN") + if len(token) > 0 { + req.Header.Set("Authorization", "Bearer "+token) + } + + var resp *http.Response + err = backoff.Retry(func() error { + resp, err = c.Do(req) + if err != nil { + return err + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + logHeaders(resp) + return errors.New(resp.Status) + } + return nil + }, backoffStrategy()) + if err != nil { + return nil, err + } + + var gitRelRes gitReleaseResponse + if err := json.NewDecoder(resp.Body).Decode(&gitRelRes); err != nil { + return nil, err + } + + return &gitRelRes, nil +} + +func backoffStrategy() *backoff.ExponentialBackOff { + strategy := backoff.NewExponentialBackOff() + strategy.InitialInterval = 2 * time.Second + strategy.MaxElapsedTime = 1 * time.Minute + return strategy +} + +func logHeaders(resp *http.Response) { + var headers strings.Builder + for key, values := range resp.Header { + headers.WriteString(fmt.Sprintf("%s: %s\n", key, strings.Join(values, ","))) + } + log.Debug(headers.String()) +} + +type gitReleaseResponse struct { + ID int32 `json:"id"` + Url string `json:"url"` + HtmlUrl string `json:"html_url"` + AssetsUrl string `json:"assets_url"` + UploadUrl string `json:"upload_url"` + TarballUrl string `json:"tarball_url"` + ZipballUrl string `json:"zipball_url"` + NodeID string `json:"node_id"` + TagName string `json:"tag_name"` + TargetCommitish string `json:"target_commitish"` + Name string `json:"name"` + Body string `json:"body"` + Draft bool `json:"draft"` + Prerelease bool `json:"prerelease"` + CreatedAt time.Time `json:"created_at"` + PublishedAt time.Time `json:"published_at"` +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ecef71dfb..bea486478 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -446,6 +446,45 @@ github.com/kr/pty # github.com/kyokomi/emoji/v2 v2.2.12 ## explicit; go 1.14 github.com/kyokomi/emoji/v2 +# github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65 +## explicit; go 1.21 +github.com/lacework/go-sdk/api +github.com/lacework/go-sdk/cli/cdk/client/go +github.com/lacework/go-sdk/cli/cdk/go/proto/v1 +github.com/lacework/go-sdk/cli/cmd +github.com/lacework/go-sdk/internal/archive +github.com/lacework/go-sdk/internal/array +github.com/lacework/go-sdk/internal/cache +github.com/lacework/go-sdk/internal/capturer +github.com/lacework/go-sdk/internal/databox +github.com/lacework/go-sdk/internal/failon +github.com/lacework/go-sdk/internal/file +github.com/lacework/go-sdk/internal/format +github.com/lacework/go-sdk/internal/intgguid +github.com/lacework/go-sdk/internal/lacework +github.com/lacework/go-sdk/internal/pointer +github.com/lacework/go-sdk/internal/unique +github.com/lacework/go-sdk/internal/validate +github.com/lacework/go-sdk/lwcloud/gcp/helpers +github.com/lacework/go-sdk/lwcloud/gcp/resources/folders +github.com/lacework/go-sdk/lwcloud/gcp/resources/instances +github.com/lacework/go-sdk/lwcloud/gcp/resources/models +github.com/lacework/go-sdk/lwcloud/gcp/resources/projects +github.com/lacework/go-sdk/lwcomponent +github.com/lacework/go-sdk/lwconfig +github.com/lacework/go-sdk/lwdomain +github.com/lacework/go-sdk/lwgenerate +github.com/lacework/go-sdk/lwgenerate/aws +github.com/lacework/go-sdk/lwgenerate/aws_controltower +github.com/lacework/go-sdk/lwgenerate/aws_eks_audit +github.com/lacework/go-sdk/lwgenerate/azure +github.com/lacework/go-sdk/lwgenerate/gcp +github.com/lacework/go-sdk/lwgenerate/oci +github.com/lacework/go-sdk/lwlogger +github.com/lacework/go-sdk/lwrunner +github.com/lacework/go-sdk/lwseverity +github.com/lacework/go-sdk/lwtime +github.com/lacework/go-sdk/lwupdater # github.com/magiconair/properties v1.8.6 ## explicit; go 1.13 github.com/magiconair/properties From 5d82c0c042cf061017aef122df7e6dd4f17ab1c6 Mon Sep 17 00:00:00 2001 From: Lei Jin Date: Wed, 30 Oct 2024 19:48:49 +0000 Subject: [PATCH 2/3] Revert "chore: Change go-sdk to v2" This reverts commit e7c633d44e2714623e3dc24aff995bcfd7cd7293. --- api/version.go | 2 +- go.mod | 3 +- go.sum | 2 - vendor/github.com/lacework/go-sdk/LICENSE | 201 -- .../github.com/lacework/go-sdk/api/README.md | 61 - .../api/_templates/resource_groups/aws.json | 40 - .../api/_templates/resource_groups/azure.json | 70 - .../_templates/resource_groups/container.json | 50 - .../api/_templates/resource_groups/gcp.json | 70 - .../resource_groups/kubernetes.json | 49 - .../_templates/resource_groups/machine.json | 20 - .../api/_templates/resource_groups/oci.json | 50 - .../go-sdk/api/agent_access_tokens.go | 171 -- .../lacework/go-sdk/api/agent_info.go | 100 - .../go-sdk/api/alert_channel_datadog.go | 90 - .../lacework/go-sdk/api/alert_channels.go | 233 --- .../api/alert_channels_aws_cloudwatch.go | 51 - .../go-sdk/api/alert_channels_aws_s3.go | 57 - .../api/alert_channels_cisco_spark_webhook.go | 49 - .../go-sdk/api/alert_channels_email_user.go | 123 -- .../go-sdk/api/alert_channels_gcp_pub_sub.go | 56 - .../go-sdk/api/alert_channels_ibm_qradar.go | 75 - .../api/alert_channels_jira_cloud_server.go | 110 -- .../api/alert_channels_microsoft_teams.go | 49 - .../go-sdk/api/alert_channels_new_relic.go | 52 - .../go-sdk/api/alert_channels_pager_duty.go | 51 - .../api/alert_channels_service_now_rest.go | 82 - .../api/alert_channels_slack_channel.go | 51 - .../go-sdk/api/alert_channels_splunk.go | 61 - .../go-sdk/api/alert_channels_victorops.go | 51 - .../go-sdk/api/alert_channels_webhook.go | 51 - .../lacework/go-sdk/api/alert_profiles.go | 158 -- .../lacework/go-sdk/api/alert_rules.go | 314 ---- .../lacework/go-sdk/api/alert_templates.go | 69 - .../github.com/lacework/go-sdk/api/alerts.go | 181 -- .../lacework/go-sdk/api/alerts_close.go | 82 - .../lacework/go-sdk/api/alerts_comment.go | 50 - .../lacework/go-sdk/api/alerts_details.go | 110 -- .../go-sdk/api/alerts_details_events.go | 44 - .../go-sdk/api/alerts_details_integrations.go | 92 - .../api/alerts_details_investigation.go | 45 - .../go-sdk/api/alerts_details_related.go | 61 - .../go-sdk/api/alerts_details_timeline.go | 75 - .../lacework/go-sdk/api/alerts_search.go | 68 - vendor/github.com/lacework/go-sdk/api/api.go | 167 -- vendor/github.com/lacework/go-sdk/api/auth.go | 165 -- .../lacework/go-sdk/api/callbacks.go | 39 - .../github.com/lacework/go-sdk/api/client.go | 278 --- .../lacework/go-sdk/api/cloud_accounts.go | 296 --- .../go-sdk/api/cloud_accounts_aws_cfg.go | 57 - .../go-sdk/api/cloud_accounts_aws_ct_sqs.go | 92 - .../api/cloud_accounts_aws_eks_audit.go | 57 - .../go-sdk/api/cloud_accounts_aws_gov_cfg.go | 57 - .../go-sdk/api/cloud_accounts_aws_gov_ct.go | 58 - .../go-sdk/api/cloud_accounts_aws_sidekick.go | 84 - .../api/cloud_accounts_aws_sidekick_org.go | 107 -- .../go-sdk/api/cloud_accounts_az_al.go | 58 - .../go-sdk/api/cloud_accounts_az_cfg.go | 57 - .../go-sdk/api/cloud_accounts_azure_ad_al.go | 59 - .../api/cloud_accounts_azure_sidekick.go | 88 - .../api/cloud_accounts_gcp_al_pubsub.go | 63 - .../go-sdk/api/cloud_accounts_gcp_at.go | 77 - .../go-sdk/api/cloud_accounts_gcp_cfg.go | 80 - .../api/cloud_accounts_gcp_gke_audit.go | 62 - .../go-sdk/api/cloud_accounts_gcp_sidekick.go | 115 -- .../go-sdk/api/cloud_accounts_oci_cfg.go | 60 - .../go-sdk/api/compliance_evaluations.go | 68 - .../go-sdk/api/compliance_evaluations_aws.go | 54 - .../lacework/go-sdk/api/component_data.go | 208 --- .../lacework/go-sdk/api/components.go | 81 - .../go-sdk/api/container_registries.go | 275 --- ...container_registries_aws_ecr_access_key.go | 98 - .../container_registries_aws_ecr_iam_role.go | 69 - .../api/container_registries_dockerhub.go | 77 - .../api/container_registries_dockerhub_v2.go | 76 - .../api/container_registries_gcp_gar.go | 81 - .../api/container_registries_gcp_gcr.go | 71 - .../go-sdk/api/container_registries_ghcr.go | 82 - .../container_registries_inline_scanner.go | 67 - .../api/container_registries_proxy_scanner.go | 69 - .../lacework/go-sdk/api/data_export_rules.go | 130 -- .../lacework/go-sdk/api/datasources.go | 88 - .../lacework/go-sdk/api/entities.go | 92 - .../go-sdk/api/entities_containers.go | 158 -- .../lacework/go-sdk/api/entities_images.go | 124 -- .../go-sdk/api/entities_machine_details.go | 171 -- .../lacework/go-sdk/api/entities_machines.go | 164 -- .../lacework/go-sdk/api/entities_users.go | 87 - .../github.com/lacework/go-sdk/api/errors.go | 113 -- .../lacework/go-sdk/api/feature_flags.go | 27 - vendor/github.com/lacework/go-sdk/api/http.go | 258 --- .../lacework/go-sdk/api/inventory.go | 90 - .../lacework/go-sdk/api/inventory_aws.go | 62 - .../github.com/lacework/go-sdk/api/logging.go | 156 -- vendor/github.com/lacework/go-sdk/api/lql.go | 133 -- .../lacework/go-sdk/api/lql_delete.go | 47 - .../lacework/go-sdk/api/lql_execute.go | 159 -- .../lacework/go-sdk/api/lql_validate.go | 32 - .../github.com/lacework/go-sdk/api/metrics.go | 110 -- .../lacework/go-sdk/api/organization_info.go | 53 - .../github.com/lacework/go-sdk/api/policy.go | 364 ---- .../lacework/go-sdk/api/policy_exceptions.go | 111 -- .../github.com/lacework/go-sdk/api/reader.go | 33 - .../api/report_rule_notification_types.go | 269 --- .../lacework/go-sdk/api/report_rules.go | 305 --- .../github.com/lacework/go-sdk/api/reports.go | 123 -- .../lacework/go-sdk/api/reports_aws.go | 174 -- .../lacework/go-sdk/api/reports_azure.go | 183 -- .../go-sdk/api/reports_definitions.go | 237 --- .../go-sdk/api/reports_distributions.go | 205 -- .../lacework/go-sdk/api/reports_gcp.go | 204 -- .../lacework/go-sdk/api/resource_groups.go | 262 --- .../github.com/lacework/go-sdk/api/schemas.go | 44 - .../lacework/go-sdk/api/team_members.go | 295 --- .../lacework/go-sdk/api/user_profile.go | 84 - vendor/github.com/lacework/go-sdk/api/v2.go | 265 --- .../lacework/go-sdk/api/v2_configs.go | 34 - .../lacework/go-sdk/api/v2_configs_azure.go | 56 - .../lacework/go-sdk/api/v2_configs_gcp.go | 56 - .../lacework/go-sdk/api/v2_recommendations.go | 143 -- .../go-sdk/api/v2_recommendations_aws.go | 70 - .../go-sdk/api/v2_recommendations_azure.go | 87 - .../go-sdk/api/v2_recommendations_gcp.go | 87 - .../lacework/go-sdk/api/v2_search_filters.go | 119 -- .../lacework/go-sdk/api/v2_suppressions.go | 101 - .../go-sdk/api/v2_suppressions_aws.go | 29 - .../go-sdk/api/v2_suppressions_azure.go | 29 - .../go-sdk/api/v2_suppressions_gcp.go | 29 - .../lacework/go-sdk/api/v2_vulnerabilities.go | 772 -------- .../v2_vulnerabilities_software_packages.go | 205 -- .../github.com/lacework/go-sdk/api/version.go | 10 - .../go-sdk/api/vulnerability_exceptions.go | 474 ----- .../api/vulnerability_exceptions_container.go | 91 - .../api/vulnerability_exceptions_host.go | 88 - .../go-sdk/cli/cdk/client/go/client.go | 215 --- .../go-sdk/cli/cdk/go/proto/v1/cdk.pb.go | 708 ------- .../go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go | 226 --- .../lacework/go-sdk/cli/cmd/access_token.go | 100 - .../lacework/go-sdk/cli/cmd/account.go | 88 - .../lacework/go-sdk/cli/cmd/agent.go | 395 ---- .../go-sdk/cli/cmd/agent_aws-install_ec2ic.go | 205 -- .../cli/cmd/agent_aws-install_ec2ssh.go | 212 --- .../cli/cmd/agent_aws-install_ec2ssm.go | 405 ---- .../go-sdk/cli/cmd/agent_gcp-install-osl.go | 215 --- .../lacework/go-sdk/cli/cmd/agent_install.go | 384 ---- .../lacework/go-sdk/cli/cmd/agent_list.go | 241 --- .../lacework/go-sdk/cli/cmd/alert.go | 163 -- .../lacework/go-sdk/cli/cmd/alert_channel.go | 227 --- .../lacework/go-sdk/cli/cmd/alert_close.go | 172 -- .../lacework/go-sdk/cli/cmd/alert_comment.go | 90 - .../lacework/go-sdk/cli/cmd/alert_list.go | 291 --- .../go-sdk/cli/cmd/alert_list_fixable.go | 108 -- .../lacework/go-sdk/cli/cmd/alert_profiles.go | 458 ----- .../lacework/go-sdk/cli/cmd/alert_rules.go | 378 ---- .../lacework/go-sdk/cli/cmd/alert_show.go | 109 -- .../go-sdk/cli/cmd/alert_show_details.go | 107 -- .../go-sdk/cli/cmd/alert_show_events.go | 16 - .../go-sdk/cli/cmd/alert_show_integrations.go | 55 - .../cli/cmd/alert_show_investigation.go | 52 - .../go-sdk/cli/cmd/alert_show_related.go | 59 - .../go-sdk/cli/cmd/alert_show_timeline.go | 57 - .../github.com/lacework/go-sdk/cli/cmd/api.go | 133 -- .../github.com/lacework/go-sdk/cli/cmd/aws.go | 224 --- .../lacework/go-sdk/cli/cmd/awsiam.go | 320 ---- .../lacework/go-sdk/cli/cmd/cache.go | 317 ---- .../github.com/lacework/go-sdk/cli/cmd/cdk.go | 177 -- .../lacework/go-sdk/cli/cmd/cli_state.go | 517 ------ .../lacework/go-sdk/cli/cmd/cli_unix.go | 101 - .../lacework/go-sdk/cli/cmd/cli_windows.go | 63 - .../lacework/go-sdk/cli/cmd/cloud_account.go | 339 ---- .../lacework/go-sdk/cli/cmd/compliance.go | 572 ------ .../lacework/go-sdk/cli/cmd/compliance_aws.go | 761 -------- .../go-sdk/cli/cmd/compliance_azure.go | 765 -------- .../lacework/go-sdk/cli/cmd/compliance_gcp.go | 845 --------- .../lacework/go-sdk/cli/cmd/component.go | 1295 ------------- .../lacework/go-sdk/cli/cmd/component_args.go | 179 -- .../lacework/go-sdk/cli/cmd/component_dev.go | 675 ------- .../lacework/go-sdk/cli/cmd/configure.go | 426 ----- .../cli/cmd/configure_switch_profile.go | 79 - .../go-sdk/cli/cmd/container_registry.go | 199 -- .../go-sdk/cli/cmd/content_library.go | 205 -- .../lacework/go-sdk/cli/cmd/docs.go | 98 - .../lacework/go-sdk/cli/cmd/emoji.go | 30 - .../lacework/go-sdk/cli/cmd/emoji_unix.go | 31 - .../lacework/go-sdk/cli/cmd/emoji_windows.go | 27 - .../lacework/go-sdk/cli/cmd/errors.go | 137 -- .../lacework/go-sdk/cli/cmd/errors_lql.go | 101 - .../lacework/go-sdk/cli/cmd/flags.go | 124 -- .../github.com/lacework/go-sdk/cli/cmd/gcp.go | 111 -- .../lacework/go-sdk/cli/cmd/generate.go | 323 ---- .../lacework/go-sdk/cli/cmd/generate_aws.go | 1645 ----------------- .../cli/cmd/generate_aws_controltower.go | 730 -------- .../go-sdk/cli/cmd/generate_aws_eks_audit.go | 983 ---------- .../lacework/go-sdk/cli/cmd/generate_azure.go | 895 --------- .../go-sdk/cli/cmd/generate_cloud_account.go | 31 - .../go-sdk/cli/cmd/generate_execute.go | 477 ----- .../lacework/go-sdk/cli/cmd/generate_gcp.go | 802 -------- .../lacework/go-sdk/cli/cmd/generate_gke.go | 505 ----- .../lacework/go-sdk/cli/cmd/generate_k8s.go | 24 - .../lacework/go-sdk/cli/cmd/generate_oci.go | 438 ----- .../lacework/go-sdk/cli/cmd/grpc.go | 50 - .../lacework/go-sdk/cli/cmd/honeyvent.go | 225 --- .../go-sdk/cli/cmd/integration_aws.go | 168 -- .../cli/cmd/integration_aws_cloudwatch.go | 64 - .../cli/cmd/integration_aws_govcloud.go | 140 -- .../cli/cmd/integration_aws_s3_channel.go | 80 - .../go-sdk/cli/cmd/integration_azure.go | 211 --- .../go-sdk/cli/cmd/integration_cisco_webex.go | 64 - .../cli/cmd/integration_ctr_reg_limits.go | 125 -- .../go-sdk/cli/cmd/integration_datadog.go | 96 - .../go-sdk/cli/cmd/integration_docker_hub.go | 128 -- .../go-sdk/cli/cmd/integration_docker_v2.go | 112 -- .../go-sdk/cli/cmd/integration_ecr.go | 212 --- .../go-sdk/cli/cmd/integration_email.go | 68 - .../go-sdk/cli/cmd/integration_gar.go | 166 -- .../go-sdk/cli/cmd/integration_gcp.go | 196 -- .../cli/cmd/integration_gcp_pub_sub_audit.go | 121 -- .../cmd/integration_gcp_pub_sub_channel.go | 109 -- .../go-sdk/cli/cmd/integration_gcr.go | 156 -- .../go-sdk/cli/cmd/integration_ghcr.go | 124 -- .../cli/cmd/integration_inline_scanner.go | 93 - .../go-sdk/cli/cmd/integration_jira.go | 171 -- .../cli/cmd/integration_microsoft_teams.go | 64 - .../cli/cmd/integration_new_relic_channel.go | 71 - .../go-sdk/cli/cmd/integration_oci.go | 107 -- .../go-sdk/cli/cmd/integration_pagerduty.go | 64 - .../cli/cmd/integration_proxy_scanner.go | 110 -- .../cli/cmd/integration_qradar_channel.go | 85 - .../cmd/integration_service_now_channel.go | 112 -- .../cli/cmd/integration_slack_channel.go | 64 - .../go-sdk/cli/cmd/integration_splunk.go | 106 -- .../go-sdk/cli/cmd/integration_victorops.go | 64 - .../go-sdk/cli/cmd/integration_webhook.go | 64 - .../github.com/lacework/go-sdk/cli/cmd/lql.go | 590 ------ .../lacework/go-sdk/cli/cmd/lql_create.go | 156 -- .../lacework/go-sdk/cli/cmd/lql_delete.go | 57 - .../lacework/go-sdk/cli/cmd/lql_library.go | 117 -- .../lacework/go-sdk/cli/cmd/lql_list.go | 88 - .../lacework/go-sdk/cli/cmd/lql_preview.go | 117 -- .../lacework/go-sdk/cli/cmd/lql_show.go | 81 - .../lacework/go-sdk/cli/cmd/lql_sources.go | 195 -- .../lacework/go-sdk/cli/cmd/lql_update.go | 149 -- .../lacework/go-sdk/cli/cmd/lql_validate.go | 108 -- .../lacework/go-sdk/cli/cmd/migration.go | 179 -- .../lacework/go-sdk/cli/cmd/outputs.go | 150 -- .../go-sdk/cli/cmd/package_manifest.go | 560 ------ .../lacework/go-sdk/cli/cmd/policy.go | 807 -------- .../lacework/go-sdk/cli/cmd/policy_create.go | 147 -- .../lacework/go-sdk/cli/cmd/policy_delete.go | 94 - .../lacework/go-sdk/cli/cmd/policy_disable.go | 71 - .../lacework/go-sdk/cli/cmd/policy_enable.go | 75 - .../go-sdk/cli/cmd/policy_exceptions.go | 640 ------- .../lacework/go-sdk/cli/cmd/policy_library.go | 552 ------ .../lacework/go-sdk/cli/cmd/policy_update.go | 225 --- .../lacework/go-sdk/cli/cmd/prompt.go | 32 - .../go-sdk/cli/cmd/report_definitions.go | 375 ---- .../cli/cmd/report_definitions_create.go | 343 ---- .../go-sdk/cli/cmd/report_definitions_diff.go | 166 -- .../cli/cmd/report_definitions_revert.go | 67 - .../cli/cmd/report_definitions_update.go | 345 ---- .../go-sdk/cli/cmd/report_distributions.go | 226 --- .../cli/cmd/report_distributions_create.go | 441 ----- .../cli/cmd/report_distributions_update.go | 360 ---- .../lacework/go-sdk/cli/cmd/report_rules.go | 412 ----- .../go-sdk/cli/cmd/resource_group_v2.go | 94 - .../go-sdk/cli/cmd/resource_groups.go | 254 --- .../lacework/go-sdk/cli/cmd/root.go | 431 ----- .../lacework/go-sdk/cli/cmd/suppressions.go | 271 --- .../go-sdk/cli/cmd/suppressions_aws.go | 437 ----- .../go-sdk/cli/cmd/suppressions_azure.go | 414 ----- .../go-sdk/cli/cmd/suppressions_gcp.go | 368 ---- .../lacework/go-sdk/cli/cmd/table_render.go | 80 - .../lacework/go-sdk/cli/cmd/team_members.go | 405 ---- .../lacework/go-sdk/cli/cmd/telemetry.go | 96 - .../test_resources/content-library/.version | 1 - .../content-library-nonzero.sh | 2 - .../content-library-nonzero.sh.sig | 1 - .../content-library-noparse.sh | 2 - .../content-library-noparse.sh.sig | 1 - .../lacework/go-sdk/cli/cmd/version.go | 294 --- .../lacework/go-sdk/cli/cmd/vuln_container.go | 142 -- .../cmd/vuln_container_list_assessments.go | 679 ------- .../cli/cmd/vuln_container_list_registries.go | 55 - .../go-sdk/cli/cmd/vuln_container_scan.go | 332 ---- .../cmd/vuln_container_show_assessments.go | 783 -------- .../lacework/go-sdk/cli/cmd/vuln_host.go | 247 --- .../cli/cmd/vuln_host_gen_package_manifest.go | 48 - .../go-sdk/cli/cmd/vuln_host_list_cves.go | 352 ---- .../go-sdk/cli/cmd/vuln_host_list_hosts.go | 237 --- .../cmd/vuln_host_scan_package_manifest.go | 329 ---- .../cli/cmd/vuln_host_show_assessment.go | 625 ------- .../lacework/go-sdk/cli/cmd/vuln_html.go | 370 ---- .../lacework/go-sdk/cli/cmd/vulnerability.go | 362 ---- .../cmd/vulnerability_exception_container.go | 188 -- .../cli/cmd/vulnerability_exception_host.go | 177 -- .../go-sdk/cli/cmd/vulnerabilty_exceptions.go | 391 ---- .../go-sdk/internal/archive/detect.go | 89 - .../lacework/go-sdk/internal/archive/gz.go | 34 - .../lacework/go-sdk/internal/archive/tar.go | 64 - .../go-sdk/internal/array/contains.go | 66 - .../lacework/go-sdk/internal/array/join.go | 31 - .../lacework/go-sdk/internal/array/sort.go | 37 - .../lacework/go-sdk/internal/array/unique.go | 33 - .../lacework/go-sdk/internal/cache/cache.go | 34 - .../internal/capturer/capture_output.go | 62 - .../lacework/go-sdk/internal/databox/blob.go | 34 - .../lacework/go-sdk/internal/databox/box.go | 91 - .../go-sdk/internal/failon/count_operation.go | 49 - .../lacework/go-sdk/internal/file/file.go | 55 - .../lacework/go-sdk/internal/format/secret.go | 32 - .../go-sdk/internal/format/strings.go | 46 - .../lacework/go-sdk/internal/intgguid/rand.go | 53 - .../go-sdk/internal/lacework/server.go | 97 - .../lacework/go-sdk/internal/pointer/bool.go | 10 - .../go-sdk/internal/unique/strings.go | 19 - .../go-sdk/internal/validate/email.go | 22 - .../go-sdk/lwcloud/gcp/helpers/helper.go | 79 - .../lwcloud/gcp/resources/folders/folders.go | 107 -- .../gcp/resources/instances/instances.go | 238 --- .../lwcloud/gcp/resources/models/models.go | 70 - .../gcp/resources/projects/projects.go | 132 -- .../lacework/go-sdk/lwcomponent/DESIGN.md | 158 -- .../lacework/go-sdk/lwcomponent/api_info.go | 38 - .../lacework/go-sdk/lwcomponent/catalog.go | 468 ----- .../go-sdk/lwcomponent/cdk_component.go | 239 --- .../go-sdk/lwcomponent/cdk_executable.go | 119 -- .../lacework/go-sdk/lwcomponent/component.go | 769 -------- .../lacework/go-sdk/lwcomponent/dev_info.go | 41 - .../lacework/go-sdk/lwcomponent/error.go | 38 - .../lacework/go-sdk/lwcomponent/executable.go | 99 - .../lacework/go-sdk/lwcomponent/host_info.go | 152 -- .../lacework/go-sdk/lwcomponent/http.go | 81 - .../lacework/go-sdk/lwcomponent/library.go | 34 - .../lacework/go-sdk/lwcomponent/minisign.go | 72 - .../lacework/go-sdk/lwcomponent/staging.go | 253 --- .../lacework/go-sdk/lwcomponent/status.go | 84 - .../lwcomponent/test_resources/env-vars.sh | 2 - .../lwcomponent/test_resources/env-vars.sig | 1 - .../lwcomponent/test_resources/hello-world.sh | 2 - .../test_resources/hello-world.sig | 1 - .../test_resources/hello-world2.sh | 4 - .../test_resources/hello-world2.sig | 1 - .../lacework/go-sdk/lwcomponent/types.go | 50 - .../lacework/go-sdk/lwconfig/README.md | 46 - .../lacework/go-sdk/lwconfig/config.go | 184 -- .../lacework/go-sdk/lwdomain/README.md | 43 - .../lacework/go-sdk/lwdomain/domain.go | 99 - .../lacework/go-sdk/lwgenerate/aws/aws.go | 1406 -------------- .../aws_controltower/aws_controltower.go | 477 ----- .../lwgenerate/aws_eks_audit/aws_eks_audit.go | 697 ------- .../lacework/go-sdk/lwgenerate/azure/azure.go | 671 ------- .../lacework/go-sdk/lwgenerate/constants.go | 43 - .../lacework/go-sdk/lwgenerate/gcp/gcp.go | 932 ---------- .../lacework/go-sdk/lwgenerate/gcp/gke.go | 256 --- .../go-sdk/lwgenerate/gcp/service_account.go | 110 -- .../lacework/go-sdk/lwgenerate/hcl.go | 662 ------- .../lacework/go-sdk/lwgenerate/oci/oci.go | 177 -- .../lacework/go-sdk/lwlogger/README.md | 48 - .../lacework/go-sdk/lwlogger/logger.go | 198 -- .../lacework/go-sdk/lwrunner/awsrunner.go | 422 ----- .../lacework/go-sdk/lwrunner/gcprunner.go | 122 -- .../lacework/go-sdk/lwrunner/runner.go | 227 --- .../lacework/go-sdk/lwrunner/ssh.go | 103 -- .../lacework/go-sdk/lwseverity/severity.go | 172 -- .../lacework/go-sdk/lwtime/README.md | 112 -- .../lacework/go-sdk/lwtime/epoch.go | 39 - .../lacework/go-sdk/lwtime/epochstring.go | 33 - .../lacework/go-sdk/lwtime/nanotime.go | 33 - .../lacework/go-sdk/lwtime/nattime.go | 201 -- .../lacework/go-sdk/lwtime/reltime.go | 257 --- .../lacework/go-sdk/lwtime/rfc1123z.go | 29 - .../lacework/go-sdk/lwtime/rfc3339.go | 5 - .../lacework/go-sdk/lwupdater/README.md | 46 - .../lacework/go-sdk/lwupdater/updater.go | 223 --- vendor/modules.txt | 39 - 375 files changed, 2 insertions(+), 68997 deletions(-) delete mode 100644 vendor/github.com/lacework/go-sdk/LICENSE delete mode 100644 vendor/github.com/lacework/go-sdk/api/README.md delete mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/aws.json delete mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/azure.json delete mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/container.json delete mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/gcp.json delete mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/kubernetes.json delete mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/machine.json delete mode 100644 vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/oci.json delete mode 100644 vendor/github.com/lacework/go-sdk/api/agent_access_tokens.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/agent_info.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channel_datadog.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_aws_cloudwatch.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_aws_s3.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_cisco_spark_webhook.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_email_user.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_gcp_pub_sub.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_ibm_qradar.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_jira_cloud_server.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_microsoft_teams.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_new_relic.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_pager_duty.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_service_now_rest.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_slack_channel.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_splunk.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_victorops.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_channels_webhook.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_profiles.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_rules.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alert_templates.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_close.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_comment.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_events.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_integrations.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_investigation.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_related.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_details_timeline.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/alerts_search.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/api.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/auth.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/callbacks.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/client.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_cfg.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_ct_sqs.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_cfg.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_ct.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick_org.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_al.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_cfg.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_ad_al.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_sidekick.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_al_pubsub.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_at.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_cfg.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_gke_audit.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_sidekick.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/cloud_accounts_oci_cfg.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/compliance_evaluations.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/compliance_evaluations_aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/component_data.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/components.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_access_key.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_iam_role.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub_v2.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gar.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gcr.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_ghcr.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_inline_scanner.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/container_registries_proxy_scanner.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/data_export_rules.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/datasources.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/entities.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/entities_containers.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/entities_images.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/entities_machine_details.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/entities_machines.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/entities_users.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/errors.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/feature_flags.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/http.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/inventory.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/inventory_aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/logging.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/lql.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/lql_delete.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/lql_execute.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/lql_validate.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/metrics.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/organization_info.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/policy.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/policy_exceptions.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/reader.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/report_rule_notification_types.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/report_rules.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/reports.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/reports_aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/reports_azure.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/reports_definitions.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/reports_distributions.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/reports_gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/resource_groups.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/schemas.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/team_members.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/user_profile.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_configs.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_configs_azure.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_configs_gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_recommendations.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_recommendations_aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_recommendations_azure.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_recommendations_gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_search_filters.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_suppressions.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_suppressions_aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_suppressions_azure.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_suppressions_gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities_software_packages.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/version.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go delete mode 100644 vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cdk/client/go/client.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk.pb.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/access_token.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/account.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ic.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssh.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssm.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_gcp-install-osl.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_install.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/agent_list.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_channel.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_close.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_comment.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_list.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_list_fixable.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_profiles.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_rules.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_details.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_events.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_integrations.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_investigation.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_related.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_timeline.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/api.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/awsiam.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cache.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cdk.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cli_state.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cli_unix.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cli_windows.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/cloud_account.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/compliance.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/compliance_aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/compliance_azure.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/compliance_gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/component.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/component_args.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/component_dev.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/configure.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/configure_switch_profile.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/container_registry.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/content_library.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/docs.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/emoji.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/emoji_unix.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/emoji_windows.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/errors.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/errors_lql.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/flags.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_controltower.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_eks_audit.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_azure.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_cloud_account.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_execute.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_gke.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_k8s.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/generate_oci.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/grpc.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/honeyvent.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_cloudwatch.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_govcloud.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_s3_channel.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_azure.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_cisco_webex.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_ctr_reg_limits.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_datadog.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_hub.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_v2.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_ecr.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_email.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gar.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_audit.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_channel.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcr.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_ghcr.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_inline_scanner.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_jira.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_microsoft_teams.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_new_relic_channel.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_oci.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_pagerduty.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_proxy_scanner.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_qradar_channel.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_service_now_channel.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_slack_channel.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_splunk.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_victorops.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/integration_webhook.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_create.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_delete.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_library.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_list.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_preview.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_show.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_sources.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_update.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/lql_validate.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/migration.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/outputs.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/package_manifest.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_create.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_delete.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_disable.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_enable.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_exceptions.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_library.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/policy_update.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/prompt.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_create.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_diff.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_revert.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_update.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_create.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_update.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/report_rules.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/resource_group_v2.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/resource_groups.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/root.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/suppressions.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_azure.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/table_render.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/team_members.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/telemetry.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/.version delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh.sig delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh.sig delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/version.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_assessments.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_registries.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_scan.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_show_assessments.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_gen_package_manifest.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_cves.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_hosts.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_scan_package_manifest.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_show_assessment.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vuln_html.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_container.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_host.go delete mode 100644 vendor/github.com/lacework/go-sdk/cli/cmd/vulnerabilty_exceptions.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/archive/detect.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/archive/gz.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/archive/tar.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/array/contains.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/array/join.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/array/sort.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/array/unique.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/cache/cache.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/capturer/capture_output.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/databox/blob.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/databox/box.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/failon/count_operation.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/file/file.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/format/secret.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/format/strings.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/intgguid/rand.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/lacework/server.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/pointer/bool.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/unique/strings.go delete mode 100644 vendor/github.com/lacework/go-sdk/internal/validate/email.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/helpers/helper.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/folders/folders.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/instances/instances.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/models/models.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/projects/projects.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/DESIGN.md delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/api_info.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/catalog.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/cdk_component.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/cdk_executable.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/component.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/dev_info.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/error.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/executable.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/host_info.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/http.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/library.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/minisign.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/staging.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/status.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sh delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sig delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sh delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sig delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sh delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sig delete mode 100644 vendor/github.com/lacework/go-sdk/lwcomponent/types.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwconfig/README.md delete mode 100644 vendor/github.com/lacework/go-sdk/lwconfig/config.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwdomain/README.md delete mode 100644 vendor/github.com/lacework/go-sdk/lwdomain/domain.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/aws/aws.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/aws_controltower/aws_controltower.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/aws_eks_audit/aws_eks_audit.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/azure/azure.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/constants.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gcp.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gke.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/gcp/service_account.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/hcl.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwgenerate/oci/oci.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwlogger/README.md delete mode 100644 vendor/github.com/lacework/go-sdk/lwlogger/logger.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwrunner/awsrunner.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwrunner/gcprunner.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwrunner/runner.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwrunner/ssh.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwseverity/severity.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwtime/README.md delete mode 100644 vendor/github.com/lacework/go-sdk/lwtime/epoch.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwtime/epochstring.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwtime/nanotime.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwtime/nattime.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwtime/reltime.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwtime/rfc1123z.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwtime/rfc3339.go delete mode 100644 vendor/github.com/lacework/go-sdk/lwupdater/README.md delete mode 100644 vendor/github.com/lacework/go-sdk/lwupdater/updater.go diff --git a/api/version.go b/api/version.go index 0c9b1c1b9..7b8f06b8c 100644 --- a/api/version.go +++ b/api/version.go @@ -1,5 +1,5 @@ // Code generated by: scripts/version_updater.sh -// File generated at: 20241030194552 +// File generated at: 20241030175518 // // <<< DO NOT EDIT >>> // diff --git a/go.mod b/go.mod index 37ea0bde0..a778edf3f 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/lacework/go-sdk/v2 +module github.com/lacework/go-sdk go 1.21 @@ -62,7 +62,6 @@ require ( github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.0 github.com/hashicorp/consul/sdk v0.13.1 - github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65 github.com/mattn/go-isatty v0.0.18 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/otiai10/copy v1.14.0 diff --git a/go.sum b/go.sum index 2cec564f6..2fcb558a6 100644 --- a/go.sum +++ b/go.sum @@ -355,8 +355,6 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kyokomi/emoji/v2 v2.2.12 h1:sSVA5nH9ebR3Zji1o31wu3yOwD1zKXQA2z0zUyeit60= github.com/kyokomi/emoji/v2 v2.2.12/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= -github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65 h1:A4LDKoyuC0fKknf7Nd6BM3MkFqzlbmjs0gXDPsH5szQ= -github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65/go.mod h1:l0kCskNExDs1E8fBfpaZeafC42pmKucdXn3nZO1iyLI= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= diff --git a/vendor/github.com/lacework/go-sdk/LICENSE b/vendor/github.com/lacework/go-sdk/LICENSE deleted file mode 100644 index 261eeb9e9..000000000 --- a/vendor/github.com/lacework/go-sdk/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/lacework/go-sdk/api/README.md b/vendor/github.com/lacework/go-sdk/api/README.md deleted file mode 100644 index 4e3267824..000000000 --- a/vendor/github.com/lacework/go-sdk/api/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# API Client - -A Golang API client for interacting with [Lacework APIs](https://docs.lacework.net/api/about-the-lacework-api). - -## Usage - -Download the library into your `$GOPATH`: - - $ go get github.com/lacework/go-sdk/api - -Import the library into your tool: - -```go -import "github.com/lacework/go-sdk/api" -``` - -## Requirements - -To interact with Lacework's API you need to have: - -1. A Lacework account -2. Either API access keys or token for authentication - -## Examples - -Create a new Lacework client that will automatically generate a new access token -from the provided set of API keys, then hit the `/api/v2/AlertChannels` endpoint -to list all available alert channels in your account: -```go -package main - -import ( - "fmt" - "log" - - "github.com/lacework/go-sdk/api" -) - -func main() { - lacework, err := api.NewClient("account", - api.WithTokenFromKeys("KEY", "SECRET"), - ) - if err != nil { - log.Fatal(err) - } - - alertChannels, err := lacework.V2.AlertChannels.List() - if err != nil { - log.Fatal(err) - } - - for _, channel := range alertChannels.Data { - fmt.Printf("Alert channel: %s\n", channel.Name) - } - // Output: - // - // Alert channel: DEFAULT EMAIL -} -``` - -Look at the [_examples/](_examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/aws.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/aws.json deleted file mode 100644 index 58e86a3c0..000000000 --- a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/aws.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "filters": { - "filter0": { - "field": "Account", - "operation": "EQUALS", - "values": [ - "123456789012" - ] - }, - "filter1": { - "field": "Resource Tag", - "operation": "INCLUDES", - "key": "Hostname", - "values": [ - "*" - ] - }, - "filter2": { - "field": "Region", - "operation": "STARTS_WITH", - "values": [ - "ap-south" - ] - } - }, - "expression": { - "operator": "AND", - "children": [ - { - "filterName": "filter0" - }, - { - "filterName": "filter1" - }, - { - "filterName": "filter2" - } - ] - } -} \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/azure.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/azure.json deleted file mode 100644 index a373c8ddf..000000000 --- a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/azure.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "filters": { - "filter0": { - "field": "Subscription ID", - "operation": "EQUALS", - "values": [ - "0fe75302-1906-45ec-bde1-79b76899dd74" - ] - }, - "filter1": { - "field": "Subscription Name", - "operation": "STARTS_WITH", - "values": [ - "prod" - ] - }, - "filter2": { - "field": "Tenant ID", - "operation": "EQUALS", - "values": [ - "b329d4bf-4587-4ccf-e132-84e7025fa22d" - ] - }, - "filter3": { - "field": "Tenant Name", - "operation": "INCLUDES", - "values": [ - "*" - ] - }, - "filter4": { - "field": "Resource Tag", - "operation": "EQUALS", - "key": "Env", - "values": [ - "dev" - ] - }, - "filter5": { - "field": "Region", - "operation": "EQUALS", - "values": [ - "westus2" - ] - } - }, - "expression": { - "operator": "AND", - "children": [ - { - "filterName": "filter0" - }, - { - "filterName": "filter1" - }, - { - "filterName": "filter2" - }, - { - "filterName": "filter3" - }, - { - "filterName": "filter4" - }, - { - "filterName": "filter5" - } - ] - } - } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/container.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/container.json deleted file mode 100644 index 768b49d98..000000000 --- a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/container.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "filters": { - "filter0": { - "field": "Image Tag", - "operation": "EQUALS", - "values": [ - "1.17.1" - ] - }, - "filter1": { - "field": "Container Label", - "operation": "INCLUDES", - "key": "app", - "values": [ - "*" - ] - }, - "filter2": { - "field": "Image Repo", - "operation": "EQUALS", - "values": [ - "parrotsec/core" - ] - }, - "filter3": { - "field": "Image Registry", - "operation": "EQUALS", - "values": [ - "k8s.gcr.io" - ] - } - }, - "expression": { - "operator": "AND", - "children": [ - { - "filterName": "filter0" - }, - { - "filterName": "filter1" - }, - { - "filterName": "filter2" - }, - { - "filterName": "filter3" - } - ] - } - } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/gcp.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/gcp.json deleted file mode 100644 index 9baedb611..000000000 --- a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/gcp.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "filters": { - "filter0": { - "field": "Project ID", - "operation": "EQUALS", - "values": [ - "abc-demo-project-123" - ] - }, - "filter1": { - "field": "Organization ID", - "operation": "EQUALS", - "values": [ - "123456789012" - ] - }, - "filter2": { - "field": "Organization Name", - "operation": "STARTS_WITH", - "values": [ - "somecompany" - ] - }, - "filter3": { - "field": "Folder", - "operation": "EQUALS", - "values": [ - "1234567890123" - ] - }, - "filter4": { - "field": "Resource Label", - "operation": "EQUALS", - "key": "Env", - "values": [ - "dev" - ] - }, - "filter5": { - "field": "Region", - "operation": "EQUALS", - "values": [ - "australia-southeast2" - ] - } - }, - "expression": { - "operator": "AND", - "children": [ - { - "filterName": "filter0" - }, - { - "filterName": "filter1" - }, - { - "filterName": "filter2" - }, - { - "filterName": "filter3" - }, - { - "filterName": "filter4" - }, - { - "filterName": "filter5" - } - ] - } - } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/kubernetes.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/kubernetes.json deleted file mode 100644 index a91d047c0..000000000 --- a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/kubernetes.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "filters": { - "filter1": { - "field": "AWS Account", - "operation": "EQUALS", - "values": [ - "123456789012" - ] - }, - "filter2": { - "field": "AWS Region", - "operation": "EQUALS", - "values": [ - "us-west-2" - ] - }, - "filter3": { - "field": "Cluster Name", - "operation": "EQUALS", - "values": [ - "*" - ] - }, - "filter4": { - "field": "Namespace", - "operation": "EQUALS", - "values": [ - "prod" - ] - } - }, - "expression": { - "operator": "OR", - "children": [ - { - "filterName": "filter1" - }, - { - "filterName": "filter2" - }, - { - "filterName": "filter3" - }, - { - "filterName": "filter4" - } - ] - } -} \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/machine.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/machine.json deleted file mode 100644 index ff31679c3..000000000 --- a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/machine.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "filters": { - "filter0": { - "field": "Machine Tag", - "operation": "EQUALS", - "key": "cluster", - "values": [ - "dev" - ] - } - }, - "expression": { - "operator": "OR", - "children": [ - { - "filterName": "filter0" - } - ] - } - } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/oci.json b/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/oci.json deleted file mode 100644 index bcab5eb71..000000000 --- a/vendor/github.com/lacework/go-sdk/api/_templates/resource_groups/oci.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "filters": { - "filter0": { - "field": "Compartment ID", - "operation": "EQUALS", - "values": [ - "ocid1.tenancy.oc1..abaaaabaqp6mzxi6z3xouvhawvsekntanafelw5vmwdjw5vqmgvlegcs2s6q" - ] - }, - "filter1": { - "field": "Compartment Name", - "operation": "INCLUDES", - "values": [ - "prod" - ] - }, - "filter2": { - "field": "Region", - "operation": "STARTS_WITH", - "values": [ - "us-" - ] - }, - "filter3": { - "field": "Resource Tag", - "operation": "EQUALS", - "key": "\"definedTags\".\"Oracle-Tags\".\"CreatedBy\"", - "values": [ - "default/my.email@somecompany.net" - ] - } - }, - "expression": { - "operator": "AND", - "children": [ - { - "filterName": "filter0" - }, - { - "filterName": "filter1" - }, - { - "filterName": "filter2" - }, - { - "filterName": "filter3" - } - ] - } - } \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/api/agent_access_tokens.go b/vendor/github.com/lacework/go-sdk/api/agent_access_tokens.go deleted file mode 100644 index e22d68bca..000000000 --- a/vendor/github.com/lacework/go-sdk/api/agent_access_tokens.go +++ /dev/null @@ -1,171 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "time" - - "github.com/pkg/errors" -) - -// AgentAccessTokensService is the service that interacts with -// the AgentAccessTokens schema from the Lacework APIv2 Server -type AgentAccessTokensService struct { - client *Client -} - -// List returns a list of Agent Access Tokens -func (svc *AgentAccessTokensService) List() (response AgentAccessTokensResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2AgentAccessTokens, nil, &response) - return -} - -// Create creates a single Agent Access Token -func (svc *AgentAccessTokensService) Create(alias, desc string) ( - response AgentAccessTokenResponse, - err error, -) { - if alias == "" { - err = errors.New("token alias is required") - return - } - - err = svc.client.RequestEncoderDecoder("POST", - apiV2AgentAccessTokens, - AgentAccessTokenRequest{ - TokenAlias: alias, - Enabled: 1, - Props: &AgentAccessTokenProps{ - Description: desc, - }, - }, - &response, - ) - return -} - -// Get returns an Agent Access Token with the matching ID (token) -func (svc *AgentAccessTokensService) Get(token string) ( - response AgentAccessTokenResponse, - err error, -) { - err = svc.client.RequestDecoder("GET", - fmt.Sprintf(apiV2AgentAccessTokenFromID, token), - nil, - &response, - ) - return -} - -// Update updates an Agent Access Token with the provided request data -func (svc *AgentAccessTokensService) Update(token string, data AgentAccessTokenRequest) ( - response AgentAccessTokenResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder("PATCH", - fmt.Sprintf(apiV2AgentAccessTokenFromID, token), - data, - &response, - ) - return -} - -// UpdateState updates only the state of an Agent Access Token (enable or disable) -func (svc *AgentAccessTokensService) UpdateState(token string, enable bool) ( - response AgentAccessTokenResponse, - err error, -) { - - request := AgentAccessTokenRequest{Enabled: 0} - if enable { - request.Enabled = 1 - } - err = svc.client.RequestEncoderDecoder("PATCH", - fmt.Sprintf(apiV2AgentAccessTokenFromID, token), - request, - &response, - ) - return -} - -// SearchAlias will search for an Agent Access Token that matches the provider token alias -func (svc *AgentAccessTokensService) SearchAlias(alias string) ( - response AgentAccessTokensResponse, - err error, -) { - - if alias == "" { - err = errors.New("specify a token alias to search") - return - } - err = svc.client.RequestEncoderDecoder("POST", - apiV2AgentAccessTokensSearch, - SearchFilter{ - Filters: []Filter{ - { - Field: "tokenAlias", - Expression: "eq", - Value: alias, - }, - }, - }, - &response, - ) - return -} - -type AgentAccessToken struct { - AccessToken string `json:"accessToken"` - CreatedTime time.Time `json:"createdTime"` - Props AgentAccessTokenProps `json:"props,omitempty"` - TokenAlias string `json:"tokenAlias"` - Enabled int `json:"tokenEnabled"` - Version string `json:"version"` -} - -func (t AgentAccessToken) State() bool { - return t.Enabled == 1 -} - -func (t AgentAccessToken) PrettyState() string { - if t.State() { - return "Enabled" - } - return "Disabled" -} - -type AgentAccessTokenProps struct { - CreatedTime time.Time `json:"createdTime,omitempty"` - Description string `json:"description,omitempty"` -} - -type AgentAccessTokenResponse struct { - Data AgentAccessToken `json:"data"` -} - -type AgentAccessTokensResponse struct { - Data []AgentAccessToken `json:"data"` -} - -type AgentAccessTokenRequest struct { - Enabled int `json:"tokenEnabled"` - TokenAlias string `json:"tokenAlias,omitempty"` - Props *AgentAccessTokenProps `json:"props,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/agent_info.go b/vendor/github.com/lacework/go-sdk/api/agent_info.go deleted file mode 100644 index 6b69370fc..000000000 --- a/vendor/github.com/lacework/go-sdk/api/agent_info.go +++ /dev/null @@ -1,100 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "time" -) - -// AgentInfoService is the service that interacts with -// the AgentInfo schema from the Lacework APIv2 Server -type AgentInfoService struct { - client *Client -} - -func (svc *AgentInfoService) Search(response interface{}, filters SearchFilter) error { - return svc.client.RequestEncoderDecoder("POST", apiV2AgentInfoSearch, filters, response) -} - -type AgentInfoResponse struct { - Data []AgentInfo `json:"data"` - Paging V2Pagination `json:"paging"` - - v2PageMetadata `json:"-"` -} - -// Fulfill Pageable interface (look at api/v2.go) -func (r AgentInfoResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *AgentInfoResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -type AgentInfo struct { - AgentVersion string `json:"agentVersion"` - CreatedTime time.Time `json:"createdTime"` - Hostname string `json:"hostname"` - IpAddr string `json:"ipAddr"` - LastUpdate time.Time `json:"lastUpdate"` - Mid int `json:"mid"` - Mode string `json:"mode"` - Os string `json:"os"` - Status string `json:"status"` - Tags struct { - // Shared Tags - Arch string `json:"arch,omitempty"` - ExternalIP string `json:"ExternalIp,omitempty"` - Hostname string `json:"Hostname,omitempty"` - InstanceID string `json:"InstanceId,omitempty"` - InternalIP string `json:"InternalIp,omitempty"` - LwTokenShort string `json:"LwTokenShort,omitempty"` - Os string `json:"os,omitempty"` - VMInstanceType string `json:"VmInstanceType,omitempty"` - VMProvider string `json:"VmProvider,omitempty"` - Zone string `json:"Zone,omitempty"` - - // AWS Tags - Account string `json:"Account,omitempty"` - AmiID string `json:"AmiId,omitempty"` - Name string `json:"Name,omitempty"` - SubnetID string `json:"SubnetId,omitempty"` - VpcID string `json:"VpcId,omitempty"` - - // GCP Tags - Cluster string `json:"Cluster,omitempty"` - ClusterLocation string `json:"cluster-location,omitempty"` - ClusterName string `json:"cluster-name,omitempty"` - ClusterUID string `json:"cluster-uid,omitempty"` - CreatedBy string `json:"created-by,omitempty"` - EnableOSLogin string `json:"enable-oslogin,omitempty"` - Env string `json:"Env,omitempty"` - GCEtags string `json:"GCEtags,omitempty"` - GCIEnsureGKEDocker string `json:"gci-ensure-gke-docker,omitempty"` - GCIUpdateStrategy string `json:"gci-update-strategy,omitempty"` - GoogleComputeEnablePCID string `json:"google-compute-enable-pcid,omitempty"` - InstanceName string `json:"InstanceName,omitempty"` - InstanceTemplate string `json:"InstanceTemplate,omitempty"` - KubeLabels string `json:"kube-labels,omitempty"` - LWKubernetesCluster string `json:"lw_KubernetesCluster,omitempty"` - NumericProjectID string `json:"NumericProjectId,omitempty"` - ProjectID string `json:"ProjectId,omitempty"` - } `json:"tags"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channel_datadog.go b/vendor/github.com/lacework/go-sdk/api/alert_channel_datadog.go deleted file mode 100644 index 31b1c524b..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channel_datadog.go +++ /dev/null @@ -1,90 +0,0 @@ -// -// Author:: Vatasha White () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "github.com/pkg/errors" - -const ( - // The list of valid inputs for DatadogSite field - DatadogSiteEu datadogSite = "eu" - DatadogSiteCom datadogSite = "com" - - // The list of valid inputs for DatadogService field - DatadogServiceLogsDetails datadogService = "Logs Detail" - DatadogServiceEventsSummary datadogService = "Events Summary" - DatadogServiceLogsSummary datadogService = "Logs Summary" -) - -// GetDatadog gets a single instance of a Datadog alert channel -// with the corresponding integration guid -func (svc *AlertChannelsService) GetDatadog(guid string) (response DatadogAlertChannelResponseV2, err error) { - err = svc.get(guid, &response) - return -} - -// UpdateDatadog updates a single instance of a Datadog integration on the Lacework server -func (svc *AlertChannelsService) UpdateDatadog(data AlertChannel) (response DatadogAlertChannelResponseV2, err error) { - err = svc.update(data.ID(), data, &response) - return -} - -// DatadogSite returns the datadogSite type for the corresponding string input -func DatadogSite(site string) (datadogSite, error) { - if val, ok := datadogSites[site]; ok { - return val, nil - } - return "", errors.Errorf("%v is not a valid Datadog Site", site) -} - -// DatadogService returns the datadogService type for the corresponding string input -func DatadogService(service string) (datadogService, error) { - if val, ok := datadogServices[service]; ok { - return val, nil - } - return "", errors.Errorf("%v is not a valid Datadog Service", service) -} - -type datadogSite string -type datadogService string - -type DatadogDataV2 struct { - ApiKey string `json:"apiKey"` - DatadogSite datadogSite `json:"datadogSite,omitempty"` - DatadogType datadogService `json:"datadogType,omitempty"` -} - -type DatadogAlertChannelV2 struct { - v2CommonIntegrationData - Data DatadogDataV2 `json:"data"` -} - -type DatadogAlertChannelResponseV2 struct { - Data DatadogAlertChannelV2 `json:"data"` -} - -var datadogSites = map[string]datadogSite{ - string(DatadogSiteEu): DatadogSiteEu, - string(DatadogSiteCom): DatadogSiteCom, -} - -var datadogServices = map[string]datadogService{ - string(DatadogServiceLogsDetails): DatadogServiceLogsDetails, - string(DatadogServiceEventsSummary): DatadogServiceEventsSummary, - string(DatadogServiceLogsSummary): DatadogServiceLogsSummary, -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels.go b/vendor/github.com/lacework/go-sdk/api/alert_channels.go deleted file mode 100644 index 9c26df485..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels.go +++ /dev/null @@ -1,233 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/pkg/errors" -) - -// AlertChannelsService is the service that interacts with -// the AlertChannels schema from the Lacework APIv2 Server -type AlertChannelsService struct { - client *Client -} - -// NewAlertChannel returns an instance of the AlertChannelRaw struct with the -// provided Alert Channel integration type, name and raw data as an interface{}. -// -// NOTE: This function must be used by any Alert Channel type. -// -// Basic usage: Initialize a new EmailUserAlertChannel struct, then -// -// use the new instance to do CRUD operations -// -// client, err := api.NewClient("account") -// if err != nil { -// return err -// } -// -// emailAlertChan := api.NewAlertChannel("foo", -// api.EmailUserAlertChannelType, -// api.EmailUserData{ -// ChannelProps: api.EmailUserChannelProps{ -// Recipients: []string{"name@example.com"}, -// }, -// }, -// ) -// -// client.V2.AlertChannels.Create(emailAlertChan) -func NewAlertChannel(name string, iType alertChannelType, data interface{}) AlertChannelRaw { - return AlertChannelRaw{ - v2CommonIntegrationData: v2CommonIntegrationData{ - Name: name, - Type: iType.String(), - Enabled: 1, - }, - Data: data, - } -} - -// AlertChannel is an interface that helps us implement a few functions -// that any Alert Channel might use, there are some cases, like during -// Update, where we need to get the ID of the Alert Channel and its type, -// this will allow users to pass any Alert Channel that implements these -// methods -type AlertChannel interface { - ID() string - AlertChannelType() alertChannelType -} - -type alertChannelType int - -const ( - // NoneAlertChannelType type that defines a non-existing Alert Channel integration - NoneAlertChannelType alertChannelType = iota - EmailUserAlertChannelType - SlackChannelAlertChannelType - AwsS3AlertChannelType - CloudwatchEbAlertChannelType - DatadogAlertChannelType - WebhookAlertChannelType - VictorOpsAlertChannelType - CiscoSparkWebhookAlertChannelType - MicrosoftTeamsAlertChannelType - GcpPubSubAlertChannelType - SplunkHecAlertChannelType - ServiceNowRestAlertChannelType - NewRelicInsightsAlertChannelType - PagerDutyApiAlertChannelType - IbmQRadarAlertChannelType - JiraAlertChannelType -) - -// AlertChannelTypes is the list of available Alert Channel integration types -var AlertChannelTypes = map[alertChannelType]string{ - NoneAlertChannelType: "None", - EmailUserAlertChannelType: "EmailUser", - SlackChannelAlertChannelType: "SlackChannel", - AwsS3AlertChannelType: "AwsS3", - CloudwatchEbAlertChannelType: "CloudwatchEb", - DatadogAlertChannelType: "Datadog", - WebhookAlertChannelType: "Webhook", - VictorOpsAlertChannelType: "VictorOps", - CiscoSparkWebhookAlertChannelType: "CiscoSparkWebhook", - MicrosoftTeamsAlertChannelType: "MicrosoftTeams", - GcpPubSubAlertChannelType: "GcpPubsub", - SplunkHecAlertChannelType: "SplunkHec", - ServiceNowRestAlertChannelType: "ServiceNowRest", - NewRelicInsightsAlertChannelType: "NewRelicInsights", - PagerDutyApiAlertChannelType: "PagerDutyApi", - IbmQRadarAlertChannelType: "IbmQradar", - JiraAlertChannelType: "Jira", -} - -// String returns the string representation of a Alert Channel integration type -func (i alertChannelType) String() string { - return AlertChannelTypes[i] -} - -// FindAlertChannelType looks up inside the list of available alert channel types -// the matching type from the provided string, if none, returns NoneAlertChannelType -func FindAlertChannelType(alertChannel string) (alertChannelType, bool) { - for cType, cStr := range AlertChannelTypes { - if cStr == alertChannel { - return cType, true - } - } - return NoneAlertChannelType, false -} - -// List returns a list of Alert Channel integrations -func (svc *AlertChannelsService) List() (response AlertChannelsResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2AlertChannels, nil, &response) - return -} - -// Create creates a single Alert Channel integration -func (svc *AlertChannelsService) Create(integration AlertChannelRaw) ( - response AlertChannelResponse, - err error, -) { - err = svc.create(integration, &response) - return -} - -// Delete deletes a Alert Channel integration that matches the provided guid -func (svc *AlertChannelsService) Delete(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2AlertChannelFromGUID, guid), - nil, - nil, - ) -} - -// Test tests an Alert Channel integration that matches the provided guid -func (svc *AlertChannelsService) Test(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - apiPath := fmt.Sprintf(apiV2AlertChannelTest, guid) - return svc.client.RequestDecoder("POST", apiPath, nil, nil) -} - -// Get returns a raw response of the Alert Channel with the matching integration guid. -// -// To return a more specific Go struct of a Alert Channel integration, use the proper -// method such as GetEmailUser() where the function name is composed by: -// -// Get(guid) -// -// Where is the Alert Channel integration type. -func (svc *AlertChannelsService) Get(guid string, response interface{}) error { - return svc.get(guid, &response) -} - -type AlertChannelRaw struct { - v2CommonIntegrationData - Data interface{} `json:"data,omitempty"` -} - -func (alert AlertChannelRaw) GetData() any { - return alert.Data -} - -func (alert AlertChannelRaw) GetCommon() v2CommonIntegrationData { - return alert.v2CommonIntegrationData -} - -func (alert AlertChannelRaw) AlertChannelType() alertChannelType { - t, _ := FindAlertChannelType(alert.Type) - return t -} - -type AlertChannelResponse struct { - Data AlertChannelRaw `json:"data"` -} - -type AlertChannelsResponse struct { - Data []AlertChannelRaw `json:"data"` -} - -func (svc *AlertChannelsService) create(data interface{}, response interface{}) error { - return svc.client.RequestEncoderDecoder("POST", apiV2AlertChannels, data, response) -} - -func (svc *AlertChannelsService) get(guid string, response interface{}) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - apiPath := fmt.Sprintf(apiV2AlertChannelFromGUID, guid) - return svc.client.RequestDecoder("GET", apiPath, nil, response) -} - -func (svc *AlertChannelsService) update(guid string, data interface{}, response interface{}) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - apiPath := fmt.Sprintf(apiV2AlertChannelFromGUID, guid) - return svc.client.RequestEncoderDecoder("PATCH", apiPath, data, response) -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_cloudwatch.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_cloudwatch.go deleted file mode 100644 index 65ed9971d..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_cloudwatch.go +++ /dev/null @@ -1,51 +0,0 @@ -// -// Author:: Vatasha White () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetCloudwatchEb gets a single instance of an AWS Cloudwatch alert channel -// with the corresponding integration guid -func (svc *AlertChannelsService) GetCloudwatchEb(guid string) ( - response CloudwatchEbAlertChannelResponseV2, err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateCloudwatchEb Update AWSCloudWatch updates a single instance of an AWS -// cloudwatch integration on the Lacework server -func (svc *AlertChannelsService) UpdateCloudwatchEb(data AlertChannel) ( - response CloudwatchEbAlertChannelResponseV2, err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type CloudwatchEbDataV2 struct { - EventBusArn string `json:"eventBusArn"` - IssueGrouping string `json:"issueGrouping,omitempty"` -} - -type CloudwatchEbAlertChannelV2 struct { - v2CommonIntegrationData - Data CloudwatchEbDataV2 `json:"data"` -} - -type CloudwatchEbAlertChannelResponseV2 struct { - Data CloudwatchEbAlertChannelV2 `json:"data"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_s3.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_s3.go deleted file mode 100644 index 9e955d51c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_aws_s3.go +++ /dev/null @@ -1,57 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAwsS3 gets a single AwsS3 alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetAwsS3(guid string) ( - response AwsS3AlertChannelResponseV2, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAwsS3 updates a single AwsS3 integration on the Lacework Server -func (svc *AlertChannelsService) UpdateAwsS3(data AlertChannel) ( - response AwsS3AlertChannelResponseV2, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsS3AlertChannelResponseV2 struct { - Data AwsS3AlertChannelV2 `json:"data"` -} - -type AwsS3AlertChannelV2 struct { - v2CommonIntegrationData - Data AwsS3DataV2 `json:"data"` -} - -type AwsS3DataV2 struct { - Credentials AwsS3Credentials `json:"s3CrossAccountCredentials"` -} - -type AwsS3Credentials struct { - RoleArn string `json:"roleArn"` - ExternalID string `json:"externalId"` - BucketArn string `json:"bucketArn"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_cisco_spark_webhook.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_cisco_spark_webhook.go deleted file mode 100644 index 3b0af6692..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_cisco_spark_webhook.go +++ /dev/null @@ -1,49 +0,0 @@ -// -// Author:: Vatasha White () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetCiscoSparkWebhook gets a single instance of a Cisco Spark webhook alert channel -// with the corresponding integration guid -func (svc *AlertChannelsService) GetCiscoSparkWebhook(guid string) ( - response CiscoSparkWebhookAlertChannelResponseV2, err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateCiscoSparkWebhook updates a single instance of Cisco Spark webhook integration on the Lacework server -func (svc *AlertChannelsService) UpdateCiscoSparkWebhook(data AlertChannel) ( - response CiscoSparkWebhookAlertChannelResponseV2, err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type CiscoSparkWebhookDataV2 struct { - Webhook string `json:"webhook"` -} - -type CiscoSparkWebhookAlertChannelV2 struct { - v2CommonIntegrationData - Data CiscoSparkWebhookDataV2 `json:"data"` -} - -type CiscoSparkWebhookAlertChannelResponseV2 struct { - Data CiscoSparkWebhookAlertChannelV2 `json:"data"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_email_user.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_email_user.go deleted file mode 100644 index a68ac7c26..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_email_user.go +++ /dev/null @@ -1,123 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/json" - "strings" -) - -// GetEmailUser gets a single EmailUser alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetEmailUser(guid string) ( - response EmailUserAlertChannelResponse, - err error, -) { - - // by default, expect the correct response, if not, try the workaround - err = svc.get(guid, &response) - if err == nil { - return - } - - // Workaround from APIv2 - // Bug: https://lacework.atlassian.net/browse/RAIN-20070 - // - // This means that the response.Data.Data.ChannelProps.Recipients is a 'string' - // instead of '[]string'. We will try to deserialize and cast to correct response - var getResponse emailUserGetAlertChannelResponse - err = svc.get(guid, &getResponse) - if err != nil { - return - } - - // convert GET response to a consistent response - response, err = convertGetEmailUserAlertChannelResponse(getResponse) - return -} - -// UpdateEmailUser updates a single EmailUser integration on the Lacework Server -func (svc *AlertChannelsService) UpdateEmailUser(data AlertChannel) ( - response EmailUserAlertChannelResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type EmailUserAlertChannelResponse struct { - Data EmailUserIntegration `json:"data"` -} - -type EmailUserIntegration struct { - v2CommonIntegrationData - Data EmailUserData `json:"data"` -} - -type EmailUserData struct { - ChannelProps EmailUserChannelProps `json:"channelProps"` - NotificationTypes struct { - Properties interface{} `json:"properties,omitempty"` - } `json:"notificationTypes"` -} - -type EmailUserChannelProps struct { - Recipients []string `json:"recipients"` -} - -// Workaround from APIv2 -// Bug: https://lacework.atlassian.net/browse/RAIN-20070 -type emailUserGetData struct { - ChannelProps struct { - Recipients interface{} `json:"recipients"` - } `json:"channelProps"` - NotificationTypes struct { - Properties interface{} `json:"properties,omitempty"` - } `json:"notificationTypes"` -} -type emailUserGetIntegration struct { - v2CommonIntegrationData - Data emailUserGetData `json:"data"` -} -type emailUserGetAlertChannelResponse struct { - Data emailUserGetIntegration `json:"data"` -} - -func convertGetEmailUserAlertChannelResponse( - res emailUserGetAlertChannelResponse) (EmailUserAlertChannelResponse, error) { - - recipientsString, ok := res.Data.Data.ChannelProps.Recipients.(string) - if ok { - // deserialize string - res.Data.Data.ChannelProps.Recipients = strings.Split(recipientsString, ",") - } - - return castEmailUserAlertChannelResponse(res) -} - -func castEmailUserAlertChannelResponse( - res interface{}) (r EmailUserAlertChannelResponse, err error) { - var j []byte - j, err = json.Marshal(res) - if err != nil { - return - } - err = json.Unmarshal(j, &r) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_gcp_pub_sub.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_gcp_pub_sub.go deleted file mode 100644 index 5f35c4703..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_gcp_pub_sub.go +++ /dev/null @@ -1,56 +0,0 @@ -// -// Author:: Vatasha White () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetGcpPubSub gets a single instance of a GCP Pub Sub alert channel with the corresponding guid -func (svc *AlertChannelsService) GetGcpPubSub(guid string) (response GcpPubSubAlertChannelResponseV2, err error) { - err = svc.get(guid, &response) - return -} - -// UpdateGcpPubSub updates a single instance of GCP Pub Sub integration on the Lacework server -func (svc *AlertChannelsService) UpdateGcpPubSub(data AlertChannel) ( - response GcpPubSubAlertChannelResponseV2, err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type GcpPubSubDataV2 struct { - Credentials GcpPubSubCredentials `json:"credentials"` - IssueGrouping string `json:"issueGrouping"` - ProjectID string `json:"projectId"` - TopicID string `json:"topicId"` -} - -type GcpPubSubAlertChannelV2 struct { - v2CommonIntegrationData - Data GcpPubSubDataV2 `json:"data"` -} - -type GcpPubSubAlertChannelResponseV2 struct { - Data GcpPubSubAlertChannelV2 `json:"data"` -} - -type GcpPubSubCredentials struct { - ClientEmail string `json:"clientEmail"` - ClientID string `json:"clientId"` - PrivateKey string `json:"privateKey"` - PrivateKeyID string `json:"privateKeyId"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_ibm_qradar.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_ibm_qradar.go deleted file mode 100644 index 4c74ed0e1..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_ibm_qradar.go +++ /dev/null @@ -1,75 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "github.com/pkg/errors" - -const ( - // The list of valid inputs for QRadar Communication Type field - QRadarCommHttps qradarComm = "HTTPS" - QRadarCommHttpsSelfSigned qradarComm = "HTTPS Self Signed Cert" -) - -var qradarCommTypes = map[string]qradarComm{ - string(QRadarCommHttps): QRadarCommHttps, - string(QRadarCommHttpsSelfSigned): QRadarCommHttpsSelfSigned, -} - -// GetIbmQRadar gets a single IbmQRadar alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetIbmQRadar(guid string) ( - response IbmQRadarAlertChannelResponseV2, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateIbmQRadar updates a single IbmQRadar integration on the Lacework Server -func (svc *AlertChannelsService) UpdateIbmQRadar(data AlertChannel) ( - response IbmQRadarAlertChannelResponseV2, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type IbmQRadarAlertChannelResponseV2 struct { - Data IbmQRadarAlertChannelV2 `json:"data"` -} - -type IbmQRadarAlertChannelV2 struct { - v2CommonIntegrationData - Data IbmQRadarDataV2 `json:"data"` -} -type qradarComm string - -// QRadarComm returns the qradarComm type for the corresponding string input -func QRadarComm(site string) (qradarComm, error) { - if val, ok := qradarCommTypes[site]; ok { - return val, nil - } - return "", errors.Errorf("%v is not a valid QRadar communication type", site) -} - -type IbmQRadarDataV2 struct { - QRadarCommType qradarComm `json:"qradarCommType"` - HostURL string `json:"qradarHostUrl"` - HostPort int `json:"qradarHostPort,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_jira_cloud_server.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_jira_cloud_server.go deleted file mode 100644 index ef3e2021e..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_jira_cloud_server.go +++ /dev/null @@ -1,110 +0,0 @@ -// -// Author:: Vatasha White () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/base64" - "fmt" - "strings" -) - -type jiraIssueGrouping int - -const ( - NoneJiraIssueGrouping jiraIssueGrouping = iota - EventsJiraIssueGrouping - ResourcesJiraIssueGrouping -) - -var JiraIssueGroupings = map[jiraIssueGrouping]string{ - NoneJiraIssueGrouping: "", - EventsJiraIssueGrouping: "Events", - ResourcesJiraIssueGrouping: "Resources", -} - -var JiraIssueGroupingsSurvey = map[string]jiraIssueGrouping{ - "None": NoneJiraIssueGrouping, - "Events": EventsJiraIssueGrouping, - "Resources": ResourcesJiraIssueGrouping, -} - -func (i jiraIssueGrouping) String() string { - return JiraIssueGroupings[i] -} - -const ( - BidirectionalJiraConfiguration = "Bidirectional" - JiraCloudAlertType = "JIRA_CLOUD" - JiraServerAlertType = "JIRA_SERVER" -) - -// GetJira gets a single instance of a Jira Cloud or Jira Server alert channel with the corresponding guid -func (svc *AlertChannelsService) GetJira(guid string) (response JiraAlertChannelResponseV2, err error) { - err = svc.get(guid, &response) - return -} - -// UpdateJira updates a single instance of a Jira Cloud or Jira Server integration on the Lacework server -func (svc *AlertChannelsService) UpdateJira(data AlertChannel) (response JiraAlertChannelResponseV2, err error) { - err = svc.update(data.ID(), data, &response) - return -} - -type JiraDataV2 struct { - ApiToken string `json:"apiToken,omitempty"` // used for Jira Cloud - CustomTemplateFile string `json:"customTemplateFile,omitempty"` - IssueGrouping string `json:"issueGrouping,omitempty"` - IssueType string `json:"issueType"` - JiraType string `json:"jiraType"` - JiraUrl string `json:"jiraUrl"` - ProjectID string `json:"projectId"` - Username string `json:"username"` - Password string `json:"password,omitempty"` // used for Jira Server - Configuration string `json:"bidirectionalConfig,omitempty"` // used for bidirectional integration -} - -type JiraAlertChannelV2 struct { - v2CommonIntegrationData - Data JiraDataV2 `json:"data"` -} - -type JiraAlertChannelResponseV2 struct { - Data JiraAlertChannelV2 `json:"data"` -} - -func (jira *JiraDataV2) EncodeCustomTemplateFile(template string) { - encodedTemplate := base64.StdEncoding.EncodeToString([]byte(template)) - jira.CustomTemplateFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedTemplate) -} - -func (jira *JiraDataV2) DecodeCustomTemplateFile() (string, error) { - if len(jira.CustomTemplateFile) == 0 { - return "", nil - } - - var ( - b64 = strings.Split(jira.CustomTemplateFile, ",") - raw, err = base64.StdEncoding.DecodeString(b64[1]) - ) - if err != nil { - return "", err - } - - return string(raw), nil -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_microsoft_teams.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_microsoft_teams.go deleted file mode 100644 index eccfcd649..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_microsoft_teams.go +++ /dev/null @@ -1,49 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetMicrosoftTeams gets a single instance of a MicrosoftTeams alert channel -// with the corresponding integration guid -func (svc *AlertChannelsService) GetMicrosoftTeams(guid string) ( - response MicrosoftTeamsAlertChannelResponseV2, err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateMicrosoftTeams updates a single instance of a MicrosoftTeams integration on the Lacework server -func (svc *AlertChannelsService) UpdateMicrosoftTeams(data AlertChannel) ( - response MicrosoftTeamsAlertChannelResponseV2, err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type MicrosoftTeamsData struct { - TeamsURL string `json:"teamsUrl"` -} - -type MicrosoftTeamsAlertChannelV2 struct { - v2CommonIntegrationData - Data MicrosoftTeamsData `json:"data"` -} - -type MicrosoftTeamsAlertChannelResponseV2 struct { - Data MicrosoftTeamsAlertChannelV2 `json:"data"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_new_relic.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_new_relic.go deleted file mode 100644 index d0866657c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_new_relic.go +++ /dev/null @@ -1,52 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetNewRelicInsights gets a single NewRelic alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetNewRelicInsights(guid string) ( - response NewRelicInsightsAlertChannelResponseV2, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateNewRelicInsights updates a single NewRelic integration on the Lacework Server -func (svc *AlertChannelsService) UpdateNewRelicInsights(data AlertChannel) ( - response NewRelicInsightsAlertChannelResponseV2, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type NewRelicInsightsAlertChannelResponseV2 struct { - Data NewRelicInsightsAlertChannelV2 `json:"data"` -} - -type NewRelicInsightsAlertChannelV2 struct { - v2CommonIntegrationData - Data NewRelicInsightsDataV2 `json:"data"` -} - -type NewRelicInsightsDataV2 struct { - AccountID int `json:"accountId"` - InsertKey string `json:"insertKey"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_pager_duty.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_pager_duty.go deleted file mode 100644 index c0b73a5b8..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_pager_duty.go +++ /dev/null @@ -1,51 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetPagerDutyApi gets a single PagerDuty alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetPagerDutyApi(guid string) ( - response PagerDutyApiAlertChannelResponseV2, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdatePagerDutyApi updates a single PagerDuty integration on the Lacework Server -func (svc *AlertChannelsService) UpdatePagerDutyApi(data AlertChannel) ( - response PagerDutyApiAlertChannelResponseV2, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type PagerDutyApiAlertChannelResponseV2 struct { - Data PagerDutyApiAlertChannelV2 `json:"data"` -} - -type PagerDutyApiAlertChannelV2 struct { - v2CommonIntegrationData - Data PagerDutyApiDataV2 `json:"data"` -} - -type PagerDutyApiDataV2 struct { - IntegrationKey string `json:"apiIntgKey"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_service_now_rest.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_service_now_rest.go deleted file mode 100644 index 95e6fcbdf..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_service_now_rest.go +++ /dev/null @@ -1,82 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/base64" - "fmt" - "strings" -) - -// GetServiceNowRest gets a single ServiceNowRest alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetServiceNowRest(guid string) ( - response ServiceNowRestAlertChannelResponseV2, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateServiceNowRest updates a single ServiceNowRest integration on the Lacework Server -func (svc *AlertChannelsService) UpdateServiceNowRest(data AlertChannel) ( - response ServiceNowRestAlertChannelResponseV2, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -func (snow *ServiceNowRestDataV2) EncodeCustomTemplateFile(template string) { - encodedTemplate := base64.StdEncoding.EncodeToString([]byte(template)) - snow.CustomTemplateFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedTemplate) -} - -func (snow *ServiceNowRestDataV2) DecodeCustomTemplateFile() (string, error) { - if len(snow.CustomTemplateFile) == 0 { - return "", nil - } - - var ( - b64 = strings.Split(snow.CustomTemplateFile, ",") - raw, err = base64.StdEncoding.DecodeString(b64[1]) - ) - if err != nil { - return "", err - } - - return string(raw), nil -} - -type ServiceNowRestAlertChannelResponseV2 struct { - Data ServiceNowRestAlertChannelV2 `json:"data"` -} - -type ServiceNowRestAlertChannelV2 struct { - v2CommonIntegrationData - Data ServiceNowRestDataV2 `json:"data"` -} - -type ServiceNowRestDataV2 struct { - Username string `json:"userName"` - Password string `json:"password"` - InstanceURL string `json:"instanceUrl"` - CustomTemplateFile string `json:"customTemplateFile,omitempty"` - IssueGrouping string `json:"issueGrouping,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_slack_channel.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_slack_channel.go deleted file mode 100644 index dec0668f3..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_slack_channel.go +++ /dev/null @@ -1,51 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetSlackChannel gets a single SlackChannel alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetSlackChannel(guid string) ( - response SlackChannelAlertChannelResponseV2, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateSlackChannel updates a single SlackChannel integration on the Lacework Server -func (svc *AlertChannelsService) UpdateSlackChannel(data AlertChannel) ( - response SlackChannelAlertChannelResponseV2, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type SlackChannelAlertChannelResponseV2 struct { - Data SlackChannelAlertChannelV2 `json:"data"` -} - -type SlackChannelAlertChannelV2 struct { - v2CommonIntegrationData - Data SlackChannelDataV2 `json:"data"` -} - -type SlackChannelDataV2 struct { - SlackUrl string `json:"slackUrl"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_splunk.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_splunk.go deleted file mode 100644 index 553e1d654..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_splunk.go +++ /dev/null @@ -1,61 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetSplunkHec gets a single Splunk alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetSplunkHec(guid string) ( - response SplunkHecAlertChannelResponseV2, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateSplunkHec updates a single Splunk integration on the Lacework Server -func (svc *AlertChannelsService) UpdateSplunkHec(data AlertChannel) ( - response SplunkHecAlertChannelResponseV2, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type SplunkHecAlertChannelResponseV2 struct { - Data SplunkHecAlertChannelV2 `json:"data"` -} - -type SplunkHecAlertChannelV2 struct { - v2CommonIntegrationData - Data SplunkHecDataV2 `json:"data"` -} - -type SplunkHecDataV2 struct { - HecToken string `json:"hecToken"` - Channel string `json:"channel,omitempty"` - Host string `json:"host"` - Port int `json:"port"` - Ssl bool `json:"ssl"` - EventData SplunkHecEventDataV2 `json:"eventData"` -} - -type SplunkHecEventDataV2 struct { - Index string `json:"index"` - Source string `json:"source"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_victorops.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_victorops.go deleted file mode 100644 index 259ae234b..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_victorops.go +++ /dev/null @@ -1,51 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetVictorOps gets a single VictorOps alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetVictorOps(guid string) ( - response VictorOpsAlertChannelResponseV2, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateVictorOps updates a single VictorOps integration on the Lacework Server -func (svc *AlertChannelsService) UpdateVictorOps(data AlertChannel) ( - response VictorOpsAlertChannelResponseV2, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type VictorOpsAlertChannelResponseV2 struct { - Data VictorOpsAlertChannelV2 `json:"data"` -} - -type VictorOpsAlertChannelV2 struct { - v2CommonIntegrationData - Data VictorOpsDataV2 `json:"data"` -} - -type VictorOpsDataV2 struct { - Url string `json:"intgUrl"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_channels_webhook.go b/vendor/github.com/lacework/go-sdk/api/alert_channels_webhook.go deleted file mode 100644 index c46b42902..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_channels_webhook.go +++ /dev/null @@ -1,51 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetWebhook gets a single Webhook alert channel matching the -// provided integration guid -func (svc *AlertChannelsService) GetWebhook(guid string) ( - response WebhookAlertChannelResponseV2, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateWebhook updates a single Webhook integration on the Lacework Server -func (svc *AlertChannelsService) UpdateWebhook(data AlertChannel) ( - response WebhookAlertChannelResponseV2, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type WebhookAlertChannelResponseV2 struct { - Data WebhookAlertChannelV2 `json:"data"` -} - -type WebhookAlertChannelV2 struct { - v2CommonIntegrationData - Data WebhookDataV2 `json:"data"` -} - -type WebhookDataV2 struct { - WebhookUrl string `json:"webhookUrl"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_profiles.go b/vendor/github.com/lacework/go-sdk/api/alert_profiles.go deleted file mode 100644 index 3b74e8796..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_profiles.go +++ /dev/null @@ -1,158 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/pkg/errors" -) - -type v2alertProfilesService struct { - client *Client - Profiles *alertProfilesService - Templates *alertTemplatesService -} - -func NewV2AlertProfilesService(c *Client) *v2alertProfilesService { - return &v2alertProfilesService{c, - &alertProfilesService{c}, - &alertTemplatesService{c}, - } -} - -// AlertProfilesService is the service that interacts with -// the AlertProfiles schema from the Lacework APIv2 Server -type alertProfilesService struct { - client *Client -} - -// NewAlertProfile returns an instance of the AlertProfileConfig struct -// -// Basic usage: Initialize a new AlertProfileConfig struct, then -// -// use the new instance to do CRUD operations -// -// client, err := api.NewClient("account") -// if err != nil { -// return err -// } -// -// alertProfile := api.NewAlertProfile( -// "CUSTOM_PROFILE_NAME", -// "LW_HE_FILES_DEFAULT_PROFILE" -// []api.AlertTemplate{{ -// ... -// } -// }, -// ) -// -// client.V2.Alert.Profiles.Create(AlertProfile) -func NewAlertProfile(id string, extends string, alerts []AlertTemplate) AlertProfileConfig { - profile := AlertProfileConfig{ - Guid: id, - Extends: extends, - Alerts: alerts, - } - return profile -} - -// List returns a list of Alert Profiles -func (svc *alertProfilesService) List() (response AlertProfilesResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2AlertProfiles, nil, &response) - return -} - -// Create creates a single Alert Profile -func (svc *alertProfilesService) Create(profile AlertProfileConfig) ( - response AlertProfileResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder("POST", apiV2AlertProfiles, profile, &response) - return -} - -// Delete deletes a Alert Profile that matches the provided guid -func (svc *alertProfilesService) Delete(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2AlertProfileFromGUID, guid), - nil, - nil, - ) -} - -// Update updates a single Alert Profile of the provided guid. -func (svc *alertProfilesService) Update(guid string, data []AlertTemplate) ( - response AlertProfileResponse, - err error, -) { - if guid == "" { - err = errors.New("specify a Guid") - return - } - body := alertTemplatesUpdate{data} - apiPath := fmt.Sprintf(apiV2AlertProfileFromGUID, guid) - err = svc.client.RequestEncoderDecoder("PATCH", apiPath, body, &response) - return -} - -// Get returns a raw response of the Alert Profile with the matching guid. -func (svc *alertProfilesService) Get(guid string, response interface{}) error { - if guid == "" { - return errors.New("specify a Guid") - } - apiPath := fmt.Sprintf(apiV2AlertProfileFromGUID, guid) - return svc.client.RequestDecoder("GET", apiPath, nil, &response) -} - -type AlertProfile struct { - Guid string `json:"alertProfileId,omitempty" yaml:"alertProfileId,omitempty"` - Extends string `json:"extends" yaml:"extends"` - Fields []AlertProfileField `json:"fields,omitempty" yaml:"fields,omitempty"` - DescriptionKeys []AlertProfileDescriptionKeys `json:"descriptionKeys,omitempty" yaml:"descriptionKeys,omitempty"` - Alerts []AlertTemplate `json:"alerts" yaml:"alerts"` -} - -type AlertProfileConfig struct { - Guid string `json:"alertProfileId" yaml:"alertProfileId"` - Extends string `json:"extends" yaml:"extends"` - Alerts []AlertTemplate `json:"alerts" yaml:"alerts"` -} - -type AlertProfileField struct { - Name string `json:"name" yaml:"name"` -} - -type AlertProfileDescriptionKeys struct { - Name string `json:"name" yaml:"name"` - Spec string `json:"spec" yaml:"spec"` -} - -type AlertProfileResponse struct { - Data AlertProfile `json:"data" yaml:"data"` -} - -type AlertProfilesResponse struct { - Data []AlertProfile `json:"data" yaml:"data"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_rules.go b/vendor/github.com/lacework/go-sdk/api/alert_rules.go deleted file mode 100644 index 8cc28651f..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_rules.go +++ /dev/null @@ -1,314 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" -) - -// AlertRulesService is the service that interacts with -// the AlertRules schema from the Lacework APIv2 Server -type AlertRulesService struct { - client *Client -} - -// Valid inputs for AlertRule Source property -var AlertRuleSources = []string{"Agent", "AWS", "Azure", "GCP", "K8s", "OCI"} - -// Valid inputs for AlertRule Categories property -var AlertRuleCategories = []string{"Anomaly", "Policy", "Composite"} - -// Valid inputs for AlertRule SubCategories property -var AlertRuleSubCategories = []string{ - "Compliance", - "Application", - "Cloud Activity", - "File", - "Machine", - "User", - "Platform", - "Kubernetes Activity", - "Registry", - "SystemCall", - "Host Vulnerability", - "Container Vulnerability", - "Threat Intel", - // Deprecated eventCategory values - "App", - "Cloud", - "K8sActivity", -} - -type alertRuleSeverity int - -type AlertRuleSeverities []alertRuleSeverity - -const AlertRuleEventType = "Event" - -func (sevs AlertRuleSeverities) toInt() []int { - var res []int - for _, i := range sevs { - res = append(res, int(i)) - } - return res -} - -func (sevs AlertRuleSeverities) ToStringSlice() []string { - var res []string - for _, i := range sevs { - switch i { - case AlertRuleSeverityCritical: - res = append(res, "Critical") - case AlertRuleSeverityHigh: - res = append(res, "High") - case AlertRuleSeverityMedium: - res = append(res, "Medium") - case AlertRuleSeverityLow: - res = append(res, "Low") - case AlertRuleSeverityInfo: - res = append(res, "Info") - default: - continue - } - } - return res -} - -func NewAlertRuleSeverities(sevSlice []string) AlertRuleSeverities { - var res AlertRuleSeverities - for _, i := range sevSlice { - sev := convertSeverity(i) - if sev != AlertRuleSeverityUnknown { - res = append(res, sev) - } - } - return res -} - -func NewAlertRuleSeveritiesFromIntSlice(sevSlice []int) AlertRuleSeverities { - var res AlertRuleSeverities - for _, i := range sevSlice { - sev := convertSeverityInt(i) - if sev != AlertRuleSeverityUnknown { - res = append(res, sev) - } - } - return res -} - -func convertSeverity(sev string) alertRuleSeverity { - switch strings.ToLower(sev) { - case "critical": - return AlertRuleSeverityCritical - case "high": - return AlertRuleSeverityHigh - case "medium": - return AlertRuleSeverityMedium - case "low": - return AlertRuleSeverityLow - case "info": - return AlertRuleSeverityInfo - default: - return AlertRuleSeverityUnknown - } -} - -func convertSeverityInt(sev int) alertRuleSeverity { - switch sev { - case 1: - return AlertRuleSeverityCritical - case 2: - return AlertRuleSeverityHigh - case 3: - return AlertRuleSeverityMedium - case 4: - return AlertRuleSeverityLow - case 5: - return AlertRuleSeverityInfo - default: - return AlertRuleSeverityUnknown - } -} - -// Convert deprecated eventCatory values to subCategory values -func convertEventCategories(categories []string) []string { - var res []string - for _, c := range categories { - switch c { - case "App": - res = append(res, "Application") - case "Cloud": - res = append(res, "Cloud Activity") - case "K8sActivity": - res = append(res, "Kubernetes Activity") - default: - res = append(res, c) - } - } - return res -} - -const ( - AlertRuleSeverityCritical alertRuleSeverity = 1 - AlertRuleSeverityHigh alertRuleSeverity = 2 - AlertRuleSeverityMedium alertRuleSeverity = 3 - AlertRuleSeverityLow alertRuleSeverity = 4 - AlertRuleSeverityInfo alertRuleSeverity = 5 - AlertRuleSeverityUnknown alertRuleSeverity = 0 -) - -// NewAlertRule returns an instance of the AlertRule struct -// -// Basic usage: Initialize a new AlertRule struct, then -// -// use the new instance to do CRUD operations -// -// client, err := api.NewClient("account") -// if err != nil { -// return err -// } -// -// alertRule := api.NewAlertRule( -// "Foo", -// api.AlertRuleConfig{ -// Description: "My Alert Rule" -// Severities: api.AlertRuleSeverities{api.AlertRuleSeverityHigh, -// Channels: []string{"TECHALLY_000000000000AAAAAAAAAAAAAAAAAAAA"}, -// ResourceGroups: []string{"TECHALLY_111111111111AAAAAAAAAAAAAAAAAAAA"} -// }, -// }, -// ) -// -// client.V2.AlertRules.Create(alertRule) -func NewAlertRule(name string, rule AlertRuleConfig) AlertRule { - return AlertRule{ - Channels: rule.Channels, - Type: AlertRuleEventType, - Filter: AlertRuleFilter{ - Name: name, - Enabled: 1, - Description: rule.Description, - Severity: rule.Severities.toInt(), - ResourceGroups: rule.ResourceGroups, - AlertSubCategories: convertEventCategories(rule.AlertSubCategories), - AlertCategories: rule.AlertCategories, - AlertSources: rule.AlertSources, - }, - } -} - -func (rule AlertRuleFilter) Status() string { - if rule.Enabled == 1 { - return "Enabled" - } - return "Disabled" -} - -// List returns a list of Alert Rules -func (svc *AlertRulesService) List() (response AlertRulesResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2AlertRules, nil, &response) - return -} - -// Create creates a single Alert Rule -func (svc *AlertRulesService) Create(rule AlertRule) ( - response AlertRuleResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder("POST", apiV2AlertRules, rule, &response) - return -} - -// Delete deletes a Alert Rule that matches the provided guid -func (svc *AlertRulesService) Delete(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2AlertRuleFromGUID, guid), - nil, - nil, - ) -} - -// Update updates a single Alert Rule of the provided guid. -func (svc *AlertRulesService) Update(data AlertRule) ( - response AlertRuleResponse, - err error, -) { - if data.Guid == "" { - err = errors.New("specify a Guid") - return - } - apiPath := fmt.Sprintf(apiV2AlertRuleFromGUID, data.Guid) - err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) - return -} - -// Get returns a raw response of the Alert Rule with the matching guid. -func (svc *AlertRulesService) Get(guid string, response interface{}) error { - if guid == "" { - return errors.New("specify a Guid") - } - apiPath := fmt.Sprintf(apiV2AlertRuleFromGUID, guid) - return svc.client.RequestDecoder("GET", apiPath, nil, &response) -} - -type AlertRuleConfig struct { - Channels []string - Description string - Severities AlertRuleSeverities - ResourceGroups []string - AlertSubCategories []string - AlertCategories []string - AlertSources []string -} - -type AlertRule struct { - Guid string `json:"mcGuid,omitempty"` - Type string `json:"type"` - Channels []string `json:"intgGuidList"` - Filter AlertRuleFilter `json:"filters"` -} - -type AlertRuleFilter struct { - Name string `json:"name"` - Enabled int `json:"enabled"` - Description string `json:"description,omitempty"` - Severity []int `json:"severity"` - ResourceGroups []string `json:"resourceGroups"` - AlertSubCategories []string `json:"subCategory"` - AlertCategories []string `json:"category"` - AlertSources []string `json:"source,omitempty"` - CreatedOrUpdatedTime string `json:"createdOrUpdatedTime,omitempty"` - CreatedOrUpdatedBy string `json:"createdOrUpdatedBy,omitempty"` -} - -type AlertRuleResponse struct { - Data AlertRule `json:"data"` -} - -type AlertRulesResponse struct { - Data []AlertRule `json:"data"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alert_templates.go b/vendor/github.com/lacework/go-sdk/api/alert_templates.go deleted file mode 100644 index 306db66f3..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alert_templates.go +++ /dev/null @@ -1,69 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "fmt" - -type alertTemplatesService struct { - client *Client -} - -func (svc *alertTemplatesService) Create(alertProfileID string, template AlertTemplate) ( - response AlertProfileResponse, - err error, -) { - apiPath := fmt.Sprintf(apiV2AlertTemplates, alertProfileID) - err = svc.client.RequestEncoderDecoder("POST", apiPath, template, &response) - return -} - -func (svc *alertTemplatesService) Update(alertProfileID string, template AlertTemplate) ( - response AlertProfileResponse, - err error, -) { - body := alertTemplateUpdate{template.EventName, template.Description, template.Subject} - apiPath := fmt.Sprintf(apiV2AlertTemplatesFromGUID, alertProfileID, template.Name) - err = svc.client.RequestEncoderDecoder("POST", apiPath, body, &response) - return -} - -func (svc *alertTemplatesService) Delete(alertProfileID string, alertTemplateID string) ( - err error, -) { - apiPath := fmt.Sprintf(apiV2AlertTemplatesFromGUID, alertProfileID, alertTemplateID) - err = svc.client.RequestEncoderDecoder("POST", apiPath, nil, nil) - return -} - -type AlertTemplate struct { - Name string `json:"name" yaml:"name"` - EventName string `json:"eventName" yaml:"eventName"` - Description string `json:"description" yaml:"description"` - Subject string `json:"subject" yaml:"subject"` -} - -type alertTemplatesUpdate struct { - Alerts []AlertTemplate `json:"alerts" yaml:"alerts"` -} - -type alertTemplateUpdate struct { - EventName string `json:"eventName,omitempty" yaml:"eventName,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Subject string `json:"subject,omitempty" yaml:"subject,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts.go b/vendor/github.com/lacework/go-sdk/api/alerts.go deleted file mode 100644 index 3181b4acd..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts.go +++ /dev/null @@ -1,181 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "sort" - "time" - - "github.com/lacework/go-sdk/lwseverity" - "github.com/lacework/go-sdk/lwtime" -) - -// AlertsService is a service that interacts with the Alerts -// endpoints from the Lacework Server -type AlertsService struct { - client *Client -} - -// ValidAlertSeverities is a list of all valid alert severities -var ValidAlertSeverities = []string{"critical", "high", "medium", "low", "info"} - -// ValidAlertStatuses is a list of all valid alert statuses -var ValidAlertStatuses = []string{"Open", "Closed"} - -type AlertInfo struct { - Subject string `json:"subject"` - Description string `json:"description"` -} - -type AlertSpec struct { - Profile string `json:"alertProfile"` - Name string `json:"name"` -} - -type AlertDerivedFields struct { - Category string `json:"category"` - SubCategory string `json:"sub_category"` - Source string `json:"source"` -} - -type Alert struct { - ID int `json:"alertId"` - Name string `json:"alertName"` - Type string `json:"alertType"` - Severity string `json:"severity"` - Info AlertInfo `json:"alertInfo"` - Spec AlertSpec `json:"alertSpec"` - Status string `json:"status"` - StartTime string `json:"startTime"` - EndTime string `json:"endTime"` - UpdateTime string `json:"lastUserUpdateTime"` - PolicyID string `json:"policyId"` - DerivedFields AlertDerivedFields `json:"derivedFields"` - Reachability string `json:"reachability"` -} - -func (a Alert) GetSeverity() string { - return a.Severity -} - -type Alerts []Alert - -// Sort by alert ID descending -func (a Alerts) SortByID() { - sort.Slice(a, func(i, j int) bool { - return a[i].ID > a[j].ID - }) -} - -// Sort by alert severity descending (from critical -> low) -func (a Alerts) SortBySeverity() { - lwseverity.SortSlice(a) -} - -type AlertsResponse struct { - Data Alerts `json:"data"` - Paging V2Pagination `json:"paging"` - - v2PageMetadata `json:"-"` -} - -// Fulfill Pageable interface (look at api/v2.go) -func (r AlertsResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *AlertsResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -func (svc *AlertsService) List() (response AlertsResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2Alerts, nil, &response) - return -} - -func (svc *AlertsService) ListAll() (response AlertsResponse, err error) { - response, err = svc.List() - if err != nil { - return - } - - var ( - all Alerts - pageOk bool - ) - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -func (svc *AlertsService) ListByTime(start, end time.Time) ( - response AlertsResponse, - err error, -) { - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf( - apiV2AlertsByTime, - start.UTC().Format(lwtime.RFC3339Milli), - end.UTC().Format(lwtime.RFC3339Milli), - ), - nil, - &response, - ) - return -} - -func (svc *AlertsService) ListAllByTime(start, end time.Time) ( - response AlertsResponse, - err error, -) { - response, err = svc.ListByTime(start, end) - if err != nil { - return - } - - var ( - all Alerts - pageOk bool - ) - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_close.go b/vendor/github.com/lacework/go-sdk/api/alerts_close.go deleted file mode 100644 index ee1087ec1..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts_close.go +++ /dev/null @@ -1,82 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" -) - -type alertCloseReason int - -const ( - AlertCloseReasonOther alertCloseReason = iota - AlertCloseReasonFalsePositive - AlertCloseReasonNotEnoughInfo - AlertCloseReasonMalicious - AlertCloseReasonExpected - AlertCloseReasonExpectedBehavior -) - -// String returns the string representation of an Alert closure reason -func (i alertCloseReason) String() string { - return AlertCloseReasons[i] -} - -type alertCloseReasons map[alertCloseReason]string - -// AlertCloseReasons is the list of available Alert closure reasons -var AlertCloseReasons = alertCloseReasons{ - AlertCloseReasonOther: "Other", - AlertCloseReasonFalsePositive: "False positive", - AlertCloseReasonNotEnoughInfo: "Not enough information", - AlertCloseReasonMalicious: "Malicious and have resolution in place", - AlertCloseReasonExpected: "Expected because of routine testing", - AlertCloseReasonExpectedBehavior: "Expected Behavior", -} - -func (acr alertCloseReasons) GetOrderedReasonStrings() []string { - reasons := []string{} - for i := 0; i < len(acr); i++ { - reasons = append(reasons, acr[alertCloseReason(i)]) - } - return reasons -} - -type AlertCloseRequest struct { - AlertID int `json:"-"` - Reason int `json:"reason"` - Comment string `json:"comment,omitempty"` -} - -type AlertCloseResponse struct { - Message string `json:"message"` -} - -func (svc *AlertsService) Close(request AlertCloseRequest) ( - response AlertCloseResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder( - "POST", - fmt.Sprintf(apiV2AlertsClose, request.AlertID), - request, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_comment.go b/vendor/github.com/lacework/go-sdk/api/alerts_comment.go deleted file mode 100644 index 5e5e7091a..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts_comment.go +++ /dev/null @@ -1,50 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/pkg/errors" -) - -type AlertsCommentRequest struct { - Comment string `json:"comment"` -} - -type AlertsCommentResponse struct { - Data AlertTimeline `json:"data"` -} - -func (svc *AlertsService) Comment(id int, comment string) ( - response AlertsCommentResponse, - err error, -) { - if comment == "" { - err = errors.New("alert comment must be provided") - return - } - err = svc.client.RequestEncoderDecoder( - "POST", - fmt.Sprintf(apiV2AlertsComment, id), - AlertsCommentRequest{Comment: comment}, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details.go b/vendor/github.com/lacework/go-sdk/api/alerts_details.go deleted file mode 100644 index 60bb20678..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts_details.go +++ /dev/null @@ -1,110 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "net/http" - - "github.com/pkg/errors" -) - -type alertScope int - -const ( - AlertDetailsScope alertScope = iota - AlertInvestigationScope - AlertEventsScope - AlertRelatedAlertsScope - AlertIntegrationsScope - AlertTimelineScope -) - -var AlertScopes = map[alertScope]string{ - AlertDetailsScope: "Details", - AlertInvestigationScope: "Investigation", - AlertEventsScope: "Events", - AlertRelatedAlertsScope: "RelatedAlerts", - AlertIntegrationsScope: "Integrations", - AlertTimelineScope: "Timeline", -} - -func (i alertScope) String() string { - return AlertScopes[i] -} - -type AlertDetails struct { - Alert - EntityMap map[string]interface{} `json:"entityMap"` // @dhazekamp: this needs to be built out properly -} - -type AlertDetailsResponse struct { - Data AlertDetails `json:"data"` -} - -func (svc *AlertsService) Get(id int, scope alertScope) (interface{}, error) { - switch scope { - case AlertDetailsScope: - return svc.GetDetails(id) - case AlertInvestigationScope: - return svc.GetInvestigation(id) - case AlertEventsScope: - return svc.GetEvents(id) - case AlertRelatedAlertsScope: - return svc.GetRelatedAlerts(id) - case AlertIntegrationsScope: - return svc.GetIntegrations(id) - case AlertTimelineScope: - return svc.GetTimeline(id) - default: - return nil, errors.New(fmt.Sprintf("alert scope (%s) not recognized", scope)) - } -} - -func (svc *AlertsService) GetDetails(id int) ( - response AlertDetailsResponse, - err error, -) { - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf(apiV2AlertsDetails, id, AlertDetailsScope), - nil, - &response, - ) - return -} - -func (svc *AlertsService) Exists(id int) (bool, error) { - var response AlertDetailsResponse - err := svc.client.RequestDecoder( - "GET", - fmt.Sprintf(apiV2AlertsDetails, id, AlertDetailsScope), - nil, - &response, - ) - - if err == nil { - return true, nil - } - errResponse, ok := err.(*errorResponse) - if ok && errResponse.Response.StatusCode == http.StatusNotFound { - return false, nil - } - return false, err -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_events.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_events.go deleted file mode 100644 index 81ca38d14..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts_details_events.go +++ /dev/null @@ -1,44 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" -) - -// @dhazekamp: is this the same structure as v2/Events? -// @dhazekamp: is this structure consistent across alerts (types) -type AlertEvent map[string]interface{} - -type AlertEventsResponse struct { - Data []AlertEvent `json:"data"` -} - -func (svc *AlertsService) GetEvents(id int) ( - response AlertEventsResponse, - err error, -) { - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf(apiV2AlertsDetails, id, AlertEventsScope), - nil, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_integrations.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_integrations.go deleted file mode 100644 index dd2b79adb..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts_details_integrations.go +++ /dev/null @@ -1,92 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" -) - -type AlertIntegrationChannelState struct { - Ok bool `json:"ok"` - LastUpdatedTime int `json:"lastUpdatedTime"` - LastSuccessfulTime int `json:"lastSuccessfulTime"` - Details map[string]interface{} `json:"details,omitempty"` -} - -type AlertIntegrationChannel struct { - IntgGuid string `json:"INTG_GUID,omitempty"` - Name string `json:"NAME"` - CreatedOrUpdatedTime string `json:"CREATED_OR_UPDATED_TIME,omitempty"` - CreatedOrUpdatedBy string `json:"CREATED_OR_UPDATED_BY,omitempty"` - Type string `json:"TYPE"` - Enabled int `json:"ENABLED"` - State AlertIntegrationChannelState `json:"STATE,omitempty"` - IsOrg int `json:"IS_ORG,omitempty"` - TypeName string `json:"TYPE_NAME,omitempty"` - EnvironmentGUID string `json:"ENV_GUID"` - Data map[string]interface{} `json:"DATA"` -} - -func (c AlertIntegrationChannel) Status() string { - if c.Enabled == 1 { - return "Enabled" - } - return "Disabled" -} - -func (c AlertIntegrationChannel) StateString() string { - if c.State.Ok { - return "Ok" - } - return "Pending" -} - -type AlertIntegrationContext struct { - ID string `json:"id"` - Link string `json:"link"` -} - -type AlertIntegration struct { - ID string `json:"alertIntegrationId"` - AlertID int `json:"alertId"` - Type string `json:"integrationType"` - Channel AlertIntegrationChannel `json:"alertChannel"` - Context AlertIntegrationContext `json:"integrationContext"` - IntgGUID string `json:"intgGuid"` - LastSyncTime string `json:"lastSyncTime"` - Status string `json:"status"` - Bidirectional bool `json:"isBidirectional"` -} - -type AlertIntegrationsResponse struct { - Data []AlertIntegration `json:"data"` -} - -func (svc *AlertsService) GetIntegrations(id int) ( - response AlertIntegrationsResponse, - err error, -) { - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf(apiV2AlertsDetails, id, AlertIntegrationsScope), - nil, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_investigation.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_investigation.go deleted file mode 100644 index e4c9cf2da..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts_details_investigation.go +++ /dev/null @@ -1,45 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" -) - -type AlertInvestigation struct { - Question string `json:"question"` - Answer string `json:"answer"` -} - -type AlertInvestigationResponse struct { - Data []AlertInvestigation `json:"data"` -} - -func (svc *AlertsService) GetInvestigation(id int) ( - response AlertInvestigationResponse, - err error, -) { - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf(apiV2AlertsDetails, id, AlertInvestigationScope), - nil, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_related.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_related.go deleted file mode 100644 index 2247fc989..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts_details_related.go +++ /dev/null @@ -1,61 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "sort" -) - -type RelatedAlert struct { - ID string `json:"eventId"` - Name string `json:"eventName"` - Type string `json:"eventType"` - Severity string `json:"severity"` - Rank int `json:"rank"` - Info AlertInfo `json:"eventInfo"` - StartTime string `json:"startTime"` - EndTime string `json:"endTime"` -} - -type RelatedAlerts []RelatedAlert - -type RelatedAlertsResponse struct { - Data RelatedAlerts `json:"data"` -} - -func (ra RelatedAlerts) SortRankDescending() RelatedAlerts { - sort.Slice(ra, func(i, j int) bool { - return ra[i].Rank > ra[j].Rank - }) - return ra -} - -func (svc *AlertsService) GetRelatedAlerts(id int) ( - response RelatedAlertsResponse, - err error, -) { - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf(apiV2AlertsDetails, id, AlertRelatedAlertsScope), - nil, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_details_timeline.go b/vendor/github.com/lacework/go-sdk/api/alerts_details_timeline.go deleted file mode 100644 index 7363d4104..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts_details_timeline.go +++ /dev/null @@ -1,75 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" -) - -type AlertTimelineMessage struct { - Format string `json:"format"` - Value string `json:"value"` -} - -type AlertTimelineUser struct { - UserGUID string `json:"userGuid"` - Name string `json:"username"` -} - -type AlertTimelineNewIntegrationContext struct { - AlertID int `json:"alertId"` - LastSyncTime string `json:"lastSyncTime"` - AlertIntegrationStatus string `json:"alertIntegrationStatus"` - Status string `json:"status"` - Bidirectional bool `json:"isBidirectional"` -} - -type AlertTimelineUpdateContext struct { - NewIntegration AlertTimelineNewIntegrationContext `json:"newIntegration"` -} - -type AlertTimeline struct { - ID int `json:"id"` - AlertID int `json:"alertId"` - EntryType string `json:"entryType"` - EntryAuthorType string `json:"entryAuthorType"` - IntgGUID string `json:"intgGuid"` - Message AlertTimelineMessage `json:"message"` - ExternalTime string `json:"externalTime"` - User AlertTimelineUser `json:"user"` - UpdateContext AlertTimelineUpdateContext `json:"updateContext"` - Channel AlertIntegrationChannel `json:"alertChannel"` -} - -type AlertTimelineResponse struct { - Data []AlertTimeline `json:"data"` -} - -func (svc *AlertsService) GetTimeline(id int) ( - response AlertTimelineResponse, - err error, -) { - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf(apiV2AlertsDetails, id, AlertTimelineScope), - nil, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/alerts_search.go b/vendor/github.com/lacework/go-sdk/api/alerts_search.go deleted file mode 100644 index 6c95691aa..000000000 --- a/vendor/github.com/lacework/go-sdk/api/alerts_search.go +++ /dev/null @@ -1,68 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -type alertsFilterField string - -const ( - AlertsFilterFieldType alertsFilterField = "alertType" - AlertsFilterFieldSeverity alertsFilterField = "severity" - AlertsFilterFieldStatus alertsFilterField = "status" -) - -func (svc *AlertsService) Search(filter SearchFilter) ( - response AlertsResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder( - "POST", - apiV2AlertsSearch, - filter, - &response, - ) - return -} - -func (svc *AlertsService) SearchAll(filter SearchFilter) ( - response AlertsResponse, - err error, -) { - response, err = svc.Search(filter) - if err != nil { - return - } - - var ( - all Alerts - pageOk bool - ) - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/api.go b/vendor/github.com/lacework/go-sdk/api/api.go deleted file mode 100644 index 0cbe2be69..000000000 --- a/vendor/github.com/lacework/go-sdk/api/api.go +++ /dev/null @@ -1,167 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "strings" -) - -const ( - // API v2 Endpoints - apiTokens = "v2/access/tokens" // Auth - - apiV2HoneyMetrics = "v2/metrics/honeycomb" - - apiV2UserProfile = "v2/UserProfile" - - apiV2ContainerRegistries = "v2/ContainerRegistries" - apiV2ContainerRegistryFromGUID = "v2/ContainerRegistries/%s" - - apiV2AlertChannels = "v2/AlertChannels" - apiV2AlertChannelFromGUID = "v2/AlertChannels/%s" - apiV2AlertChannelTest = "v2/AlertChannels/%s/test" - - apiV2AlertProfiles = "v2/AlertProfiles" - apiV2AlertProfileFromGUID = "v2/AlertProfiles/%s" - - apiV2AlertTemplates = "v2/AlertProfiles/%s/AlertTemplates" - apiV2AlertTemplatesFromGUID = "v2/AlertProfiles/%s/AlertTemplates/%s" - - apiV2AlertRules = "v2/AlertRules" - apiV2AlertRuleFromGUID = "v2/AlertRules/%s" - - apiV2CloudAccounts = "v2/CloudAccounts" - apiV2CloudAccountsWithParam = "v2/CloudAccounts/%s" - - apiV2AgentAccessTokens = "v2/AgentAccessTokens" - apiV2AgentAccessTokensSearch = "v2/AgentAccessTokens/search" - apiV2AgentAccessTokenFromID = "v2/AgentAccessTokens/%s" - - apiV2AgentInfoSearch = "v2/AgentInfo/search" - - apiV2PolicyExceptions = "v2/Exceptions?policyId=%s" - apiV2PolicyExceptionsFromExceptionID = "v2/Exceptions/%s?policyId=%s" - - apiV2InventorySearch = "v2/Inventory/search" - apiV2InventoryScanCsp = "v2/Inventory/scan?csp=%s" - - apiV2ComplianceEvaluationsSearch = "v2/Configs/ComplianceEvaluations/search" - - apiV2Components = "v2/Components?os=%s&arch=%s" - apiV2ComponentsVersions = "v2/Components/%d?os=%s&arch=%s" - apiV2ComponentsFetch = "v2/Components/Artifact/%d?os=%s&arch=%s&version=%s" - - apiV2ComponentDataRequest = "v2/ComponentData/requestUpload" - apiV2ComponentDataComplete = "v2/ComponentData/completeUpload" - - apiV2ConfigsAzure = "v2/Configs/AzureSubscriptions" - apiV2ConfigsAzureSubscriptions = "v2/Configs/AzureSubscriptions?tenantId=%s" - apiV2ConfigsGcp = "v2/Configs/GcpProjects" - apiV2ConfigsGcpProjects = "v2/Configs/GcpProjects?orgId=%s" - - apiV2FeatureFlags = "v2/FeatureFlags" - - apiV2Policies = "v2/Policies" - apiV2Queries = "v2/Queries" - apiV2QueriesExecute = "v2/Queries/execute" - apiV2QueriesValidate = "v2/Queries/validate" - - apiV2Reports = "v2/Reports?primaryQueryId=%s&format=%s&%s=%s" - apiV2ReportsSecondaryQuery = "v2/Reports?primaryQueryId=%s&secondaryQueryId=%s&format=%s&%s=%s" - - apiV2ReportDefinitions = "v2/ReportDefinitions" - apiV2ReportDefinitionsFromGUID = "v2/ReportDefinitions/%s" - apiV2ReportDefinitionsRevert = "v2/ReportDefinitions/%s?revertTo=%d" - apiV2ReportDefinitionsVersions = "v2/ReportDefinitions/%s?allVersions=true" - - apiV2ReportDistributions = "v2/ReportDistributions" - apiV2ReportDistributionsFromGUID = "v2/ReportDistributions/%s" - - apiV2ReportRules = "v2/ReportRules" - apiV2ReportRuleFromGUID = "v2/ReportRules/%s" - - apiV2ResourceGroups = "v2/ResourceGroups" - apiV2ResourceGroupsFromGUID = "v2/ResourceGroups/%s" - - apiV2Datasources = "v2/Datasources" - - apiV2DataExportRules = "v2/DataExportRules" - apiV2DataExportRulesFromGUID = "v2/DataExportRules/%s" - apiV2DataExportRulesSearch = "v2/DataExportRules/search" - - apiV2TeamMembers = "v2/TeamMembers" - apiV2TeamMembersFromGUID = "v2/TeamMembers/%s" - apiV2TeamMembersSearch = "v2/TeamMembers/search" - - apiV2VulnerabilitiesContainersSearch = "v2/Vulnerabilities/Containers/search" - apiV2VulnerabilitiesContainersScan = "v2/Vulnerabilities/Containers/scan" - apiV2VulnerabilitiesContainersScanStatus = "v2/Vulnerabilities/Containers/scan/%s" - apiV2VulnerabilitiesHostsSearch = "v2/Vulnerabilities/Hosts/search" - apiV2VulnerabilitiesSoftwarePackagesScan = "v2/Vulnerabilities/SoftwarePackages/scan" - - apiV2VulnerabilityExceptions = "v2/VulnerabilityExceptions" - apiV2VulnerabilityExceptionFromGUID = "v2/VulnerabilityExceptions/%s" - - apiV2EntitiesSearch = "v2/Entities/%s/search" - - apiV2Alerts = "v2/Alerts" - apiV2AlertsByTime = "v2/Alerts?startTime=%s&endTime=%s" - apiV2AlertsSearch = "v2/Alerts/search" - apiV2AlertsDetails = "v2/Alerts/%d?scope=%s" - apiV2AlertsComment = "v2/Alerts/%d/comment" - apiV2AlertsClose = "v2/Alerts/%d/close" - - apiV2OrganizationInfo = "v2/OrganizationInfo" - - apiSuppressions = "v2/suppressions/%s/allExceptions" - - apiRecommendations = "v2/recommendations/%s" - - apiV2MigrateGcpAtSes = "v2/migrateGcpAtSes" -) - -// WithApiV2 configures the client to use the API version 2 (/api/v2) -// for common API endpoints -// -// (no-op) DEPRECATED -func WithApiV2() Option { - return clientFunc(func(c *Client) error { - c.log.Warn("WithApiV2() has been deprecated, all clients now default to APIv2") - return nil - }) -} - -// ApiVersion returns the API client version -func (c *Client) ApiVersion() string { - return c.apiVersion -} - -// apiPath builds a path by using the current API version -func (c *Client) apiPath(p string) string { - if strings.HasPrefix(p, "/api/v") { - return p - } - - if strings.HasPrefix(p, "v1") || strings.HasPrefix(p, "v2") { - return fmt.Sprintf("/api/%s", p) - } - - return fmt.Sprintf("/api/%s/%s", c.apiVersion, p) -} diff --git a/vendor/github.com/lacework/go-sdk/api/auth.go b/vendor/github.com/lacework/go-sdk/api/auth.go deleted file mode 100644 index 4b02cb92b..000000000 --- a/vendor/github.com/lacework/go-sdk/api/auth.go +++ /dev/null @@ -1,165 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "time" - - "go.uber.org/zap" - - "github.com/lacework/go-sdk/internal/format" -) - -const DefaultTokenExpiryTime = 3600 - -// authConfig representing information like key_id, secret and token -// used for authenticating requests -type authConfig struct { - keyID string - secret string - token string - expiration int - expiresAt time.Time -} - -// WithApiKeys sets the key_id and secret used to generate API access tokens -func WithApiKeys(id, secret string) Option { - return clientFunc(func(c *Client) error { - if c.auth == nil { - c.auth = &authConfig{} - } - - c.log.Debug("setting up auth", - zap.String("key", id), - zap.String("secret", format.Secret(4, secret)), - ) - c.auth.keyID = id - c.auth.secret = secret - return nil - }) -} - -// WithTokenFromKeys sets the API access keys and triggers a new token generation -// NOTE: Order matters when using this option, use it at the end of a NewClient() func -func WithTokenFromKeys(id, secret string) Option { - return clientFunc(func(c *Client) error { - if c.auth == nil { - c.auth = &authConfig{} - } - - _, err := c.GenerateTokenWithKeys(id, secret) - return err - }) -} - -// WithToken sets the token used to authenticate the API requests -func WithToken(token string) Option { - return clientFunc(func(c *Client) error { - c.log.Debug("setting up auth", zap.String("token", format.Secret(4, token))) - c.auth.token = token - c.auth.expiresAt = time.Now().UTC().Add(DefaultTokenExpiryTime * time.Second) - return nil - }) -} - -// WithTokenAndExpiration sets the token used to authenticate the API requests -// and additionally configures the expiration of the token -func WithTokenAndExpiration(token string, expiration time.Time) Option { - return clientFunc(func(c *Client) error { - c.log.Debug("setting up auth", - zap.String("token", format.Secret(4, token)), - zap.Time("expires_at", expiration), - ) - c.auth.token = token - c.auth.expiresAt = expiration.UTC() - return nil - }) -} - -// WithExpirationTime configures the token expiration time -func WithExpirationTime(t int) Option { - return clientFunc(func(c *Client) error { - c.log.Debug("setting up auth", zap.Int("expiration", t)) - c.auth.expiration = t - c.auth.expiresAt = time.Now().UTC().Add(time.Duration(t) * time.Second) - return nil - }) -} - -func (c *Client) TokenExpired() bool { - return c.auth.expiresAt.Sub(time.Now().UTC()) <= 0 -} - -// GenerateToken generates a new access token -func (c *Client) GenerateToken() (*TokenData, error) { - if c.auth.keyID == "" || c.auth.secret == "" { - return nil, fmt.Errorf("unable to generate access token: auth keys missing") - } - - body, err := jsonReader(tokenRequest{c.auth.keyID, c.auth.expiration}) - if err != nil { - return nil, err - } - - request, err := c.NewRequest("POST", apiTokens, body) - if err != nil { - return nil, err - } - - var tokenData TokenData - res, err := c.DoDecoder(request, &tokenData) - if err != nil { - return nil, err - } - defer res.Body.Close() - - c.log.Debug("storing token", - zap.String("token", format.Secret(4, tokenData.Token)), - zap.Time("expires_at", tokenData.ExpiresAt), - ) - c.auth.token = tokenData.Token - c.auth.expiresAt = tokenData.ExpiresAt - if err != nil { - c.log.Error("failed to parse token expiration response", zap.Error(err)) - } - return &tokenData, nil -} - -// GenerateTokenWithKeys generates a new access token with the provided keys -func (c *Client) GenerateTokenWithKeys(keyID, secretKey string) (*TokenData, error) { - c.log.Debug("setting up auth", - zap.String("key", keyID), - zap.String("secret", format.Secret(4, secretKey)), - ) - c.auth.keyID = keyID - c.auth.secret = secretKey - return c.GenerateToken() -} - -type tokenRequest struct { - KeyID string `json:"keyId"` - ExpiryTime int `json:"expiryTime"` -} - -// APIv2 -type TokenData struct { - ExpiresAt time.Time `json:"expiresAt"` - Token string `json:"token"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/callbacks.go b/vendor/github.com/lacework/go-sdk/api/callbacks.go deleted file mode 100644 index 9239d6165..000000000 --- a/vendor/github.com/lacework/go-sdk/api/callbacks.go +++ /dev/null @@ -1,39 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "net/http" - -type LifecycleCallbacks struct { - // RequestCallback is a function that will be executed after every client request - RequestCallback func(int, http.Header) error - - // TokenExpiredCallback is a function that the consumer can configure - // into the client so that it is run when the token expired - TokenExpiredCallback func() error -} - -// WithLifecycleCallbacks will configure the lifecycle callback functions -func WithLifecycleCallbacks(callbacks LifecycleCallbacks) Option { - return clientFunc(func(c *Client) error { - c.log.Debug("setting up client callbacks") - c.callbacks = callbacks - return nil - }) -} diff --git a/vendor/github.com/lacework/go-sdk/api/client.go b/vendor/github.com/lacework/go-sdk/api/client.go deleted file mode 100644 index 37d41b669..000000000 --- a/vendor/github.com/lacework/go-sdk/api/client.go +++ /dev/null @@ -1,278 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "context" - "fmt" - "math/rand" - "net" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/cenkalti/backoff/v4" - "github.com/lacework/go-sdk/lwdomain" - "github.com/pkg/errors" - "go.uber.org/zap" -) - -const ( - defaultTimeout = 120 * time.Second - defaultTLSTimeout = 123 * time.Second -) - -type Client struct { - id string - account string - subaccount string - apiVersion string - baseURL *url.URL - auth *authConfig - c *http.Client - log *zap.Logger - headers map[string]string - callbacks LifecycleCallbacks - retries *backoff.ExponentialBackOff - - Policy *PolicyService - - V2 *V2Endpoints -} - -type Option interface { - apply(c *Client) error -} - -type clientFunc func(c *Client) error - -func (fn clientFunc) apply(c *Client) error { - return fn(c) -} - -// New generates a new Lacework API client -// -// Example of basic usage -// -// lacework, err := api.NewClient("demo") -// if err == nil { -// lacework.Integrations.List() -// } -func NewClient(account string, opts ...Option) (*Client, error) { - if account == "" { - return nil, errors.New("account cannot be empty") - } - - // verify if the user provided the full qualified domain name - if strings.Contains(account, ".lacework.net") { - domain, err := lwdomain.New(account) - if err != nil { - return nil, err - } - account = domain.String() - } - - baseURL, err := url.Parse(fmt.Sprintf("https://%s.lacework.net", account)) - if err != nil { - return nil, err - } - - defaultTransport := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: defaultTransportDialContext(&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }), - ForceAttemptHTTP2: true, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: defaultTLSTimeout, - ExpectContinueTimeout: 1 * time.Second, - } - - c := &Client{ - id: newID(), - account: account, - baseURL: baseURL, - apiVersion: "v2", - headers: map[string]string{ - "User-Agent": fmt.Sprintf("Go Client/%s", Version), - }, - auth: &authConfig{ - expiration: DefaultTokenExpiryTime, - }, - c: &http.Client{Timeout: defaultTimeout, - Transport: defaultTransport}, - } - - c.V2 = NewV2Endpoints(c) - - // init logger, this could change if a user calls api.WithLogLevel() - c.initLogger("") - - for _, opt := range opts { - if err := opt.apply(c); err != nil { - return c, err - } - } - - c.log.Info("api client created", - zap.String("url", c.baseURL.String()), - zap.String("version", c.apiVersion), - zap.String("log_level", c.log.Level().CapitalString()), - zap.Int("timeout", c.auth.expiration), - ) - return c, nil -} - -// CopyClient generates a copy of the provider Lacework API Go client -// -// Example of basic usage -// -// client, err := api.NewClient("demo") -// if err == nil { -// client.Integrations.List() -// } -// -// clientCopy, err := api.CopyClient(client, api.WithOrgAccess()) -// if err == nil { -// clientCopy.Integrations.List() -// } -func CopyClient(origin *Client, opts ...Option) (*Client, error) { - dest := new(Client) - *dest = *origin - - // no client should have the same ID - dest.id = newID() - - for _, opt := range opts { - if err := opt.apply(dest); err != nil { - return dest, err - } - } - return dest, nil -} - -// WithSubaccount sets a subaccount into an API client -func WithSubaccount(subaccount string) Option { - return clientFunc(func(c *Client) error { - if subaccount != "" { - c.log.Debug("setting up client", zap.String("subaccount", subaccount)) - c.subaccount = subaccount - c.log.Debug("setting up header", zap.String("Account-Name", subaccount)) - c.headers["Account-Name"] = subaccount - } - return nil - }) -} - -// WithTimeout changes the default client timeout -func WithTimeout(timeout time.Duration) Option { - return clientFunc(func(c *Client) error { - c.log.Debug("setting up client", zap.Reflect("timeout", timeout)) - c.c.Timeout = timeout - return nil - }) -} - -// WithRetries sets the retrying policy for API requests -func WithRetries(retries *backoff.ExponentialBackOff) Option { - return clientFunc(func(c *Client) error { - c.log.Debug("setting up retrying policy", zap.Reflect("retries", retries)) - c.retries = retries - return nil - }) -} - -// WithTransport changes the default transport to increase TLSHandshakeTimeout -func WithTransport(transport http.RoundTripper) Option { - return clientFunc(func(c *Client) error { - c.c.Transport = transport - return nil - }) -} - -// WithURL sets the base URL, this options is only available for test purposes -func WithURL(baseURL string) Option { - return clientFunc(func(c *Client) error { - u, err := url.Parse(baseURL) - if err != nil { - return err - } - - c.log.Debug("setting up client", zap.String("url", baseURL)) - c.baseURL = u - return nil - }) -} - -// WithHeader configures a HTTP Header to pass to every request -func WithHeader(header, value string) Option { - return clientFunc(func(c *Client) error { - if header != "" && value != "" { - c.log.Debug("setting up header", zap.String(header, value)) - c.headers[header] = value - } - return nil - }) -} - -// WithOrgAccess sets the Org-Access Header to access the organization level data sets -func WithOrgAccess() Option { - return clientFunc(func(c *Client) error { - c.log.Debug("setting up header", zap.String("Org-Access", "true")) - c.headers["Org-Access"] = "true" - return nil - }) -} - -// URL returns the base url configured -func (c *Client) URL() string { - return c.baseURL.String() -} - -// Retries returns the retrying policy configured -func (c *Client) Retries() *backoff.ExponentialBackOff { - return c.retries -} - -// ValidAuth verifies that the client has valid authentication -func (c *Client) ValidAuth() bool { - return c.auth.token != "" -} - -// OrgAccess check if the Org-Access header is set to 'true', if so, -// the client is configured to manage org level dataset -func (c *Client) OrgAccess() bool { - return c.headers["Org-Access"] == "true" -} - -// newID generates a new client id, this id is useful for logging purposes -// when there are more than one client running on the same machine -func newID() string { - now := time.Now().UTC().UnixNano() - seed := rand.New(rand.NewSource(now)) - return strconv.FormatInt(seed.Int63(), 16) -} - -func defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { - return dialer.DialContext -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts.go deleted file mode 100644 index 4ad222120..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts.go +++ /dev/null @@ -1,296 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "time" - - "github.com/pkg/errors" - - "github.com/lacework/go-sdk/lwtime" -) - -// CloudAccountsService is the service that interacts with -// the CloudAccounts schema from the Lacework APIv2 Server -type CloudAccountsService struct { - client *Client -} - -// NewCloudAccount returns an instance of the CloudAccountRaw struct with the -// provided Cloud Account integration type, name and raw data as an interface{}. -// -// NOTE: This function must be used by any Cloud Account type. -// -// Basic usage: Initialize a new AwsIntegration struct, then use the new -// instance to do CRUD operations -// -// client, err := api.NewClient("account") -// if err != nil { -// return err -// } -// -// awsCtSqs := api.NewCloudAccount("foo", -// api.AwsCtSqsCloudAccount, -// api.AwsCtSqsData{ -// QueueUrl: "https://sqs.us-west-2.amazonaws.com/123456789000/lw", -// Credentials: &api.AwsCtSqsCredentials { -// RoleArn: "arn:aws:XYZ", -// ExternalID: "1", -// }, -// }, -// ) -// -// client.V2.CloudAccount.Create(awsCtSqs) -func NewCloudAccount(name string, iType cloudAccountType, data interface{}) CloudAccountRaw { - return CloudAccountRaw{ - v2CommonIntegrationData: v2CommonIntegrationData{ - Name: name, - Type: iType.String(), - Enabled: 1, - }, - Data: data, - } -} - -// CloudAccount is an interface that helps us implement a few functions -// that any Cloud Account might use, there are some cases, like during -// Update, where we need to get the ID of the Cloud Account and its type, -// this will allow users to pass any Cloud Account that implements these -// methods -type CloudAccount interface { - ID() string - CloudAccountType() cloudAccountType -} - -type cloudAccountType int - -const ( - // type that defines a non-existing Cloud Account integration - NoneCloudAccount cloudAccountType = iota - AwsCfgCloudAccount - AwsCtSqsCloudAccount - AwsEksAuditCloudAccount - AwsSidekickCloudAccount - AwsSidekickOrgCloudAccount - AwsUsGovCfgCloudAccount - AwsUsGovCtSqsCloudAccount - AzureAdAlCloudAccount - AzureAlSeqCloudAccount - AzureCfgCloudAccount - GcpAtSesCloudAccount - GcpCfgCloudAccount - GcpGkeAuditCloudAccount - GcpSidekickCloudAccount - AzureSidekickCloudAccount - GcpAlPubSubCloudAccount - OciCfgCloudAccount -) - -// CloudAccountTypes is the list of available Cloud Account integration types -var CloudAccountTypes = map[cloudAccountType]string{ - NoneCloudAccount: "None", - AwsCfgCloudAccount: "AwsCfg", - AwsCtSqsCloudAccount: "AwsCtSqs", - AwsEksAuditCloudAccount: "AwsEksAudit", - AwsSidekickCloudAccount: "AwsSidekick", - AwsSidekickOrgCloudAccount: "AwsSidekickOrg", - AwsUsGovCfgCloudAccount: "AwsUsGovCfg", - AwsUsGovCtSqsCloudAccount: "AwsUsGovCtSqs", - AzureAdAlCloudAccount: "AzureAdAl", - AzureAlSeqCloudAccount: "AzureAlSeq", - AzureCfgCloudAccount: "AzureCfg", - GcpAtSesCloudAccount: "GcpAtSes", - GcpCfgCloudAccount: "GcpCfg", - GcpGkeAuditCloudAccount: "GcpGkeAudit", - GcpSidekickCloudAccount: "GcpSidekick", - AzureSidekickCloudAccount: "AzureSidekick", - GcpAlPubSubCloudAccount: "GcpAlPubSub", - OciCfgCloudAccount: "OciCfg", -} - -// String returns the string representation of a Cloud Account integration type -func (i cloudAccountType) String() string { - return CloudAccountTypes[i] -} - -// FindCloudAccountType looks up inside the list of available cloud account types -// the matching type from the provided string, if none, returns NoneCloudAccount -func FindCloudAccountType(cloudAccount string) (cloudAccountType, bool) { - for cType, cStr := range CloudAccountTypes { - if cStr == cloudAccount { - return cType, true - } - } - return NoneCloudAccount, false -} - -// List returns a list of Cloud Account integrations -func (svc *CloudAccountsService) List() (response CloudAccountsResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2CloudAccounts, nil, &response) - return -} - -// ListByType lists the cloud accounts from the provided type that are available -// on the Lacework Server -func (svc *CloudAccountsService) ListByType(caType cloudAccountType) (response CloudAccountsResponse, err error) { - err = svc.get(caType.String(), &response) - return -} - -// Create creates a single Cloud Account integration -func (svc *CloudAccountsService) Create(integration CloudAccountRaw) ( - response CloudAccountResponse, - err error, -) { - err = svc.create(integration, &response) - return -} - -// Delete deletes a Cloud Account integration that matches the provided guid -func (svc *CloudAccountsService) Delete(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2CloudAccountsWithParam, guid), - nil, - nil, - ) -} - -// Migrate marks a Cloud Account integration that matches the provided guid for migration -func (svc *CloudAccountsService) Migrate(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - data := MigrateRequestData{ - MigrateData{ - IntgGuid: guid, - Props: Props{ - Migrate: true, - MigrationTimestamp: time.Now(), - }, - }, - } - - return svc.client.RequestEncoderDecoder( - "PATCH", - apiV2MigrateGcpAtSes, - data, - nil, - ) -} - -// Get returns a raw response of the Cloud Account with the matching integration guid. -// -// To return a more specific Go struct of a Cloud Account integration, use the proper -// method such as GetAwsCtSqs() where the function name is composed by: -// -// Get(guid) -// -// Where is the Cloud Account integration type. -func (svc *CloudAccountsService) Get(guid string, response interface{}) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - return svc.get(guid, &response) -} - -type CloudAccountRaw struct { - v2CommonIntegrationData - Data interface{} `json:"data,omitempty"` -} - -func (cloud CloudAccountRaw) GetData() any { - return cloud.Data -} - -func (cloud CloudAccountRaw) GetCommon() v2CommonIntegrationData { - return cloud.v2CommonIntegrationData -} - -func (cloud CloudAccountRaw) CloudAccountType() cloudAccountType { - t, _ := FindCloudAccountType(cloud.Type) - return t -} - -type CloudAccountResponse struct { - Data CloudAccountRaw `json:"data"` -} - -type CloudAccountsResponse struct { - Data []CloudAccountRaw `json:"data"` -} - -type v2CommonIntegrationData struct { - IntgGuid string `json:"intgGuid,omitempty"` - Name string `json:"name"` - CreatedOrUpdatedTime string `json:"createdOrUpdatedTime,omitempty"` - CreatedOrUpdatedBy string `json:"createdOrUpdatedBy,omitempty"` - Type string `json:"type"` - Enabled int `json:"enabled"` - IsOrg int `json:"isOrg,omitempty"` - State *V2IntegrationState `json:"state,omitempty"` -} - -func (c v2CommonIntegrationData) ID() string { - return c.IntgGuid -} - -func (c v2CommonIntegrationData) Status() string { - if c.Enabled == 1 { - return "Enabled" - } - return "Disabled" -} - -func (c v2CommonIntegrationData) StateString() string { - if c.State != nil && c.State.Ok { - return "Ok" - } - return "Pending" -} - -type V2IntegrationState struct { - Ok bool `json:"ok"` - Details map[string]interface{} `json:"details"` - LastUpdatedTime lwtime.Epoch `json:"lastUpdatedTime"` - LastSuccessfulTime lwtime.Epoch `json:"lastSuccessfulTime"` -} - -func (svc *CloudAccountsService) create(data interface{}, response interface{}) error { - return svc.client.RequestEncoderDecoder("POST", apiV2CloudAccounts, data, response) -} - -func (svc *CloudAccountsService) get(param string, response interface{}) error { - apiPath := fmt.Sprintf(apiV2CloudAccountsWithParam, param) - return svc.client.RequestDecoder("GET", apiPath, nil, response) -} - -func (svc *CloudAccountsService) update(guid string, data interface{}, response interface{}) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - apiPath := fmt.Sprintf(apiV2CloudAccountsWithParam, guid) - return svc.client.RequestEncoderDecoder("PATCH", apiPath, data, response) -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_cfg.go deleted file mode 100644 index e765e3bd8..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_cfg.go +++ /dev/null @@ -1,57 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAwsCfg gets a single AwsCfg integration matching the -// provided integration guid -func (svc *CloudAccountsService) GetAwsCfg(guid string) ( - response AwsCfgIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAwsCfg updates a single AwsCfg integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAwsCfg(data CloudAccount) ( - response AwsCfgIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsCfgIntegrationResponse struct { - Data AwsCfg `json:"data"` -} - -type AwsCfg struct { - v2CommonIntegrationData - Data AwsCfgData `json:"data"` -} - -type AwsCfgData struct { - Credentials AwsCfgCredentials `json:"crossAccountCredentials"` - AwsAccountID string `json:"awsAccountId,omitempty"` -} - -type AwsCfgCredentials struct { - RoleArn string `json:"roleArn"` - ExternalID string `json:"externalId"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_ct_sqs.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_ct_sqs.go deleted file mode 100644 index c2f3ddabb..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_ct_sqs.go +++ /dev/null @@ -1,92 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/base64" - "fmt" - "strings" -) - -// GetAwsCtSqs gets a single AwsCtSqs integration matching the -// provided integration guid -func (svc *CloudAccountsService) GetAwsCtSqs(guid string) ( - response AwsCtSqsIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAwsCtSqs updates a single AwsCtSqs integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAwsCtSqs(data CloudAccount) ( - response AwsCtSqsIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsCtSqsIntegrationResponse struct { - Data AwsCtSqsIntegration `json:"data"` -} - -type AwsCtSqsIntegration struct { - v2CommonIntegrationData - Data AwsCtSqsData `json:"data"` -} - -type AwsCtSqsData struct { - Credentials AwsCtSqsCredentials `json:"crossAccountCredentials"` - QueueUrl string `json:"queueUrl"` - AwsAccountID string `json:"awsAccountId,omitempty"` - - // This field must be a base64 encode with the following format: - // - // "data:application/json;name=i.json;base64,[ENCODING]" - // - // [ENCODING] is the the base64 encode, use EncodeAccountMappingFile() to encode a JSON mapping file - AccountMappingFile string `json:"accountMappingFile,omitempty"` -} - -type AwsCtSqsCredentials struct { - RoleArn string `json:"roleArn"` - ExternalID string `json:"externalId"` -} - -func (aws *AwsCtSqsData) EncodeAccountMappingFile(mapping []byte) { - encodedMappings := base64.StdEncoding.EncodeToString(mapping) - aws.AccountMappingFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedMappings) -} - -func (aws *AwsCtSqsData) DecodeAccountMappingFile() ([]byte, error) { - if len(aws.AccountMappingFile) == 0 { - return []byte{}, nil - } - - var ( - b64 = strings.Split(aws.AccountMappingFile, ",") - raw, err = base64.StdEncoding.DecodeString(b64[1]) - ) - if err != nil { - return []byte{}, err - } - - return raw, nil -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go deleted file mode 100644 index a53cc6e7a..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go +++ /dev/null @@ -1,57 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAwsEksAudit gets a single AwsEksAudit integration matching the provided integration guid -func (svc *CloudAccountsService) GetAwsEksAudit(guid string) ( - response AwsEksAuditIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAwsEksAudit updates a single AwsEksAudit integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAwsEksAudit(data CloudAccount) ( - response AwsEksAuditIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsEksAuditIntegrationResponse struct { - Data AwsEksAuditIntegration `json:"data"` -} - -type AwsEksAuditIntegration struct { - v2CommonIntegrationData - Data AwsEksAuditData `json:"data"` -} - -type AwsEksAuditData struct { - Credentials AwsEksAuditCredentials `json:"crossAccountCredentials"` - SnsArn string `json:"snsArn"` - S3BucketArn string `json:"s3BucketArn,omitempty"` -} - -type AwsEksAuditCredentials struct { - RoleArn string `json:"roleArn"` - ExternalID string `json:"externalId"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_cfg.go deleted file mode 100644 index 17cb99e82..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_cfg.go +++ /dev/null @@ -1,57 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAwsUsGovCfg gets a single AwsUsGovCfg integration matching the -// provided integration guid -func (svc *CloudAccountsService) GetAwsUsGovCfg(guid string) ( - response AwsUsGovCfgIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAwsUsGovCfg updates a single AwsUsGovCfg integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAwsUsGovCfg(data CloudAccount) ( - response AwsUsGovCfgIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsUsGovCfgIntegrationResponse struct { - Data AwsUsGovCfg `json:"data"` -} - -type AwsUsGovCfg struct { - v2CommonIntegrationData - Data AwsUsGovCfgData `json:"data"` -} - -type AwsUsGovCfgData struct { - Credentials AwsUsGovCfgCredentials `json:"accessKeyCredentials"` -} - -type AwsUsGovCfgCredentials struct { - AwsAccountID string `json:"accountId"` - AccessKeyID string `json:"accessKeyId"` - SecretAccessKey string `json:"secretAccessKey"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_ct.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_ct.go deleted file mode 100644 index cd2dd1b9d..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_gov_ct.go +++ /dev/null @@ -1,58 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAwsUsGovCtSqs gets a single AwsUsGovCtSqs integration matching the -// provided integration guid -func (svc *CloudAccountsService) GetAwsUsGovCtSqs(guid string) ( - response AwsUsGovCtSqsIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAwsUsGovCtSqs updates a single AwsUsGovCtSqs integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAwsUsGovCtSqs(data CloudAccount) ( - response AwsUsGovCtSqsIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsUsGovCtSqsIntegrationResponse struct { - Data AwsUsGovCtSqs `json:"data"` -} - -type AwsUsGovCtSqs struct { - v2CommonIntegrationData - Data AwsUsGovCtSqsData `json:"data"` -} - -type AwsUsGovCtSqsData struct { - Credentials AwsUsGovCtSqsCredentials `json:"accessKeyCredentials"` - QueueUrl string `json:"queueUrl"` -} - -type AwsUsGovCtSqsCredentials struct { - AwsAccountID string `json:"accountId"` - AccessKeyID string `json:"accessKeyId"` - SecretAccessKey string `json:"secretAccessKey"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick.go deleted file mode 100644 index fbce51146..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick.go +++ /dev/null @@ -1,84 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAwsSidekick gets a single AwsSidekick integration matching the provided integration guid -func (svc *CloudAccountsService) GetAwsSidekick(guid string) ( - response AwsSidekickResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// CreateAwsSidekick creates an AwsSidekick Cloud Account integration -func (svc *CloudAccountsService) CreateAwsSidekick(data CloudAccount) ( - response AwsSidekickResponse, - err error, -) { - err = svc.create(data, &response) - return -} - -// UpdateAwsSidekick updates a single AwsSidekick integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAwsSidekick(data CloudAccount) ( - response AwsSidekickResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsSidekickResponse struct { - Data AwsSidekick `json:"data"` -} - -type AwsSidekick struct { - v2CommonIntegrationData - awsSidekickToken `json:"serverToken"` - Data AwsSidekickData `json:"data"` -} - -type awsSidekickToken struct { - ServerToken string `json:"serverToken"` - Uri string `json:"uri"` -} - -type AwsSidekickData struct { - //QueryText represents an lql json string - QueryText string `json:"queryText,omitempty"` - - //ScanFrequency in hours, 24 == 24 hours - ScanFrequency int `json:"scanFrequency"` - - ScanContainers bool `json:"scanContainers"` - ScanHostVulnerabilities bool `json:"scanHostVulnerabilities"` - ScanMultiVolume bool `json:"scanMultiVolume"` - ScanStoppedInstances bool `json:"scanStoppedInstances"` - ScanShortLivedInstances bool `json:"scanShortLivedInstances"` - - AccountID string `json:"awsAccountId,omitempty"` - BucketArn string `json:"bucketArn,omitempty"` - CrossAccountCreds AwsSidekickCrossAccountCredentials `json:"crossAccountCredentials"` -} - -type AwsSidekickCrossAccountCredentials struct { - RoleArn string `json:"roleArn,omitempty"` - ExternalID string `json:"externalId,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick_org.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick_org.go deleted file mode 100644 index 3cb750ba5..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_sidekick_org.go +++ /dev/null @@ -1,107 +0,0 @@ -// -// Author:: Teddy Reed () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/base64" - "fmt" - "strings" -) - -// GetAwsSidekickOrg gets a single AwsSidekickOrg integration matching the provided integration guid -func (svc *CloudAccountsService) GetAwsSidekickOrg(guid string) ( - response AwsSidekickOrgResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// CreateAwsSidekickOrg creates an AwsSidekickOrg Cloud Account integration -func (svc *CloudAccountsService) CreateAwsSidekickOrg(data CloudAccount) ( - response AwsSidekickOrgResponse, - err error, -) { - err = svc.create(data, &response) - return -} - -// UpdateAwsSidekickOrg updates a single AwsSidekickOrg integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAwsSidekickOrg(data CloudAccount) ( - response AwsSidekickOrgResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsSidekickOrgResponse struct { - Data AwsSidekickOrg `json:"data"` -} - -type AwsSidekickOrg struct { - v2CommonIntegrationData - awsSidekickToken `json:"serverToken"` - Data AwsSidekickOrgData `json:"data"` -} - -type AwsSidekickOrgData struct { - //QueryText represents an lql json string - QueryText string `json:"queryText,omitempty"` - - //ScanFrequency in hours, 24 == 24 hours - ScanFrequency int `json:"scanFrequency"` - - ScanContainers bool `json:"scanContainers"` - ScanHostVulnerabilities bool `json:"scanHostVulnerabilities"` - ScanMultiVolume bool `json:"scanMultiVolume"` - ScanStoppedInstances bool `json:"scanStoppedInstances"` - ScanShortLivedInstances bool `json:"scanShortLivedInstances"` - - //Properties specific to the AWS organization integration type - ScanningAccount string `json:"scanningAccount"` - ManagementAccount string `json:"managementAccount,omitempty"` - MonitoredAccounts string `json:"monitoredAccounts"` - - AccountID string `json:"awsAccountId,omitempty"` - BucketArn string `json:"bucketArn,omitempty"` - CrossAccountCreds AwsSidekickCrossAccountCredentials `json:"crossAccountCredentials"` - AccountMappingFile string `json:"accountMappingFile,omitempty"` -} - -func (aws *AwsSidekickOrgData) EncodeAccountMappingFile(mapping []byte) { - encodedMappings := base64.StdEncoding.EncodeToString(mapping) - aws.AccountMappingFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedMappings) -} - -func (aws *AwsSidekickOrgData) DecodeAccountMappingFile() ([]byte, error) { - if len(aws.AccountMappingFile) == 0 { - return []byte{}, nil - } - - var ( - b64 = strings.Split(aws.AccountMappingFile, ",") - raw, err = base64.StdEncoding.DecodeString(b64[1]) - ) - if err != nil { - return []byte{}, err - } - - return raw, nil -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_al.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_al.go deleted file mode 100644 index 39fefefab..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_al.go +++ /dev/null @@ -1,58 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAzureAlSeq gets a single AzureAlSeq integration matching the -// provided integration guid -func (svc *CloudAccountsService) GetAzureAlSeq(guid string) ( - response AzureAlSeqIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAzureAlSeq updates a single AzureAlSeq integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAzureAlSeq(data CloudAccount) ( - response AzureAlSeqIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AzureAlSeqIntegrationResponse struct { - Data AzureAlSeq `json:"data"` -} - -type AzureAlSeq struct { - v2CommonIntegrationData - Data AzureAlSeqData `json:"data"` -} - -type AzureAlSeqData struct { - Credentials AzureAlSeqCredentials `json:"credentials"` - TenantID string `json:"tenantId"` - QueueUrl string `json:"queueUrl"` -} - -type AzureAlSeqCredentials struct { - ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_cfg.go deleted file mode 100644 index 4c23e03f3..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_az_cfg.go +++ /dev/null @@ -1,57 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAzureCfg gets a single AzureCfg integration matching the -// provided integration guid -func (svc *CloudAccountsService) GetAzureCfg(guid string) ( - response AzureCfgIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAzureCfg updates a single AzureCfg integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAzureCfg(data CloudAccount) ( - response AzureCfgIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AzureCfgIntegrationResponse struct { - Data AzureCfg `json:"data"` -} - -type AzureCfg struct { - v2CommonIntegrationData - Data AzureCfgData `json:"data"` -} - -type AzureCfgData struct { - Credentials AzureCfgCredentials `json:"credentials"` - TenantID string `json:"tenantId"` -} - -type AzureCfgCredentials struct { - ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_ad_al.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_ad_al.go deleted file mode 100644 index be008791c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_ad_al.go +++ /dev/null @@ -1,59 +0,0 @@ -// -// Author:: Rubinder Singh () -// Copyright:: Copyright 2024, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAzureAdAl gets a single AzureAdAl integration matching the -// provided integration guid -func (svc *CloudAccountsService) GetAzureAdAl(guid string) ( - response AzureAdAlIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAzureAdAl updates a single AzureAdAl integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAzureAdAl(data CloudAccount) ( - response AzureAdAlIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AzureAdAlIntegrationResponse struct { - Data AzureAdAl `json:"data"` -} - -type AzureAdAl struct { - v2CommonIntegrationData - Data AzureAdAlData `json:"data"` -} - -type AzureAdAlData struct { - Credentials AzureAdAlCredentials `json:"credentials"` - TenantID string `json:"tenantId"` - EventHubNamespace string `json:"eventHubNamespace"` - EventHubName string `json:"eventHubName"` -} - -type AzureAdAlCredentials struct { - ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_sidekick.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_sidekick.go deleted file mode 100644 index b82d7d4e5..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_azure_sidekick.go +++ /dev/null @@ -1,88 +0,0 @@ -// -// Author:: Ao Zhang () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License.n -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -const ( - AzureSubscriptionIntegration string = "SUBSCRIPTION" - AzureTenantIntegration string = "TENANT" -) - -// GetAzureSidekick gets a single AzureSidekick integration matching the provided integration guid -func (svc *CloudAccountsService) GetAzureSidekick(guid string) ( - response AzureSidekickIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// CreateAzureSidekick creates an AzureSidekick Cloud Account integration -func (svc *CloudAccountsService) CreateAzureSidekick(data CloudAccount) ( - response AzureSidekickIntegrationResponse, - err error, -) { - err = svc.create(data, &response) - return -} - -// UpdateAzureSidekick updates a single AzureSidekick integration on the Lacework Server -func (svc *CloudAccountsService) UpdateAzureSidekick(data CloudAccount) ( - response AzureSidekickIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AzureSidekickIntegrationResponse struct { - Data V2AzureSidekickIntegration `json:"data"` -} - -type AzureSidekickToken struct { - ServerToken string `json:"serverToken"` - Uri string `json:"uri"` -} - -type V2AzureSidekickIntegration struct { - v2CommonIntegrationData - AzureSidekickToken `json:"serverToken"` - Data AzureSidekickData `json:"data"` -} - -type AzureSidekickData struct { - Credentials AzureSidekickCredentials `json:"credentials"` - IntegrationLevel string `json:"integrationLevel"` // SUBSCRIPTION or TENANT - ScanningSubscriptionId string `json:"scanningSubscriptionId"` - TenantId string `json:"tenantId"` - BlobContainerName string `json:"blobContainerName"` - ScanningResourceGroupName string `json:"scanningResourceGroupName"` - StorageAccountUrl string `json:"storageAccountUrl"` - SubscriptionsList string `json:"subscriptionsList,omitempty"` - QueryText string `json:"queryText,omitempty"` - ScanFrequency int `json:"scanFrequency"` // in hours - ScanContainers bool `json:"scanContainers"` - ScanHostVulnerabilities bool `json:"scanHostVulnerabilities"` - ScanMultiVolume bool `json:"scanMultiVolume"` - ScanStoppedInstances bool `json:"scanStoppedInstances"` -} - -type AzureSidekickCredentials struct { - ClientId string `json:"clientId"` - ClientSecret string `json:"clientSecret,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_al_pubsub.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_al_pubsub.go deleted file mode 100644 index 1013b052a..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_al_pubsub.go +++ /dev/null @@ -1,63 +0,0 @@ -// -// Author:: David McTavish() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetGcpAlPubSub gets a single GcpAlPubSub integration matching the provided integration guid -func (svc *CloudAccountsService) GetGcpAlPubSub(guid string) ( - response GcpAlPubSubIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateGcpAtSes updates a single GcpAtSes integration on the Lacework Server -func (svc *CloudAccountsService) UpdateGcpAlPubSub(data CloudAccount) ( - response GcpAlPubSubIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type GcpAlPubSubIntegrationResponse struct { - Data V2GcpAlPubSubIntegration `json:"data"` -} - -type V2GcpAlPubSubIntegration struct { - v2CommonIntegrationData - Data GcpAlPubSubSesData `json:"data"` -} - -type GcpAlPubSubSesData struct { - Credentials GcpAlPubSubCredentials `json:"credentials"` - IntegrationType string `json:"integrationType"` - // OrganizationId is optional for a project level integration, therefore we omit if empty - OrganizationID string `json:"organizationId,omitempty"` - ProjectID string `json:"projectId"` - SubscriptionName string `json:"subscriptionName"` - TopicID string `json:"topicId"` -} - -type GcpAlPubSubCredentials struct { - ClientID string `json:"clientId"` - ClientEmail string `json:"clientEmail"` - PrivateKeyID string `json:"privateKeyId"` - PrivateKey string `json:"privateKey,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_at.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_at.go deleted file mode 100644 index 5d326c18e..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_at.go +++ /dev/null @@ -1,77 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "time" - -// GetGcpAtSes gets a single GcpAtSes integration matching the provided integration guid -func (svc *CloudAccountsService) GetGcpAtSes(guid string) ( - response GcpAtSesIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateGcpAtSes updates a single GcpAtSes integration on the Lacework Server -func (svc *CloudAccountsService) UpdateGcpAtSes(data CloudAccount) ( - response GcpAtSesIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type GcpAtSesIntegrationResponse struct { - Data V2GcpAtSesIntegration `json:"data"` -} - -type V2GcpAtSesIntegration struct { - v2CommonIntegrationData - Data GcpAtSesData `json:"data"` -} - -type GcpAtSesData struct { - Credentials GcpAtSesCredentials `json:"credentials"` - IDType string `json:"idType"` - // Either the org id or project id - ID string `json:"id"` - SubscriptionName string `json:"subscriptionName"` -} - -type GcpAtSesCredentials struct { - ClientID string `json:"clientId"` - ClientEmail string `json:"clientEmail"` - PrivateKeyID string `json:"privateKeyId,omitempty"` - PrivateKey string `json:"privateKey,omitempty"` -} - -type Props struct { - Migrate bool `json:"migrate"` - MigrationTimestamp time.Time `json:"migrationTimestamp"` -} - -type MigrateData struct { - IntgGuid string `json:"intgGuid"` - Props Props `json:"props"` -} - -type MigrateRequestData struct { - Data MigrateData `json:"data"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_cfg.go deleted file mode 100644 index d1c3131c5..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_cfg.go +++ /dev/null @@ -1,80 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// gcpResourceLevel determines Project or Organization level integration -type gcpResourceLevel int - -const ( - // Project level integration with GCP - GcpProjectIntegration gcpResourceLevel = iota - - // Organization level integration with GCP - GcpOrganizationIntegration -) - -var gcpResourceLevels = map[gcpResourceLevel]string{ - GcpProjectIntegration: "PROJECT", - GcpOrganizationIntegration: "ORGANIZATION", -} - -func (g gcpResourceLevel) String() string { - return gcpResourceLevels[g] -} - -// GetGcpCfg gets a single GcpCfg integration matching the provided integration guid -func (svc *CloudAccountsService) GetGcpCfg(guid string) ( - response GcpCfgIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateGcpCfg updates a single GcpCfg integration on the Lacework Server -func (svc *CloudAccountsService) UpdateGcpCfg(data CloudAccount) ( - response GcpCfgIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type GcpCfgIntegrationResponse struct { - Data V2GcpCfgIntegration `json:"data"` -} - -type V2GcpCfgIntegration struct { - v2CommonIntegrationData - Data GcpCfgData `json:"data"` -} - -type GcpCfgData struct { - Credentials GcpCfgCredentials `json:"credentials"` - IDType string `json:"idType"` - // Either the org id or project id - ID string `json:"id"` -} - -type GcpCfgCredentials struct { - ClientID string `json:"clientId"` - ClientEmail string `json:"clientEmail"` - PrivateKeyID string `json:"privateKeyId,omitempty"` - PrivateKey string `json:"privateKey,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_gke_audit.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_gke_audit.go deleted file mode 100644 index d5f3ab15c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_gke_audit.go +++ /dev/null @@ -1,62 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetGcpGkeAudit gets a single GcpGkeAudit integration matching the provided integration guid -func (svc *CloudAccountsService) GetGcpGkeAudit(guid string) ( - response GcpGkeAuditIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateGcpGkeAudit updates a single GcpGkeAudit integration on the Lacework Server -func (svc *CloudAccountsService) UpdateGcpGkeAudit(data CloudAccount) ( - response GcpGkeAuditIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type GcpGkeAuditIntegrationResponse struct { - Data GcpGkeAuditIntegration `json:"data"` -} - -type GcpGkeAuditIntegration struct { - v2CommonIntegrationData - Data GcpGkeAuditData `json:"data"` -} - -type GcpGkeAuditData struct { - Credentials GcpGkeAuditCredentials `json:"credentials"` - IntegrationType string `json:"integrationType"` - // OrganizationId is optional for a project level integration, therefore we omit if empty - OrganizationId string `json:"organizationId,omitempty"` - ProjectId string `json:"projectId"` - SubscriptionName string `json:"subscriptionName"` -} - -type GcpGkeAuditCredentials struct { - ClientId string `json:"clientId"` - ClientEmail string `json:"clientEmail"` - PrivateKeyId string `json:"privateKeyId"` - PrivateKey string `json:"privateKey"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_sidekick.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_sidekick.go deleted file mode 100644 index fa5cd93ab..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_gcp_sidekick.go +++ /dev/null @@ -1,115 +0,0 @@ -// -// Author:: Ammar Ekbote() -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/base64" - "fmt" - "strings" -) - -// GetGcpSidekick gets a single GcpSidekick integration matching the provided integration guid -func (svc *CloudAccountsService) GetGcpSidekick(guid string) ( - response GcpSidekickIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// CreateGcpSidekick creates an GcpSidekick Cloud Account integration -func (svc *CloudAccountsService) CreateGcpSidekick(data CloudAccount) ( - response GcpSidekickIntegrationResponse, - err error, -) { - err = svc.create(data, &response) - return -} - -// UpdateGcpSidekick updates a single GcpSidekick integration on the Lacework Server -func (svc *CloudAccountsService) UpdateGcpSidekick(data CloudAccount) ( - response GcpSidekickIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type GcpSidekickIntegrationResponse struct { - Data V2GcpSidekickIntegration `json:"data"` -} - -type GcpSidekickToken struct { - ServerToken string `json:"serverToken"` - Uri string `json:"uri"` -} - -type V2GcpSidekickIntegration struct { - v2CommonIntegrationData - GcpSidekickToken `json:"serverToken"` - Data GcpSidekickData `json:"data"` -} - -type GcpSidekickData struct { - Credentials GcpSidekickCredentials `json:"credentials"` - IDType string `json:"idType"` - // Either the org id or project id - ID string `json:"id"` - ScanningProjectId string `json:"scanningProjectId"` - SharedBucket string `json:"sharedBucketName"` - FilterList string `json:"filterList,omitempty"` - QueryText string `json:"queryText,omitempty"` - //ScanFrequency in hours, 24 == 24 hours - ScanFrequency int `json:"scanFrequency"` - ScanContainers bool `json:"scanContainers"` - ScanHostVulnerabilities bool `json:"scanHostVulnerabilities"` - ScanMultiVolume bool `json:"scanMultiVolume"` - ScanStoppedInstances bool `json:"scanStoppedInstances"` - - AccountMappingFile string `json:"accountMappingFile,omitempty"` -} - -type GcpSidekickCredentials struct { - ClientID string `json:"clientId"` - ClientEmail string `json:"clientEmail"` - PrivateKeyID string `json:"privateKeyId,omitempty"` - PrivateKey string `json:"privateKey,omitempty"` - TokenUri string `json:"tokenUri,omitempty"` -} - -func (gcp *GcpSidekickData) EncodeAccountMappingFile(mapping []byte) { - encodedMappings := base64.StdEncoding.EncodeToString(mapping) - gcp.AccountMappingFile = fmt.Sprintf("data:application/json;name=i.json;base64,%s", encodedMappings) -} - -func (gcp *GcpSidekickData) DecodeAccountMappingFile() ([]byte, error) { - if len(gcp.AccountMappingFile) == 0 { - return []byte{}, nil - } - - var ( - b64 = strings.Split(gcp.AccountMappingFile, ",") - raw, err = base64.StdEncoding.DecodeString(b64[1]) - ) - if err != nil { - return []byte{}, err - } - - return raw, nil -} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_oci_cfg.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_oci_cfg.go deleted file mode 100644 index b42062d70..000000000 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_oci_cfg.go +++ /dev/null @@ -1,60 +0,0 @@ -// -// Author:: Kolbeinn Karlsson () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetOciCfg gets a single OciCfg integration matching the -// provided integration guid -func (svc *CloudAccountsService) GetOciCfg(guid string) ( - response OciCfgIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateOciCfg updates a single OciCfg integration on the Lacework Server -func (svc *CloudAccountsService) UpdateOciCfg(data CloudAccount) ( - response OciCfgIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type OciCfgIntegrationResponse struct { - Data OciCfg `json:"data"` -} - -type OciCfg struct { - v2CommonIntegrationData - Data OciCfgData `json:"data"` -} - -type OciCfgData struct { - Credentials OciCfgCredentials `json:"credentials"` - HomeRegion string `json:"homeRegion"` - TenantID string `json:"tenantId"` - TenantName string `json:"tenantName"` - UserOCID string `json:"userOcid"` -} - -type OciCfgCredentials struct { - Fingerprint string `json:"fingerprint"` - PrivateKey string `json:"privateKey,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/compliance_evaluations.go b/vendor/github.com/lacework/go-sdk/api/compliance_evaluations.go deleted file mode 100644 index 12f1e1aae..000000000 --- a/vendor/github.com/lacework/go-sdk/api/compliance_evaluations.go +++ /dev/null @@ -1,68 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "time" - -type ComplianceEvaluationService struct { - client *Client -} - -type complianceEvaluationDataset string - -const AwsComplianceEvaluationDataset complianceEvaluationDataset = "AwsCompliance" - -// Search expects the response and the search filters -// -// e.g. -// -// var ( -// awsComplianceEvaluationSearchResponse api.ComplianceEvaluationAwsResponse -// filter = api.ComplianceEvaluationSearch{ -// SearchFilter: api.SearchFilter{ -// Filters: []api.Filter{{ -// Expression: "eq", -// Field: "resource", -// Value: arn:aws:s3:::my-bucket, -// }}, -// }, -// Dataset: api.AwsComplianceEvaluationDataset, -// } -// ) -// lacework.V2.ComplianceEvaluation.Search(&awsComplianceEvaluationSearchResponse, filters) -func (svc *ComplianceEvaluationService) Search(response interface{}, filters SearchableFilter) error { - return svc.client.RequestEncoderDecoder("POST", apiV2ComplianceEvaluationsSearch, filters, response) -} - -func (c *ComplianceEvaluationSearch) GetTimeFilter() *TimeFilter { - return c.TimeFilter -} - -func (c *ComplianceEvaluationSearch) SetStartTime(t *time.Time) { - c.TimeFilter.StartTime = t -} - -func (c *ComplianceEvaluationSearch) SetEndTime(t *time.Time) { - c.TimeFilter.EndTime = t -} - -type ComplianceEvaluationSearch struct { - SearchFilter - Dataset complianceEvaluationDataset `json:"dataset"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/compliance_evaluations_aws.go b/vendor/github.com/lacework/go-sdk/api/compliance_evaluations_aws.go deleted file mode 100644 index f3106f2c1..000000000 --- a/vendor/github.com/lacework/go-sdk/api/compliance_evaluations_aws.go +++ /dev/null @@ -1,54 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "time" - -type ComplianceEvaluationAwsResponse struct { - Data []ComplianceEvaluationAws `json:"data"` - Paging V2Pagination `json:"paging"` -} - -func (r ComplianceEvaluationAwsResponse) GetDataLength() int { - return len(r.Data) -} - -func (r ComplianceEvaluationAwsResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *ComplianceEvaluationAwsResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -type ComplianceEvaluationAws struct { - Account struct { - AccountId string `json:"AccountId"` - AccountAlias string `json:"Account_Alias"` - } `json:"account"` - EvalType string `json:"evalType"` - Id string `json:"id"` - Reason string `json:"reason"` - Recommendation string `json:"recommendation"` - ReportTime time.Time `json:"reportTime"` - Resource string `json:"resource"` - Section string `json:"section"` - Severity string `json:"severity"` - Status string `json:"status"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/component_data.go b/vendor/github.com/lacework/go-sdk/api/component_data.go deleted file mode 100644 index b434d69e5..000000000 --- a/vendor/github.com/lacework/go-sdk/api/component_data.go +++ /dev/null @@ -1,208 +0,0 @@ -package api - -import ( - "bytes" - "io" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/pkg/errors" -) - -type ComponentDataService struct { - client *Client -} - -const URL_TYPE_DEFAULT = "Default" -const URL_TYPE_SAST_TABLES = "SastTables" -const URL_TYPE_PROSAST = "ProSast" - -var URL_TYPES = []string{URL_TYPE_DEFAULT, URL_TYPE_SAST_TABLES, URL_TYPE_PROSAST} - -type ComponentDataInitialRequest struct { - Name string `json:"name"` - Tags []string `json:"tags"` - SupportedMethods []string `json:"supportedMethods"` - Documents []*DocumentSpec `json:"documents"` - UrlType string `json:"urlType"` -} - -type DocumentSpec struct { - Name string `json:"name"` - Size int64 `json:"size"` -} - -type ComponentDataInitialResponseRaw struct { - Data *ComponentDataInitialResponse `json:"data,omitempty"` -} - -type ComponentDataInitialResponse struct { - Guid string `json:"guid,omitempty"` - UploadMethods []*ComponentDataUploadMethod `json:"uploadMethods,omitempty"` -} - -type ComponentDataUploadMethod struct { - Method string `json:"method,omitempty"` - Info map[string]string `json:"info,omitempty"` -} - -type ComponentDataCompleteRequest struct { - UploadGuid string `json:"uploadGuid"` - UrlType string `json:"urlType"` -} - -type ComponentDataCompleteResponseRaw struct { - Data *ComponentDataCompleteResponse `json:"data,omitempty"` -} - -type ComponentDataCompleteResponse struct { - Guid string `json:"guid,omitempty"` -} - -func (svc *ComponentDataService) UploadFiles( - name string, tags []string, paths []string) (string, error) { - return svc.doUploadFiles(name, tags, paths, URL_TYPE_DEFAULT) -} - -func (svc *ComponentDataService) UploadSastTables( - name string, paths []string) (string, error) { - return svc.doUploadFiles(name, []string{"sast"}, paths, URL_TYPE_SAST_TABLES) -} - -func (svc *ComponentDataService) UploadProSast(name string, paths []string) (string, error) { - return svc.doUploadFiles(name, []string{"sast"}, paths, URL_TYPE_PROSAST) -} - -func (svc *ComponentDataService) doUploadFiles( - name string, tags []string, paths []string, urlType string) (string, error) { - var hasValidType = false - for _, validType := range URL_TYPES { - if urlType == validType { - hasValidType = true - break - } - } - if !hasValidType { - return "", errors.Errorf("Invalid URL type: (%s)", urlType) - } - initialRequest, err := buildComponentDataInitialRequest(name, tags, paths, urlType) - if err != nil { - return "", err - } - var initialResponse ComponentDataInitialResponseRaw - err = doWithExponentialBackoffWaiting(func() error { - return svc.client.RequestEncoderDecoder(http.MethodPost, - apiV2ComponentDataRequest, - initialRequest, - &initialResponse, - ) - }) - if err != nil { - return "", err - } - var chosenMethod *ComponentDataUploadMethod - for _, method := range initialResponse.Data.UploadMethods { - if method.Method == "AwsS3" { - chosenMethod = method - } - } - if chosenMethod == nil { - return "", errors.New("couldn't find a supported upload method in the upload request response") - } - for _, path := range paths { - err = doWithExponentialBackoffWaiting(func() error { - return svc.putFileToS3(path, chosenMethod.Info) - }) - if err != nil { - return "", err - } - } - completeRequest := ComponentDataCompleteRequest{ - UploadGuid: initialResponse.Data.Guid, - UrlType: urlType, - } - var completeResponse ComponentDataCompleteResponseRaw - err = doWithExponentialBackoffWaiting(func() error { - return svc.client.RequestEncoderDecoder(http.MethodPost, - apiV2ComponentDataComplete, - completeRequest, - &completeResponse, - ) - }) - if err != nil { - return "", err - } - if initialResponse.Data.Guid != completeResponse.Data.Guid { - return "", errors.New("expected the initial GUID and the one returned on completion to match") - } - return initialResponse.Data.Guid, nil -} - -func buildComponentDataInitialRequest( - name string, tags []string, paths []string, urlType string, -) (*ComponentDataInitialRequest, error) { - documents := make([]*DocumentSpec, 0, len(paths)) - for _, path := range paths { - info, err := os.Lstat(path) - if err != nil { - return nil, err - } - documents = append(documents, &DocumentSpec{ - Name: filepath.Base(path), - Size: info.Size(), - }) - } - return &ComponentDataInitialRequest{ - Name: name, - Tags: tags, - SupportedMethods: []string{"AwsS3"}, - Documents: documents, - UrlType: urlType, - }, nil -} - -func (svc *ComponentDataService) putFileToS3(path string, uploadUrls map[string]string) error { - contents, err := os.ReadFile(path) - if err != nil { - return err - } - req, err := http.NewRequest(http.MethodPut, uploadUrls[filepath.Base(path)], bytes.NewReader(contents)) - if err != nil { - return err - } - resp, err := svc.client.Do(req) - if err != nil { - return err - } - if resp.StatusCode != http.StatusOK { - body, err := io.ReadAll(resp.Body) - if err == nil { - return errors.Errorf("Upload to S3 failed (%s): %s", resp.Status, body) - } - return errors.Errorf("Upload to S3 failed (%s)", resp.Status) - } - return nil -} - -func doWithExponentialBackoffWaiting(f func() error) error { - return DoWithExponentialBackoff(f, func(x int) { - time.Sleep(time.Duration(x) * time.Second) - }) -} - -func DoWithExponentialBackoff(f func() error, wait func(x int)) error { - err := f() - if err == nil { - return nil - } - for waitTime := 2; waitTime < 60; waitTime *= 2 { - wait(waitTime) - err := f() - if err == nil { - return nil - } - } - return err -} diff --git a/vendor/github.com/lacework/go-sdk/api/components.go b/vendor/github.com/lacework/go-sdk/api/components.go deleted file mode 100644 index f80946bbd..000000000 --- a/vendor/github.com/lacework/go-sdk/api/components.go +++ /dev/null @@ -1,81 +0,0 @@ -package api - -import "fmt" - -type ComponentsService struct { - client *Client -} - -type ListComponentsResponse struct { - Data []LatestComponent `json:"data"` - Message string `json:"message"` -} - -type LatestComponent struct { - Components []LatestComponentVersion `json:"components"` -} - -type LatestComponentVersion struct { - Id int32 `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Version string `json:"version"` - Size int64 `json:"size"` - ComponentType string `json:"type"` - Deprecated bool `json:"deprecated"` -} - -func (svc *ComponentsService) ListComponents(os string, arch string) (response ListComponentsResponse, err error) { - apiPath := fmt.Sprintf(apiV2Components, os, arch) - - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - - return -} - -type ListComponentVersionsResponse struct { - Data []ComponentVersions `json:"data"` -} - -type ComponentVersions struct { - Id int32 `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Component_type string `json:"type"` - Deprecated bool `json:"deprecated"` - Versions []string `json:"versions"` -} - -func (svc *ComponentsService) ListComponentVersions(id int32, os string, arch string) ( - response ListComponentVersionsResponse, - err error) { - apiPath := fmt.Sprintf(apiV2ComponentsVersions, id, os, arch) - - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - - return -} - -type FetchComponentResponse struct { - Data []Artifact `json:"data"` -} - -type Artifact struct { - Id int32 `json:"id"` - Name string `json:"name"` - Version string `json:"version"` - Size int64 `json:"size"` - InstallMessage string `json:"installMessage"` - UpdateMessage string `json:"updateMessage"` - ArtifactUrl string `json:"artifact_url"` -} - -func (svc *ComponentsService) FetchComponentArtifact(id int32, os string, arch string, version string) ( - response FetchComponentResponse, - err error) { - apiPath := fmt.Sprintf(apiV2ComponentsFetch, id, os, arch, version) - - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries.go b/vendor/github.com/lacework/go-sdk/api/container_registries.go deleted file mode 100644 index 0e28c3e37..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries.go +++ /dev/null @@ -1,275 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/fatih/structs" - "github.com/pkg/errors" -) - -// ContainerRegistriesService is the service that interacts with -// the ContainerRegistries schema from the Lacework APIv2 Server -type ContainerRegistriesService struct { - client *Client -} - -// NewContainerRegistry returns an instance of the ContainerRegistryRaw struct with the -// provided Container Registry integration type, name and raw data as an interface{}. -// -// NOTE: This function must be used by any Container Registry type. -// -// Basic usage: Initialize a new GhcrContainerRegistry integration struct, then -// -// use the new instance to do CRUD operations -// -// client, err := api.NewClient("account") -// if err != nil { -// return err -// } -// -// ghcr := api.NewContainerRegistry("foo", -// api.GhcrContainerRegistry, -// api.GhcrData{ -// Credentials: api.GhcrCredentials { -// Username: "bubu", -// Password: "supers3cret", -// Ssl: true, -// }, -// }, -// ) -// -// client.V2.ContainerRegistries.Create(ghcr) -func NewContainerRegistry(name string, regType containerRegistryType, data interface{}) ContainerRegistryRaw { - reg := ContainerRegistryRaw{ - v2CommonIntegrationData: v2CommonIntegrationData{ - Name: name, - Type: "ContVulnCfg", - Enabled: 1, - }, - } - - switch regType { - case GcpGarContainerRegistry: - reg.Data = verifyGcpGarContainerRegistry(data) - case GhcrContainerRegistry: - reg.Data = verifyGhcrContainerRegistry(data) - case InlineScannerContainerRegistry: - reg.Data = verifyInlineScannerContainerRegistry(data) - case ProxyScannerContainerRegistry: - reg.Data = verifyProxyScannerContainerRegistry(data) - case AwsEcrContainerRegistry: - reg.Data = verifyAwsEcrContainerRegistry(data) - case DockerhubContainerRegistry: - reg.Data = verifyDockerhubContainerRegistry(data) - case DockerhubV2ContainerRegistry: - reg.Data = verifyDockerhubV2ContainerRegistry(data) - case GcpGcrContainerRegistry: - reg.Data = verifyGcpGcrContainerRegistry(data) - default: - reg.Data = data - } - - return reg -} - -// ContainerRegistry is an interface that helps us implement a few functions -// that any Container Registry might use, there are some cases, like during -// Update, where we need to get the ID of the Container Registry and its type, -// this will allow users to pass any Container Registry that implements these -// methods -type ContainerRegistry interface { - ID() string - ContainerRegistryType() containerRegistryType -} - -type containerRegistryType int - -const ( - // type that defines a non-existing Container Registry integration - NoneContainerRegistry containerRegistryType = iota - GcpGarContainerRegistry - GhcrContainerRegistry - InlineScannerContainerRegistry - ProxyScannerContainerRegistry - AwsEcrContainerRegistry - DockerhubContainerRegistry - DockerhubV2ContainerRegistry - GcpGcrContainerRegistry -) - -// ContainerRegistryTypes is the list of available Container Registry integration types -var ContainerRegistryTypes = map[containerRegistryType]string{ - NoneContainerRegistry: "None", - GcpGarContainerRegistry: "GCP_GAR", - GhcrContainerRegistry: "GHCR", - InlineScannerContainerRegistry: "INLINE_SCANNER", - ProxyScannerContainerRegistry: "PROXY_SCANNER", - AwsEcrContainerRegistry: "AWS_ECR", - DockerhubContainerRegistry: "DOCKERHUB", - DockerhubV2ContainerRegistry: "V2_REGISTRY", - GcpGcrContainerRegistry: "GCP_GCR", -} - -// String returns the string representation of a Container Registry integration type -func (i containerRegistryType) String() string { - return ContainerRegistryTypes[i] -} - -// FindContainerRegistryType looks up inside the list of available container registry types -// the matching type from the provided string, if none, returns NoneContainerRegistry -func FindContainerRegistryType(containerRegistry string) (containerRegistryType, bool) { - for cType, cStr := range ContainerRegistryTypes { - if cStr == containerRegistry { - return cType, true - } - } - return NoneContainerRegistry, false -} - -// List returns a list of Container Registry integrations -func (svc *ContainerRegistriesService) List() (response ContainerRegistriesResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2ContainerRegistries, nil, &response) - return -} - -// Create creates a single Container Registry integration -func (svc *ContainerRegistriesService) Create(integration ContainerRegistryRaw) ( - response ContainerRegistryResponse, - err error, -) { - err = svc.create(integration, &response) - return -} - -// Delete deletes a Container Registry integration that matches the provided guid -func (svc *ContainerRegistriesService) Delete(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2ContainerRegistryFromGUID, guid), - nil, - nil, - ) -} - -// Get returns a raw response of the Container Registry with the matching integration guid. -// -// To return a more specific Go struct of a Container Registry integration, use the proper -// method such as GetGhcr() where the function name is composed by: -// -// Get(guid) -// -// Where is the Container Registry integration type. -func (svc *ContainerRegistriesService) Get(guid string, response interface{}) error { - return svc.get(guid, &response) -} - -type ContainerRegistryRaw struct { - v2CommonIntegrationData - Data interface{} `json:"data,omitempty"` - ServerToken *V2ServerToken `json:"serverToken,omitempty"` -} - -func (reg ContainerRegistryRaw) StateString() string { - switch reg.ContainerRegistryType() { - case InlineScannerContainerRegistry, ProxyScannerContainerRegistry: - return "Ok" - default: - return reg.v2CommonIntegrationData.StateString() - } -} - -type V2ServerToken struct { - ServerToken string `json:"serverToken"` - Uri string `json:"uri"` -} - -func (reg ContainerRegistryRaw) GetData() any { - return reg.Data -} - -func (reg ContainerRegistryRaw) GetCommon() v2CommonIntegrationData { - return reg.v2CommonIntegrationData -} - -func (reg ContainerRegistryRaw) ContainerRegistryType() containerRegistryType { - if casting, ok := reg.Data.(map[string]interface{}); ok { - if regType, exist := casting["registryType"]; exist { - t, _ := FindContainerRegistryType(regType.(string)) - return t - } - } - - m := structs.Map(reg.Data) - if regType, exist := m["RegistryType"]; exist { - t, _ := FindContainerRegistryType(regType.(string)) - return t - } - - return NoneContainerRegistry -} - -func (reg ContainerRegistryRaw) ContainerRegistryDomain() string { - if casting, ok := reg.Data.(map[string]interface{}); ok { - if domain, exist := casting["registryDomain"]; exist { - return domain.(string) - } - } - - if structs.IsStruct(reg.Data) { - m := structs.Map(reg.Data) - if domain, exist := m["RegistryDomain"]; exist { - return domain.(string) - } - } - return "" -} - -type ContainerRegistryResponse struct { - Data ContainerRegistryRaw `json:"data"` -} - -type ContainerRegistriesResponse struct { - Data []ContainerRegistryRaw `json:"data"` -} - -func (svc *ContainerRegistriesService) create(data interface{}, response interface{}) error { - return svc.client.RequestEncoderDecoder("POST", apiV2ContainerRegistries, data, response) -} - -func (svc *ContainerRegistriesService) get(guid string, response interface{}) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - apiPath := fmt.Sprintf(apiV2ContainerRegistryFromGUID, guid) - return svc.client.RequestDecoder("GET", apiPath, nil, response) -} - -func (svc *ContainerRegistriesService) update(guid string, data interface{}, response interface{}) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - apiPath := fmt.Sprintf(apiV2ContainerRegistryFromGUID, guid) - return svc.client.RequestEncoderDecoder("PATCH", apiPath, data, response) -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_access_key.go b/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_access_key.go deleted file mode 100644 index bd392f080..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_access_key.go +++ /dev/null @@ -1,98 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -type ecrAuthType int - -const ( - AwsEcrIAM ecrAuthType = iota - AwsEcrAccessKey -) - -// AwsEcrAuthTypes is the list of available ECR auth types -var AwsEcrAuthTypes = map[ecrAuthType]string{ - AwsEcrIAM: "AWS_IAM", - AwsEcrAccessKey: "AWS_ACCESS_KEY", -} - -// String returns the string representation of an ECR auth type -func (i ecrAuthType) String() string { - return AwsEcrAuthTypes[i] -} - -// GetAwsEcrAccessKey gets a single AwsEcrAccessKey integration with access key credentials matching the -// provided integration guid -func (svc *ContainerRegistriesService) GetAwsEcrAccessKey(guid string) ( - response AwsEcrAccessKeyIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAwsEcrAccessKey updates a single AwsEcrAccessKey integration with access key credential on the Lacework Server -func (svc *ContainerRegistriesService) UpdateAwsEcrAccessKey(data ContainerRegistry) ( - response AwsEcrAccessKeyIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsEcrAccessKeyIntegrationResponse struct { - Data AwsEcrIntegration `json:"data"` -} - -type AwsEcrIntegration struct { - v2CommonIntegrationData - Data AwsEcrAccessKeyData `json:"data"` -} - -type AwsEcrAccessKeyData struct { - AccessKeyCredentials AwsEcrAccessKeyCredentials `json:"accessKeyCredentials,omitempty"` - RegistryDomain string `json:"registryDomain"` - LimitByTag []string `json:"limitByTag,omitempty"` - LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` - LimitByRep []string `json:"limitByRep,omitempty"` - LimitNumImg int `json:"limitNumImg"` - NonOSPackageEval bool `json:"nonOsPackageEval"` - AwsAuthType string `json:"awsAuthType"` - RegistryType string `json:"registryType"` -} - -func verifyAwsEcrContainerRegistry(data interface{}) interface{} { - if ecr, ok := data.(AwsEcrAccessKeyData); ok { - ecr.RegistryType = AwsEcrContainerRegistry.String() - ecr.AwsAuthType = AwsEcrAccessKey.String() - return ecr - } - - if ecr, ok := data.(AwsEcrIamRoleData); ok { - ecr.RegistryType = AwsEcrContainerRegistry.String() - ecr.AwsAuthType = AwsEcrIAM.String() - return ecr - } - - return data -} - -type AwsEcrAccessKeyCredentials struct { - AccessKeyID string `json:"accessKeyId,omitempty"` - SecretAccessKey string `json:"secretAccessKey,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_iam_role.go b/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_iam_role.go deleted file mode 100644 index e685e9abc..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries_aws_ecr_iam_role.go +++ /dev/null @@ -1,69 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetAwsEcrIamRole gets a single AwsEcr with Iam Role credentials integration matching the -// provided integration guid -func (svc *ContainerRegistriesService) GetAwsEcrIamRole(guid string) ( - response AwsEcrIamRoleIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateAwsEcrIamRole updates a single AwsEcr with Iam Role credentials integration on the Lacework Server -func (svc *ContainerRegistriesService) UpdateAwsEcrIamRole(data ContainerRegistry) ( - response AwsEcrIamRoleIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type AwsEcrIamRoleIntegrationResponse struct { - Data AwsEcrIamRoleIntegration `json:"data"` -} - -type AwsEcrIamRoleIntegration struct { - v2CommonIntegrationData - Data AwsEcrIamRoleData `json:"data"` -} - -func (reg AwsEcrIamRoleIntegration) ContainerRegistryType() containerRegistryType { - t, _ := FindContainerRegistryType(reg.Data.RegistryType) - return t -} - -type AwsEcrIamRoleData struct { - CrossAccountCredentials AwsEcrCrossAccountCredentials `json:"crossAccountCredentials,omitempty"` - RegistryDomain string `json:"registryDomain"` - RegistryType string `json:"registryType"` - LimitByTag []string `json:"limitByTag,omitempty"` - LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` - LimitByRep []string `json:"limitByRep,omitempty"` - LimitNumImg int `json:"limitNumImg"` - NonOSPackageEval bool `json:"nonOsPackageEval"` - AwsAuthType string `json:"awsAuthType"` -} - -type AwsEcrCrossAccountCredentials struct { - RoleArn string `json:"roleArn,omitempty"` - ExternalID string `json:"externalId,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub.go b/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub.go deleted file mode 100644 index 7bf6c467d..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub.go +++ /dev/null @@ -1,77 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetDockerhub gets a single Dockerhub integration matching the -// provided integration guid -func (svc *ContainerRegistriesService) GetDockerhub(guid string) ( - response DockerhubIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateDockerhub updates a single Dockerhub integration on the Lacework Server -func (svc *ContainerRegistriesService) UpdateDockerhub(data ContainerRegistry) ( - response DockerhubIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type DockerhubIntegrationResponse struct { - Data DockerhubIntegration `json:"data"` -} - -type DockerhubIntegration struct { - v2CommonIntegrationData - Data DockerhubData `json:"data"` -} - -func (reg DockerhubIntegration) ContainerRegistryType() containerRegistryType { - t, _ := FindContainerRegistryType(reg.Data.RegistryType) - return t -} - -type DockerhubData struct { - Credentials DockerhubCredentials `json:"credentials"` - RegistryDomain string `json:"registryDomain"` // always "index.docker.io" - RegistryType string `json:"registryType"` // always "DOCKERHUB" - LimitByTag []string `json:"limitByTag,omitempty"` - LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` - LimitByRep []string `json:"limitByRep,omitempty"` - LimitNumImg int `json:"limitNumImg"` - NonOSPackageEval bool `json:"nonOsPackageEval"` -} - -func verifyDockerhubContainerRegistry(data interface{}) interface{} { - if hub, ok := data.(DockerhubData); ok { - hub.RegistryType = DockerhubContainerRegistry.String() - hub.RegistryDomain = "index.docker.io" - return hub - } - return data -} - -type DockerhubCredentials struct { - Username string `json:"username"` - Password string `json:"password"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub_v2.go b/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub_v2.go deleted file mode 100644 index 699bdd944..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries_dockerhub_v2.go +++ /dev/null @@ -1,76 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetDockerhubV2 gets a single DockerhubV2 integration matching the -// provided integration guid -func (svc *ContainerRegistriesService) GetDockerhubV2(guid string) ( - response DockerhubV2IntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateDockerhubV2 updates a single DockerhubV2 integration on the Lacework Server -func (svc *ContainerRegistriesService) UpdateDockerhubV2(data ContainerRegistry) ( - response DockerhubV2IntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type DockerhubV2IntegrationResponse struct { - Data DockerhubV2Integration `json:"data"` -} - -type DockerhubV2Integration struct { - v2CommonIntegrationData - Data DockerhubV2Data `json:"data"` -} - -func (reg DockerhubV2Integration) ContainerRegistryType() containerRegistryType { - t, _ := FindContainerRegistryType(reg.Data.RegistryType) - return t -} - -type DockerhubV2Data struct { - Credentials DockerhubV2Credentials `json:"credentials"` - RegistryDomain string `json:"registryDomain"` - RegistryType string `json:"registryType"` - RegistryNotifications *bool `json:"registryNotifications,omitempty"` - LimitByTag []string `json:"limitByTag,omitempty"` - LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` - NonOSPackageEval bool `json:"nonOsPackageEval"` -} - -func verifyDockerhubV2ContainerRegistry(data interface{}) interface{} { - if ecr, ok := data.(DockerhubV2Data); ok { - ecr.RegistryType = DockerhubV2ContainerRegistry.String() - return ecr - } - return data -} - -type DockerhubV2Credentials struct { - Username string `json:"username"` - Password string `json:"password"` - SSL bool `json:"ssl"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gar.go b/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gar.go deleted file mode 100644 index 4ef93c6c8..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gar.go +++ /dev/null @@ -1,81 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetGcpGar gets a single GcpGar integration matching the -// provided integration guid -func (svc *ContainerRegistriesService) GetGcpGar(guid string) ( - response GcpGarIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateGcpGar updates a single GcpGar integration on the Lacework Server -func (svc *ContainerRegistriesService) UpdateGcpGar(data ContainerRegistry) ( - response GcpGarIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type GcpGarIntegrationResponse struct { - Data GcpGarIntegration `json:"data"` -} - -type GcpGarIntegration struct { - v2CommonIntegrationData - Data GcpGarData `json:"data"` -} - -func (reg GcpGarIntegration) ContainerRegistryType() containerRegistryType { - t, _ := FindContainerRegistryType(reg.Data.RegistryType) - return t -} - -type GcpGarData struct { - Credentials GcpCredentialsV2 `json:"credentials"` - RegistryDomain string `json:"registryDomain"` - RegistryType string `json:"registryType"` // always "GCP_GAR" - LimitByTag []string `json:"limitByTag,omitempty"` - LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` - LimitByRep []string `json:"limitByRep,omitempty"` - LimitNumImg int `json:"limitNumImg"` - NonOSPackageEval bool `json:"nonOsPackageEval"` -} - -func verifyGcpGarContainerRegistry(data interface{}) interface{} { - if gar, ok := data.(GcpGarData); ok { - gar.RegistryType = GcpGarContainerRegistry.String() - return gar - } - return data -} - -// GcpCredentials is already defined in api/integrations_gcp.go:163 -// so we need to add a "V2" at the end to make it clear that this is -// the Google Credentials struct for API v2 -type GcpCredentialsV2 struct { - ClientEmail string `json:"clientEmail"` - ClientID string `json:"clientId"` - PrivateKeyID string `json:"privateKeyId"` - PrivateKey string `json:"privateKey,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gcr.go b/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gcr.go deleted file mode 100644 index c964120db..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries_gcp_gcr.go +++ /dev/null @@ -1,71 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetGcpGcr gets a single GcpGcr integration matching the -// provided integration guid -func (svc *ContainerRegistriesService) GetGcpGcr(guid string) ( - response GcpGcrIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateGcpGcr updates a single GcpGcr integration on the Lacework Server -func (svc *ContainerRegistriesService) UpdateGcpGcr(data ContainerRegistry) ( - response GcpGcrIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type GcpGcrIntegrationResponse struct { - Data GcpGcrIntegration `json:"data"` -} - -type GcpGcrIntegration struct { - v2CommonIntegrationData - Data GcpGcrData `json:"data"` -} - -func (reg GcpGcrIntegration) ContainerRegistryType() containerRegistryType { - t, _ := FindContainerRegistryType(reg.Data.RegistryType) - return t -} - -type GcpGcrData struct { - Credentials GcpCredentialsV2 `json:"credentials"` - RegistryDomain string `json:"registryDomain"` - RegistryType string `json:"registryType"` - LimitByTag []string `json:"limitByTag,omitempty"` - LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` - LimitByRep []string `json:"limitByRep,omitempty"` - LimitNumImg int `json:"limitNumImg"` - NonOSPackageEval bool `json:"nonOsPackageEval"` -} - -func verifyGcpGcrContainerRegistry(data interface{}) interface{} { - if gar, ok := data.(GcpGcrData); ok { - gar.RegistryType = GcpGcrContainerRegistry.String() - return gar - } - return data -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_ghcr.go b/vendor/github.com/lacework/go-sdk/api/container_registries_ghcr.go deleted file mode 100644 index fd5db59f8..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries_ghcr.go +++ /dev/null @@ -1,82 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetGhcr gets a single Ghcr integration matching the -// provided integration guid -func (svc *ContainerRegistriesService) GetGhcr(guid string) ( - response GhcrIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateGhcr updates a single Ghcr integration on the Lacework Server -func (svc *ContainerRegistriesService) UpdateGhcr(data ContainerRegistry) ( - response GhcrIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type GhcrIntegrationResponse struct { - Data GhcrIntegration `json:"data"` -} - -type GhcrIntegration struct { - v2CommonIntegrationData - Data GhcrData `json:"data"` -} - -func (reg GhcrIntegration) ContainerRegistryType() containerRegistryType { - t, _ := FindContainerRegistryType(reg.Data.RegistryType) - return t -} - -type GhcrData struct { - Credentials GhcrCredentials `json:"credentials"` - RegistryNotifications bool `json:"registryNotifications"` - RegistryDomain string `json:"registryDomain"` // always "ghcr.io" - RegistryType string `json:"registryType"` // always "GHCR" - LimitByTag []string `json:"limitByTag,omitempty"` - LimitByLabel []map[string]string `json:"limitByLabel,omitempty"` - LimitByRep []string `json:"limitByRep,omitempty"` - LimitNumImg int `json:"limitNumImg"` - NonOSPackageEval bool `json:"nonOsPackageEval"` -} - -func verifyGhcrContainerRegistry(data interface{}) interface{} { - if ghcr, ok := data.(GhcrData); ok { - ghcr.RegistryType = GhcrContainerRegistry.String() - ghcr.RegistryDomain = "ghcr.io" - return ghcr - } - return data -} - -// GcpCredentials is already defined in api/integrations_gcp.go:163 -// so we need to add a "V2" at the end to make it clear that this is -// the Google Credentials struct for API v2 -type GhcrCredentials struct { - Username string `json:"username"` - Password string `json:"password,omitempty"` - Ssl bool `json:"ssl"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_inline_scanner.go b/vendor/github.com/lacework/go-sdk/api/container_registries_inline_scanner.go deleted file mode 100644 index eb889c45a..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries_inline_scanner.go +++ /dev/null @@ -1,67 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetInlineScanner gets a single InlineScanner integration matching the -// provided integration guid -func (svc *ContainerRegistriesService) GetInlineScanner(guid string) ( - response InlineScannerIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateInlineScanner updates a single InlineScanner integration on the Lacework Server -func (svc *ContainerRegistriesService) UpdateInlineScanner(data ContainerRegistry) ( - response InlineScannerIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type InlineScannerIntegrationResponse struct { - Data InlineScannerIntegration `json:"data"` -} - -type InlineScannerIntegration struct { - v2CommonIntegrationData - Data InlineScannerData `json:"data"` - ServerToken V2ServerToken `json:"serverToken"` -} - -func (reg InlineScannerIntegration) ContainerRegistryType() containerRegistryType { - t, _ := FindContainerRegistryType(reg.Data.RegistryType) - return t -} - -type InlineScannerData struct { - RegistryType string `json:"registryType"` // always "INLINE_SCANNER" - IdentifierTag []map[string]string `json:"identifierTag"` - LimitNumScan string `json:"limitNumScan,omitempty"` -} - -func verifyInlineScannerContainerRegistry(data interface{}) interface{} { - if inlineScanner, ok := data.(InlineScannerData); ok { - inlineScanner.RegistryType = InlineScannerContainerRegistry.String() - return inlineScanner - } - return data -} diff --git a/vendor/github.com/lacework/go-sdk/api/container_registries_proxy_scanner.go b/vendor/github.com/lacework/go-sdk/api/container_registries_proxy_scanner.go deleted file mode 100644 index 8b29d185c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/container_registries_proxy_scanner.go +++ /dev/null @@ -1,69 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GetProxyScanner gets a single ProxyScanner integration matching the -// provided integration guid -func (svc *ContainerRegistriesService) GetProxyScanner(guid string) ( - response ProxyScannerIntegrationResponse, - err error, -) { - err = svc.get(guid, &response) - return -} - -// UpdateProxyScanner updates a single ProxyScanner integration on the Lacework Server -func (svc *ContainerRegistriesService) UpdateProxyScanner(data ContainerRegistry) ( - response ProxyScannerIntegrationResponse, - err error, -) { - err = svc.update(data.ID(), data, &response) - return -} - -type ProxyScannerIntegrationResponse struct { - Data ProxyScannerIntegration `json:"data"` -} - -type ProxyScannerIntegration struct { - v2CommonIntegrationData - Data ProxyScannerData `json:"data"` - ServerToken V2ServerToken `json:"serverToken"` -} - -func (reg ProxyScannerIntegration) ContainerRegistryType() containerRegistryType { - t, _ := FindContainerRegistryType(reg.Data.RegistryType) - return t -} - -type ProxyScannerData struct { - RegistryType string `json:"registryType"` // always "PROXY_SCANNER" - LimitByTag []string `json:"limitByTag"` - LimitByLabel []map[string]string `json:"limitByLabel"` - LimitByRep []string `json:"limitByRep"` - LimitNumImg int `json:"limitNumImg"` -} - -func verifyProxyScannerContainerRegistry(data interface{}) interface{} { - if proxyScanner, ok := data.(ProxyScannerData); ok { - proxyScanner.RegistryType = ProxyScannerContainerRegistry.String() - return proxyScanner - } - return data -} diff --git a/vendor/github.com/lacework/go-sdk/api/data_export_rules.go b/vendor/github.com/lacework/go-sdk/api/data_export_rules.go deleted file mode 100644 index 52c704f5f..000000000 --- a/vendor/github.com/lacework/go-sdk/api/data_export_rules.go +++ /dev/null @@ -1,130 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/pkg/errors" -) - -// DataExportRulesService is a service that interacts with the DataExportRule -// endpoints from the Lacework Server -type DataExportRulesService struct { - client *Client -} - -type DataExportRulesResponse struct { - Data []DataExportRule `json:"data"` - Message string `json:"message"` -} - -type DataExportRuleResponse struct { - Data DataExportRule `json:"data"` - Message string `json:"message"` -} - -type DataExportRule struct { - ID string `json:"mcGuid,omitempty"` - Filter DataExportRuleFilter `json:"filters"` - Type string `json:"type"` - IDs []string `json:"intgGuidList"` -} - -type DataExportRuleFilter struct { - Name string `json:"name"` - Description string `json:"description"` - CreatedBy string `json:"createdOrUpdatedBy,omitempty"` - UpdatedTime string `json:"createdOrUpdatedTime,omitempty"` - Enabled int `json:"enabled"` - ProfileVersions []string `json:"profileVersions,omitempty"` -} - -// List returns a list of Data Export Rules -func (svc *DataExportRulesService) List() ( - response DataExportRulesResponse, - err error, -) { - err = svc.client.RequestDecoder("GET", apiV2DataExportRules, nil, &response) - return -} - -// Get returns a raw response of the Data Export Rule with the matching guid. -func (svc *DataExportRulesService) Get(id string) ( - response DataExportRuleResponse, - err error, -) { - if id == "" { - err = errors.New("data export rule ID must be provided") - return - } - apiPath := fmt.Sprintf(apiV2DataExportRulesFromGUID, id) - - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} - -// Create creates a single Data Export Rule -func (svc *DataExportRulesService) Create(rule DataExportRule) (response DataExportRuleResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder("POST", apiV2DataExportRules, rule, &response) - return -} - -// Update updates a Data Export Rule that matches the provided guid -func (svc *DataExportRulesService) Update(rule DataExportRule) (response DataExportRuleResponse, - err error, -) { - if rule.ID == "" { - err = errors.New("specify a Guid") - return - } - apiPath := fmt.Sprintf(apiV2DataExportRulesFromGUID, rule.ID) - rule.ID = "" - rule.Filter.UpdatedTime = "" - rule.Filter.CreatedBy = "" - err = svc.client.RequestEncoderDecoder("PATCH", apiPath, rule, &response) - return -} - -// Delete deletes a Data Export Rule that matches the provided guid -func (svc *DataExportRulesService) Delete(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2DataExportRulesFromGUID, guid), - nil, - nil, - ) -} - -// Search returns a list of Data Export Rules -func (svc *DataExportRulesService) Search(filters SearchFilter) ( - response DataExportRulesResponse, err error, -) { - err = svc.client.RequestEncoderDecoder( - "POST", apiV2DataExportRulesSearch, - filters, &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/datasources.go b/vendor/github.com/lacework/go-sdk/api/datasources.go deleted file mode 100644 index 80858a21c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/datasources.go +++ /dev/null @@ -1,88 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "net/url" - - "github.com/pkg/errors" -) - -type DatasourcesResponse struct { - Data []Datasource `json:"data"` - Message string `json:"message"` -} - -type DatasourceResponse struct { - Data Datasource `json:"data"` - Message string `json:"message"` -} - -type Datasource struct { - Name string `json:"name"` - Description string `json:"description"` - ResultSchema []DatasourceSchema `json:"resultSchema"` - SourceRelationships []DatasourceRelationship `json:"sourceRelationships"` -} - -type DatasourceSchema struct { - Name string `json:"name"` - DataType string `json:"dataType"` - Description string `json:"description"` -} - -type DatasourceRelationship struct { - Name string `json:"name"` - Description string `json:"description"` - From string `json:"from"` - To string `json:"to"` - ToCardinality string `json:"toCardinality"` -} - -// DatasourcesService is a service that interacts with the Datasources -// endpoints from the Lacework Server -type DatasourcesService struct { - client *Client -} - -func (svc *DatasourcesService) List() ( - response DatasourcesResponse, - err error, -) { - err = svc.client.RequestDecoder("GET", apiV2Datasources, nil, &response) - return -} - -func (svc *DatasourcesService) Get(id string) ( - response DatasourceResponse, - err error, -) { - if id == "" { - err = errors.New("datasource ID must be provided") - return - } - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf("%s/%s", apiV2Datasources, url.QueryEscape(id)), - nil, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/entities.go b/vendor/github.com/lacework/go-sdk/api/entities.go deleted file mode 100644 index d47eb6af9..000000000 --- a/vendor/github.com/lacework/go-sdk/api/entities.go +++ /dev/null @@ -1,92 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/pkg/errors" -) - -type EntitiesService struct { - client *Client -} - -type EntityType int - -const ( - NoneEntityType EntityType = iota - MachineDetailsEntityType - UsersEntityType - ImagesEntityType - ContainersEntityType - MachineEntityType -) - -// EntityTypes is the list of available entity types -var EntityTypes = map[EntityType]string{ - NoneEntityType: "None", - MachineDetailsEntityType: "MachineDetails", - MachineEntityType: "Machines", - UsersEntityType: "Users", - ImagesEntityType: "Images", - ContainersEntityType: "Containers", -} - -// Search expects the response and the search filters -// -// e.g. -// -// var ( -// response = &api.MachineDetailsEntityResponse{} -// now = time.Now().UTC() -// before = now.AddDate(0, 0, -7) // 7 days from ago -// filters = api.SearchFilter{ -// TimeFilter: &api.TimeFilter{ -// StartTime: &before, -// EndTime: &now, -// }, -// } -// ) -// lacework.V2.Entities.Search(response, filters) -func (svc *EntitiesService) Search(response interface{}, filters SearchFilter) error { - var apiPath string - - switch response.(type) { - case *MachineDetailsEntityResponse: - apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[MachineDetailsEntityType]) - - case *UsersEntityResponse: - apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[UsersEntityType]) - - case *ImagesEntityResponse: - apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[ImagesEntityType]) - - case *ContainersEntityResponse: - apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[ContainersEntityType]) - - case *MachinesEntityResponse: - apiPath = fmt.Sprintf(apiV2EntitiesSearch, EntityTypes[MachineEntityType]) - - default: - return errors.New("missing implementation for the provided entity response") - } - - return svc.client.RequestEncoderDecoder("POST", apiPath, filters, response) -} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_containers.go b/vendor/github.com/lacework/go-sdk/api/entities_containers.go deleted file mode 100644 index 78904e16b..000000000 --- a/vendor/github.com/lacework/go-sdk/api/entities_containers.go +++ /dev/null @@ -1,158 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "time" - - "github.com/lacework/go-sdk/internal/array" -) - -// ListContainers returns a list of Active Containers from the last 7 days -func (svc *EntitiesService) ListContainers() (response ContainersEntityResponse, err error) { - now := time.Now().UTC() - before := now.AddDate(0, 0, -7) // 7 days from ago - err = svc.Search(&response, - SearchFilter{ - TimeFilter: &TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - }, - ) - return -} - -// ListContainersWithFilters returns a list of Active Containers based on a user defined filter -func (svc *EntitiesService) ListContainersWithFilters(filters SearchFilter) ( - response ContainersEntityResponse, err error, -) { - err = svc.Search(&response, filters) - return -} - -// ListAllContainers iterates over all pages to return all active container information at once -func (svc *EntitiesService) ListAllContainers() (response ContainersEntityResponse, err error) { - response, err = svc.ListContainers() - if err != nil { - return - } - - var ( - all []ContainerEntity - pageOk bool - ) - for { - all = append(all, response.Data...) - - newResponse := ContainersEntityResponse{ - Paging: response.Paging, - } - pageOk, err = svc.client.NextPage(&newResponse) - if err == nil && pageOk { - response = newResponse - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -// ListAllContainersWithFilters iterates over all pages to return all active container -// information at once based on a user defined filter -func (svc *EntitiesService) ListAllContainersWithFilters(filters SearchFilter) ( - response ContainersEntityResponse, err error, -) { - response, err = svc.ListContainersWithFilters(filters) - if err != nil { - return - } - - var ( - all []ContainerEntity - pageOk bool - ) - - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -type ContainersEntityResponse struct { - Data []ContainerEntity `json:"data"` - Paging V2Pagination `json:"paging"` - - v2PageMetadata `json:"-"` -} - -// Fulfill Pageable interface (look at api/v2.go) -func (r ContainersEntityResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *ContainersEntityResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -// Total returns the total number of active containers -func (r *ContainersEntityResponse) Total() int { - uniqMIDs := []int{} - for _, container := range r.Data { - if !array.ContainsInt(uniqMIDs, container.Mid) { - uniqMIDs = append(uniqMIDs, container.Mid) - } - } - return len(uniqMIDs) -} - -// Count returns the number of active containers with the provided image ID -func (r *ContainersEntityResponse) Count(imageID string) int { - uniqMIDs := []int{} - for _, container := range r.Data { - if container.ImageID == imageID && - !array.ContainsInt(uniqMIDs, container.Mid) { - uniqMIDs = append(uniqMIDs, container.Mid) - } - } - return len(uniqMIDs) -} - -type ContainerEntity struct { - ContainerName string `json:"containerName"` - ImageID string `json:"imageId"` - Mid int `json:"mid"` - StartTime time.Time `json:"startTime"` - EndTime time.Time `json:"endTime"` - PodName string `json:"podName"` - PropsContainer map[string]interface{} `json:"propsContainer"` - Tags map[string]interface{} `json:"tags"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_images.go b/vendor/github.com/lacework/go-sdk/api/entities_images.go deleted file mode 100644 index ac5093acf..000000000 --- a/vendor/github.com/lacework/go-sdk/api/entities_images.go +++ /dev/null @@ -1,124 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "time" - -// ListImages returns a list of UserEntity from the last 7 days -func (svc *EntitiesService) ListImages() (response ImagesEntityResponse, err error) { - now := time.Now().UTC() - before := now.AddDate(0, 0, -7) // 7 days from ago - err = svc.Search(&response, - SearchFilter{ - TimeFilter: &TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - }, - ) - return -} - -// ListImagesWithFilters returns a list of UserEntity based on a user defined filter -func (svc *EntitiesService) ListImagesWithFilters(filters SearchFilter) (response ImagesEntityResponse, err error) { - err = svc.Search(&response, filters) - return -} - -// ListAllImages iterates over all pages to return all images information at once -func (svc *EntitiesService) ListAllImages() (response ImagesEntityResponse, err error) { - response, err = svc.ListImages() - if err != nil { - return - } - - var ( - all []ImageEntity - pageOk bool - ) - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -// ListAllImagesWithFilters iterates over all pages to return all images information -// at once based on a user defined filter -func (svc *EntitiesService) ListAllImagesWithFilters(filters SearchFilter) ( - response ImagesEntityResponse, err error, -) { - response, err = svc.ListImagesWithFilters(filters) - if err != nil { - return - } - - var ( - all []ImageEntity - pageOk bool - ) - - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -type ImagesEntityResponse struct { - Data []ImageEntity `json:"data"` - Paging V2Pagination `json:"paging"` - - v2PageMetadata `json:"-"` -} - -// Fulfill Pageable interface (look at api/v2.go) -func (r ImagesEntityResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *ImagesEntityResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -type ImageEntity struct { - ContainerType string `json:"containerType"` - CreatedTime time.Time `json:"createdTime"` - ImageID string `json:"imageId"` - Mid int `json:"mid"` - Repo string `json:"repo"` - Size int `json:"size"` - Tag string `json:"tag"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_machine_details.go b/vendor/github.com/lacework/go-sdk/api/entities_machine_details.go deleted file mode 100644 index 0371023bc..000000000 --- a/vendor/github.com/lacework/go-sdk/api/entities_machine_details.go +++ /dev/null @@ -1,171 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "time" -) - -// ListMachineDetails returns a list of MachineDetailEntity from the last 7 days -func (svc *EntitiesService) ListMachineDetails() (response MachineDetailsEntityResponse, err error) { - now := time.Now().UTC() - before := now.AddDate(0, 0, -7) // 7 days from ago - err = svc.Search(&response, - SearchFilter{ - TimeFilter: &TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - }, - ) - return -} - -// ListMachineDetailsWithFilters returns a list of UserEntity based on a user defined filter -func (svc *EntitiesService) ListMachineDetailsWithFilters(filters SearchFilter) ( - response MachineDetailsEntityResponse, err error, -) { - err = svc.Search(&response, filters) - return -} - -// ListAllMachineDetails iterates over all pages to return all machine details at once -func (svc *EntitiesService) ListAllMachineDetails() (response MachineDetailsEntityResponse, err error) { - response, err = svc.ListMachineDetails() - if err != nil { - return - } - - var ( - all []MachineDetailEntity - pageOk bool - ) - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -// ListAllMachineDetailsWithFilters iterates over all pages to return all machine details -// at once based on a user defined filter -func (svc *EntitiesService) ListAllMachineDetailsWithFilters(filters SearchFilter) ( - response MachineDetailsEntityResponse, err error, -) { - response, err = svc.ListMachineDetailsWithFilters(filters) - if err != nil { - return - } - - var ( - all []MachineDetailEntity - pageOk bool - ) - - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -type MachineDetailsEntityResponse struct { - Data []MachineDetailEntity `json:"data"` - Paging V2Pagination `json:"paging"` - - v2PageMetadata `json:"-"` -} - -// Fulfill Pageable interface (look at api/v2.go) -func (r MachineDetailsEntityResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *MachineDetailsEntityResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -type MachineDetailEntity struct { - AwsInstanceID string `json:"awsInstanceId"` - AwsZone string `json:"awsZone"` - CreatedTime time.Time `json:"createdTime"` - Domain string `json:"domain"` - Hostname string `json:"hostname"` - Kernel string `json:"kernel"` - KernelRelease string `json:"kernelRelease"` - KernelVersion string `json:"kernelVersion"` - Mid int `json:"mid"` - Os string `json:"os"` - OsVersion string `json:"osVersion"` - Tags struct { - // Shared Tags - Arch string `json:"arch,omitempty"` - ExternalIP string `json:"ExternalIp,omitempty"` - Hostname string `json:"Hostname,omitempty"` - InstanceID string `json:"InstanceId,omitempty"` - InternalIP string `json:"InternalIp,omitempty"` - LwTokenShort string `json:"LwTokenShort,omitempty"` - Os string `json:"os,omitempty"` - VMInstanceType string `json:"VmInstanceType,omitempty"` - VMProvider string `json:"VmProvider,omitempty"` - Zone string `json:"Zone,omitempty"` - - // AWS Tags - Account string `json:"Account,omitempty"` - AmiID string `json:"AmiId,omitempty"` - Name string `json:"Name,omitempty"` - SubnetID string `json:"SubnetId,omitempty"` - VpcID string `json:"VpcId,omitempty"` - - // GCP Tags - Cluster string `json:"Cluster,omitempty"` - ClusterLocation string `json:"cluster-location,omitempty"` - ClusterName string `json:"cluster-name,omitempty"` - ClusterUID string `json:"cluster-uid,omitempty"` - CreatedBy string `json:"created-by,omitempty"` - EnableOSLogin string `json:"enable-oslogin,omitempty"` - Env string `json:"Env,omitempty"` - GCEtags string `json:"GCEtags,omitempty"` - GCIEnsureGKEDocker string `json:"gci-ensure-gke-docker,omitempty"` - GCIUpdateStrategy string `json:"gci-update-strategy,omitempty"` - GoogleComputeEnablePCID string `json:"google-compute-enable-pcid,omitempty"` - InstanceName string `json:"InstanceName,omitempty"` - InstanceTemplate string `json:"InstanceTemplate,omitempty"` - KubeLabels string `json:"kube-labels,omitempty"` - LWKubernetesCluster string `json:"lw_KubernetesCluster,omitempty"` - NumericProjectID string `json:"NumericProjectId,omitempty"` - ProjectID string `json:"ProjectId,omitempty"` - } `json:"tags"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_machines.go b/vendor/github.com/lacework/go-sdk/api/entities_machines.go deleted file mode 100644 index 53205c610..000000000 --- a/vendor/github.com/lacework/go-sdk/api/entities_machines.go +++ /dev/null @@ -1,164 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "time" -) - -// ListMachines returns a list of MachineEntity from the last 7 days -func (svc *EntitiesService) ListMachines() (response MachinesEntityResponse, err error) { - now := time.Now().UTC() - before := now.AddDate(0, 0, -7) // 7 days from ago - err = svc.Search(&response, - SearchFilter{ - TimeFilter: &TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - }, - ) - return -} - -// ListMachinesWithFilters returns a list of UserEntity based on a user defined filter -func (svc *EntitiesService) ListMachinesWithFilters(filters SearchFilter) (response MachinesEntityResponse, err error) { - err = svc.Search(&response, filters) - return -} - -// ListAllMachines iterates over all pages to return all machine details at once -func (svc *EntitiesService) ListAllMachines() (response MachinesEntityResponse, err error) { - response, err = svc.ListMachines() - if err != nil { - return - } - - var ( - all []MachineEntity - pageOk bool - ) - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -// ListAllMachinesWithFilters iterates over all pages to return all machine details -// at once based on a user defined filter -func (svc *EntitiesService) ListAllMachinesWithFilters(filters SearchFilter) ( - response MachinesEntityResponse, err error, -) { - response, err = svc.ListMachinesWithFilters(filters) - if err != nil { - return - } - - var ( - all []MachineEntity - pageOk bool - ) - - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -type MachinesEntityResponse struct { - Data []MachineEntity `json:"data"` - Paging V2Pagination `json:"paging"` - - v2PageMetadata `json:"-"` -} - -// Fulfill Pageable interface (look at api/v2.go) -func (r MachinesEntityResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *MachinesEntityResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -type MachineEntity struct { - AwsInstanceID string `json:"awsInstanceId"` - Hostname string `json:"hostname"` - EntityType string `json:"entityType"` - EndTime time.Time `json:"endTime"` - Mid int `json:"mid"` - PrimaryIpAddr string `json:"primaryIpAddr"` - StartTime time.Time `json:"startTime"` - Tags struct { - // Shared Tags - Cluster string `json:"Cluster,omitempty"` - Env string `json:"Env,omitempty"` - Arch string `json:"arch,omitempty"` - ExternalIP string `json:"ExternalIp,omitempty"` - Hostname string `json:"Hostname,omitempty"` - InstanceID string `json:"InstanceId,omitempty"` - InternalIP string `json:"InternalIp,omitempty"` - LwTokenShort string `json:"LwTokenShort,omitempty"` - Os string `json:"os,omitempty"` - VMInstanceType string `json:"VmInstanceType,omitempty"` - VMProvider string `json:"VmProvider,omitempty"` - Zone string `json:"Zone,omitempty"` - ClusterLocation string `json:"cluster-location,omitempty"` - ClusterName string `json:"cluster-name,omitempty"` - ClusterUid string `json:"cluster-uid,omitempty"` - CreatedBy string `json:"created-by,omitempty"` - LwKubernetesCluster string `json:"lw_KubernetesCluster,omitempty"` - KubeLabels string `json:"kube-labels,omitempty"` - - // AWS Tags - Account string `json:"Account,omitempty"` - AmiId string `json:"AmiId,omitempty"` - SubnetId string `json:"SubnetId,omitempty"` - VpcId string `json:"VpcId,omitempty"` - - // GCP Tags - GCEtags string `json:"GCEtags,omitempty"` - InstanceName string `json:"InstanceName,omitempty"` - NumericProjectId string `json:"NumericProjectId,omitempty"` - ProjectId string `json:"ProjectId,omitempty"` - EnableOslogin string `json:"enable-oslogin,omitempty"` - GciEnsureGkeDocker string `json:"gci-ensure-gke-docker,omitempty"` - GciUpdateStrategy string `json:"gci-update-strategy,omitempty"` - GoogleComputeEnablePcid string `json:"google-compute-enable-pcid,omitempty"` - InstanceTemplate string `json:"instance-template,omitempty"` - } `json:"machineTags"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/entities_users.go b/vendor/github.com/lacework/go-sdk/api/entities_users.go deleted file mode 100644 index 155d2c062..000000000 --- a/vendor/github.com/lacework/go-sdk/api/entities_users.go +++ /dev/null @@ -1,87 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "time" - -// ListUsers returns a list of UserEntity from the last 7 days -func (svc *EntitiesService) ListUsers() (response UsersEntityResponse, err error) { - now := time.Now().UTC() - before := now.AddDate(0, 0, -7) // 7 days from ago - err = svc.Search(&response, - SearchFilter{ - TimeFilter: &TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - }, - ) - return -} - -// ListAllUsers iterates over all pages to return all user information at once -func (svc *EntitiesService) ListAllUsers() (response UsersEntityResponse, err error) { - response, err = svc.ListUsers() - if err != nil { - return - } - - var ( - all []UserEntity - pageOk bool - ) - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -type UsersEntityResponse struct { - Data []UserEntity `json:"data"` - Paging V2Pagination `json:"paging"` - - v2PageMetadata `json:"-"` -} - -// Fulfill Pagination interface (look at api/v2.go) -func (r UsersEntityResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *UsersEntityResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -type UserEntity struct { - CreatedTime time.Time `json:"createdTime"` - Mid int `json:"mid"` - OtherGroupNames []string `json:"otherGroupNames"` - PrimaryGroupName string `json:"primaryGroupName"` - UID int `json:"uid"` - Username string `json:"username"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/errors.go b/vendor/github.com/lacework/go-sdk/api/errors.go deleted file mode 100644 index 329fdcac4..000000000 --- a/vendor/github.com/lacework/go-sdk/api/errors.go +++ /dev/null @@ -1,113 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/json" - "fmt" - "io" - "net/http" -) - -// errorResponse handles errors caused by a Lacework API request -type errorResponse struct { - Response *http.Response - Message string -} - -type apiErrorResponse struct { - Ok bool `json:"ok"` - V2Message string `json:"message"` - Data struct { - Message string `json:"message"` - StatusMessage string `json:"statusMessage"` - ErrorMsg string `json:"ErrorMsg"` - } `json:"data"` -} - -// Message extracts the message from an api error response -func (r *apiErrorResponse) Message() string { - if r != nil { - if r.Data.ErrorMsg != "" { - return r.Data.ErrorMsg - } - if r.Data.Message != "" { - return r.Data.Message - } - if r.V2Message != "" && r.V2Message != "SUCCESS" { - return r.V2Message - } - if r.Data.StatusMessage != "" { - return r.Data.StatusMessage - } - } - return "" -} - -// Error fulfills the built-in error interface function -func (r *errorResponse) Error() string { - return fmt.Sprintf("\n [%v] %v\n [%d] %s", - r.Response.Request.Method, - r.Response.Request.URL, - r.Response.StatusCode, - r.Message, - ) -} - -// checkResponse checks the provided response and generates an Error -func checkErrorInResponse(r *http.Response) error { - if c := r.StatusCode; c >= 200 && c <= 299 { - return nil - } - - var ( - errRes = &errorResponse{Response: r} - data, err = io.ReadAll(r.Body) - ) - if err == nil && len(data) > 0 { - // try to unmarshal the api error response - apiErrRes := &apiErrorResponse{} - if err := json.Unmarshal(data, apiErrRes); err != nil { - errRes.Message = string(data) - return errRes - } - - var ( - apiErrResMessage = apiErrRes.Message() - statusText = http.StatusText(r.StatusCode) - ) - - // try our best to parse the error message - if apiErrResMessage != "" { - errRes.Message = apiErrResMessage - return errRes - } - - // if it is empty, try to display the Status Code in pretty Text - if statusText != "" { - errRes.Message = statusText - return errRes - } - - // if we couldn't even decode the StatusCode... well, lets just be transparent - errRes.Message = "Unknown" - } - - return errRes -} diff --git a/vendor/github.com/lacework/go-sdk/api/feature_flags.go b/vendor/github.com/lacework/go-sdk/api/feature_flags.go deleted file mode 100644 index 8fa897c6e..000000000 --- a/vendor/github.com/lacework/go-sdk/api/feature_flags.go +++ /dev/null @@ -1,27 +0,0 @@ -package api - -import ( - "fmt" -) - -type FeatureFlagsService struct { - client *Client -} - -type FeatureFlag string - -type FeatureFlags struct { - Flags []FeatureFlag `json:"flags,omitempty"` -} - -type FeatureFlagsResponse struct { - Data FeatureFlags `json:"data"` -} - -func (svc *FeatureFlagsService) GetFeatureFlagsMatchingPrefix(prefix string) ( - response FeatureFlagsResponse, err error, -) { - apiPath := fmt.Sprintf("%s/%s", apiV2FeatureFlags, prefix) - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/http.go b/vendor/github.com/lacework/go-sdk/api/http.go deleted file mode 100644 index 37a590b24..000000000 --- a/vendor/github.com/lacework/go-sdk/api/http.go +++ /dev/null @@ -1,258 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - "net/url" - - "github.com/cenkalti/backoff/v4" - "go.uber.org/zap" -) - -// NewRequest generates a new http request -func (c *Client) NewRequest(method string, apiURL string, body io.Reader) (*http.Request, error) { - apiPath, err := url.Parse(c.apiPath(apiURL)) - if err != nil { - return nil, err - } - - u := c.baseURL.ResolveReference(apiPath) - request, err := http.NewRequest(method, u.String(), body) - if err != nil { - return nil, err - } - - // set all necessary headers - headers := map[string]string{ - "Method": request.Method, - "Accept": "application/json", - } - - // handle the special case that we are requesting an access token - if apiURL == apiTokens { - headers["X-LW-UAKS"] = c.auth.secret - } else { - // verify that the client has a token or token is not expired, - // if not, try to generate one - if c.auth.token == "" || c.TokenExpired() { - // run token expired callback - if c.callbacks.TokenExpiredCallback != nil && c.TokenExpired() { - if err := c.callbacks.TokenExpiredCallback(); err != nil { - c.log.Info("token expired callback failure", zap.String("error", err.Error())) - } - } - if _, err = c.GenerateToken(); err != nil { - return nil, err - } - } - headers["Authorization"] = c.auth.token - } - - if body != nil { - // @afiune we should detect the content-type from the body - // instead of hard-coding it here - headers["Content-Type"] = "application/json" - } - - for k, v := range headers { - request.Header.Set(k, v) - } - - // add all global headers that the client has configured, - // by default we set only the User-Agent header - for k, v := range c.headers { - request.Header.Set(k, v) - } - - // parse and encode query string values - values := request.URL.Query() - request.URL.RawQuery = values.Encode() - - c.log.Debug("request", - zap.String("method", request.Method), - zap.String("url", c.baseURL.String()), - zap.String("endpoint", apiPath.String()), - zap.Reflect("headers", c.httpHeadersSniffer(request.Header)), - zap.String("body", c.httpRequestBodySniffer(request)), - ) - - return request, nil -} - -// DoDecoder is used to execute (aka Do) the http request and -// decode it into the provided interface, all at once -func (c *Client) DoDecoder(req *http.Request, v interface{}) (*http.Response, error) { - res, err := c.Do(req) - if err != nil { - return nil, err - } - - // optimized return for HTTP 204 as there's nothing to decode onto v - if res.StatusCode == http.StatusNoContent { - return res, nil - } - - err = checkErrorInResponse(res) - if err != nil { - return res, err - } - - if v != nil { - var ( - resBuf bytes.Buffer - - // by using a TeeReader for capturing the reader’s data we avoid - // interfering with the consumer of the reader - resTee = io.TeeReader(res.Body, &resBuf) - ) - if w, ok := v.(io.Writer); ok { - _, err = io.Copy(w, resTee) - return res, err - } - dcoder := json.NewDecoder(resTee) - dcoder.UseNumber() - err = dcoder.Decode(v) - } - - return res, err -} - -// RequestDecoder performs an http request on an endpoint, and -// decodes the response into the provided interface, all at once -func (c *Client) RequestDecoder(method, path string, body io.Reader, v interface{}) error { - request, err := c.NewRequest(method, path, body) - if err != nil { - return err - } - - var res *http.Response - if c.retries != nil { - err = backoff.Retry(func() error { - res, err = c.DoDecoder(request, v) - return err - }, c.retries) - } else { - res, err = c.DoDecoder(request, v) - } - if err != nil { - return err - } - defer res.Body.Close() - - return err -} - -// RequestEncoderDecoder leverages RequestDecoder and performs an http request that first -// encodes the provider 'data' as a JSON Reader and passes it as the body to the request -func (c *Client) RequestEncoderDecoder(method, path string, data, v interface{}) error { - body, err := jsonReader(data) - if err != nil { - return err - } - return c.RequestDecoder(method, path, body, v) -} - -// Do calls request.Do() directly -func (c *Client) Do(req *http.Request) (*http.Response, error) { - response, err := c.c.Do(req) - if err == nil { - c.log.Info("response", - zap.String("from_req_url", req.URL.String()), - zap.Int("code", response.StatusCode), - zap.String("proto", response.Proto), - zap.Reflect("headers", c.httpHeadersSniffer(response.Header)), - zap.String("body", c.httpResponseBodySniffer(response)), - ) - } - - // run request callback - if call := c.callbacks.RequestCallback; call != nil && response != nil { - if err := call(response.StatusCode, response.Header); err != nil { - c.log.Info("request callback failure", zap.String("error", err.Error())) - } - } - - return response, err -} - -// httpHeadersSniffer is only useful to avoid logging out the headers of a request -// or response when the log level is set to INFO -func (c *Client) httpHeadersSniffer(headers interface{}) interface{} { - if !c.debugMode() { - // prevents headers to be displayed if we are not in DEBUG mode - return "suppressed" - } - return headers -} - -// httpRequestBodySniffer a request sniffer, it reads the body from the -// provided request without closing it (use only for debugging purposes) -func (c *Client) httpRequestBodySniffer(r *http.Request) string { - if !c.debugMode() { - // prevents sniffing the request if we are not in DEBUG mode - return "suppressed" - } - - if r.Body == nil || r.Body == http.NoBody { - // No need to sniff - return "" - } - - var stringBody string - r.Body, stringBody = sniffBody(r.Body) - - return stringBody -} - -// httpResponseBodySniffer a response sniffer, it reads the body from the -// provided response without closing it (use only for debugging purposes) -func (c *Client) httpResponseBodySniffer(r *http.Response) string { - if !c.debugMode() { - // prevents sniffing the response if we are not in DEBUG mode - return "suppressed" - } - - if r.Body == nil || r.ContentLength == 0 { - // No need to sniff - return "" - } - - var stringBody string - r.Body, stringBody = sniffBody(r.Body) - - return stringBody -} - -// a very simple body sniffer (use only for debugging purposes) -func sniffBody(body io.ReadCloser) (io.ReadCloser, string) { - bodyBytes, err := io.ReadAll(body) - if err != nil { - return nil, "" - } - - if err := body.Close(); err != nil { - return nil, "" - } - - return io.NopCloser(bytes.NewBuffer(bodyBytes)), string(bodyBytes) -} diff --git a/vendor/github.com/lacework/go-sdk/api/inventory.go b/vendor/github.com/lacework/go-sdk/api/inventory.go deleted file mode 100644 index c3d80e04f..000000000 --- a/vendor/github.com/lacework/go-sdk/api/inventory.go +++ /dev/null @@ -1,90 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "time" -) - -type InventoryService struct { - client *Client -} - -type inventoryType string -type inventoryDataset string - -const AwsInventoryType inventoryType = "AWS" -const AzureInventoryType inventoryType = "Azure" -const GcpInventoryType inventoryType = "GCP" -const AwsInventoryDataset inventoryDataset = "AwsCompliance" - -// Search expects the response and the search filters -// -// e.g. -// -// var ( -// awsInventorySearchResponse api.InventoryAwsResponse -// filter = api.InventorySearch{ -// SearchFilter: api.SearchFilter{ -// Filters: []api.Filter{{ -// Expression: "eq", -// Field: "urn", -// Value: arn:aws:s3:::my-bucket, -// }}, -// }, -// Dataset: api.AwsComplianceEvaluationDataset, -// } -// ) -// lacework.V2.Inventory.Search(&awsInventorySearchResponse, filters) -func (svc *InventoryService) Search(response interface{}, filters SearchableFilter) error { - return svc.client.RequestEncoderDecoder("POST", apiV2InventorySearch, filters, response) -} - -// Scan triggers a resource inventory scan -func (svc *InventoryService) Scan(cloud inventoryType) (response InventoryScanResponse, err error) { - url := fmt.Sprintf(apiV2InventoryScanCsp, cloud) - err = svc.client.RequestEncoderDecoder("POST", url, nil, &response) - return -} - -type InventorySearch struct { - SearchFilter - Csp inventoryType `json:"csp"` - Dataset inventoryDataset `json:"dataset"` -} - -func (i InventorySearch) GetTimeFilter() *TimeFilter { - return i.TimeFilter -} - -func (i InventorySearch) SetStartTime(time *time.Time) { - i.TimeFilter.StartTime = time -} - -func (i InventorySearch) SetEndTime(time *time.Time) { - i.TimeFilter.EndTime = time -} - -type InventoryScanResponse struct { - Data struct { - Status string `json:"status"` - Details string `json:"details"` - } `json:"data"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/inventory_aws.go b/vendor/github.com/lacework/go-sdk/api/inventory_aws.go deleted file mode 100644 index 1440124cd..000000000 --- a/vendor/github.com/lacework/go-sdk/api/inventory_aws.go +++ /dev/null @@ -1,62 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -type InventoryAwsResponse struct { - Data []InventoryAws `json:"data"` - Paging V2Pagination `json:"paging"` -} - -func (r InventoryAwsResponse) GetDataLength() int { - return len(r.Data) -} - -func (r InventoryAwsResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *InventoryAwsResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -type InventoryAws struct { - ApiKey string `json:"apiKey"` - Csp string `json:"csp"` - EndTime string `json:"endTime"` - StartTime string `json:"startTime"` - ResourceId string `json:"resourceId"` - ResourceRegion string `json:"resourceRegion"` - ResourceTags any `json:"resourceTags"` - ResourceType string `json:"resourceType"` - Service string `json:"service"` - Urn string `json:"urn"` - CloudDetails struct { - AccountAlias string `json:"accountAlias"` - AccountID string `json:"accountID"` - } `json:"cloudDetails"` - Status struct { - FormatVersion int `json:"formatVersion"` - Props any `json:"props"` - Status string `json:"status"` - // Error status - ErrorMessage string `json:"errorMessage,omitempty"` - ErrorType string `json:"errorType,omitempty"` - } `json:"status"` - ResourceConfig any `json:"resourceConfig"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/logging.go b/vendor/github.com/lacework/go-sdk/api/logging.go deleted file mode 100644 index 56dfe2d1c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/logging.go +++ /dev/null @@ -1,156 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "io" - "os" - "syscall" - - "github.com/pkg/errors" - "go.uber.org/zap" - - "github.com/lacework/go-sdk/lwlogger" -) - -// WithLogLevel sets the log level of the client, available: info, debug, or error -func WithLogLevel(level string) Option { - return clientFunc(func(c *Client) error { - // do not re initialize our logger if the log level - // is the same as the desired one - if level == c.log.Level().CapitalString() { - return nil - } - - if !lwlogger.ValidLevel(level) { - return fmt.Errorf("invalid log level '%s'", level) - } - - c.log.Debug("setting up client", zap.String("log_level", level)) - c.initLogger(level) - return nil - }) -} - -// WithLogLevelAndWriter sets the log level of the client -// and writes the log messages to the provided io.Writer -func WithLogLevelAndWriter(level string, w io.Writer) Option { - return clientFunc(func(c *Client) error { - if !lwlogger.ValidLevel(level) { - return fmt.Errorf("invalid log level '%s'", level) - } - - c.log.Debug("setting up client", zap.String("log_level", level)) - c.initLoggerWithWriterAndLevel(level, w) - return nil - }) -} - -// WithLogWriter configures the client to log messages to the provided io.Writer -func WithLogWriter(w io.Writer) Option { - return clientFunc(func(c *Client) error { - c.initLoggerWithWriter(w) - return nil - }) -} - -// WithLogLevelAndFile sets the log level of the client -// and writes the log messages to the provided file -func WithLogLevelAndFile(level string, filename string) Option { - return clientFunc(func(c *Client) error { - if !lwlogger.ValidLevel(level) { - return fmt.Errorf("invalid log level '%s'", level) - } - - logWriter, err := os.OpenFile(filename, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) - if err != nil { - return errors.Wrap(err, "unable to open file to initialize api logger ") - } - - c.initLoggerWithWriterAndLevel(level, logWriter) - return nil - }) -} - -// WithLogFile configures the client to write messages to the provided file -func WithLogFile(filename string) Option { - return clientFunc(func(c *Client) error { - logWriter, err := os.OpenFile(filename, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) - if err != nil { - return errors.Wrap(err, "unable to open file to initialize api logger ") - } - - c.log.Debug("setting up client redirect logger", zap.String("file", filename)) - c.initLoggerWithWriter(logWriter) - return nil - }) -} - -// initLogger initializes the logger with a set of default fields -func (c *Client) initLogger(level string) { - if c.log != nil { - _ = c.log.Sync() - } - c.log = lwlogger.New(level, - zap.Fields( - zap.Field(zap.String("id", c.id)), - zap.Field(zap.String("account", c.account)), - ), - ) - - // verify if the log level has been configure through environment variable - if envLevel := lwlogger.LogLevelFromEnvironment(); envLevel != "" { - c.log.Debug("setting up client, override log level", - zap.String("before", level), - zap.String("after", envLevel), - ) - } -} - -// initLoggerWithWriter initializes a new logger with a set -// of default fields and configues the provided io.Writer -func (c *Client) initLoggerWithWriter(w io.Writer) { - c.initLoggerWithWriterAndLevel("", w) -} - -func (c *Client) initLoggerWithWriterAndLevel(level string, w io.Writer) { - if c.log != nil { - _ = c.log.Sync() - } - c.log = lwlogger.NewWithWriter(level, w, - zap.Fields( - zap.Field(zap.String("id", c.id)), - zap.Field(zap.String("account", c.account)), - ), - ) - - // verify if the log level has been configure through environment variable - if envLevel := lwlogger.LogLevelFromEnvironment(); envLevel != "" { - c.log.Debug("setting up client, override log level", - zap.String("before", level), - zap.String("after", envLevel), - ) - } -} - -// debugMode returns true if the client is configured to display debug level logs -func (c *Client) debugMode() bool { - return c.log.Level() == zap.DebugLevel -} diff --git a/vendor/github.com/lacework/go-sdk/api/lql.go b/vendor/github.com/lacework/go-sdk/api/lql.go deleted file mode 100644 index ae476d4db..000000000 --- a/vendor/github.com/lacework/go-sdk/api/lql.go +++ /dev/null @@ -1,133 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/json" - "fmt" - "net/url" - "reflect" - - "github.com/pkg/errors" - "gopkg.in/yaml.v3" -) - -type NewQuery struct { - QueryID string `json:"queryId" yaml:"queryId"` - QueryText string `json:"queryText" yaml:"queryText"` -} - -func ParseNewQuery(s string) (NewQuery, error) { - var ( - query NewQuery - err error - ) - - // valid json - if err = json.Unmarshal([]byte(s), &query); err == nil { - return query, err - } - // valid yaml - query = NewQuery{} - err = yaml.Unmarshal([]byte(s), &query) - if err == nil && !reflect.DeepEqual(query, NewQuery{}) { // empty string unmarshals w/o error - return query, nil - } - // invalid query - return query, errors.New("unable to parse query") -} - -type UpdateQuery struct { - QueryText string `json:"queryText"` -} - -type Query struct { - QueryID string `json:"queryId" yaml:"queryId"` - QueryText string `json:"queryText" yaml:"queryText"` - Owner string `json:"owner"` - LastUpdateTime string `json:"lastUpdateTime"` - LastUpdateUser string `json:"lastUpdateUser"` - ResultSchema []map[string]interface{} `json:"resultSchema"` -} - -type QueryResponse struct { - Data Query `json:"data"` - Message string `json:"message"` -} - -type QueriesResponse struct { - Data []Query `json:"data"` - Message string `json:"message"` -} - -// QueryService is a service that interacts with the Queries -// endpoints from the Lacework Server -type QueryService struct { - client *Client -} - -func (svc *QueryService) Create(nq NewQuery) ( - response QueryResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder("POST", apiV2Queries, nq, &response) - return -} - -func (svc *QueryService) Update(id string, uq UpdateQuery) ( - response QueryResponse, - err error, -) { - if id == "" { - err = errors.New("query ID must be provided") - return - } - err = svc.client.RequestEncoderDecoder( - "PATCH", - fmt.Sprintf("%s/%s", apiV2Queries, url.QueryEscape(id)), - uq, - &response, - ) - return -} - -func (svc *QueryService) List() ( - response QueriesResponse, - err error, -) { - err = svc.client.RequestDecoder("GET", apiV2Queries, nil, &response) - return -} - -func (svc *QueryService) Get(id string) ( - response QueryResponse, - err error, -) { - if id == "" { - err = errors.New("query ID must be provided") - return - } - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf("%s/%s", apiV2Queries, url.QueryEscape(id)), - nil, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/lql_delete.go b/vendor/github.com/lacework/go-sdk/api/lql_delete.go deleted file mode 100644 index 845fea295..000000000 --- a/vendor/github.com/lacework/go-sdk/api/lql_delete.go +++ /dev/null @@ -1,47 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "net/url" - - "github.com/pkg/errors" -) - -type QueryDeleteResponse struct { - Message string `json:"message"` -} - -func (svc *QueryService) Delete(id string) ( - response QueryDeleteResponse, - err error, -) { - if id == "" { - err = errors.New("query ID must be provided") - return - } - err = svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf("%s/%s", apiV2Queries, url.QueryEscape(id)), - nil, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/lql_execute.go b/vendor/github.com/lacework/go-sdk/api/lql_execute.go deleted file mode 100644 index 178bdb980..000000000 --- a/vendor/github.com/lacework/go-sdk/api/lql_execute.go +++ /dev/null @@ -1,159 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "bytes" - "encoding/json" - "fmt" - "net/url" - "time" - - "github.com/lacework/go-sdk/lwtime" - "github.com/pkg/errors" -) - -type ExecuteQuery struct { - QueryText string `json:"queryText"` -} - -type ExecuteQueryArgumentName string - -const ( - QueryStartTimeRange ExecuteQueryArgumentName = "StartTimeRange" - QueryEndTimeRange ExecuteQueryArgumentName = "EndTimeRange" -) - -type ExecuteQueryOptions struct { - Limit *int `json:"limit,omitempty"` -} - -type ExecuteQueryArgument struct { - Name ExecuteQueryArgumentName `json:"name"` - Value string `json:"value"` -} - -type ExecuteQueryRequest struct { - Query ExecuteQuery `json:"query"` - Options ExecuteQueryOptions `json:"options"` - Arguments []ExecuteQueryArgument `json:"arguments"` -} - -type ExecuteQueryByIDRequest struct { - QueryID string `json:"queryId,omitempty"` - Options ExecuteQueryOptions `json:"options"` - Arguments []ExecuteQueryArgument `json:"arguments"` -} - -type ExecuteQueryData []interface{} - -func (d *ExecuteQueryData) UnmarshalJSON(data []byte) error { - type Alias ExecuteQueryData - - temp := (*Alias)(d) - reader := bytes.NewReader(data) - decoder := json.NewDecoder(reader) - decoder.UseNumber() - return decoder.Decode(temp) -} - -type ExecuteQueryResponse struct { - Data ExecuteQueryData `json:"data"` - Ok bool `json:"ok"` - Message string `json:"message"` -} - -func validateQueryArguments(args []ExecuteQueryArgument) error { - var ( - hasStart, hasEnd bool - start, end time.Time - err error - ) - - for _, arg := range args { - if arg.Name == QueryStartTimeRange { - hasStart = true - start, err = validateQueryTimeString(arg.Value) - } - if err != nil { - return errors.Wrap(err, "invalid StartTimeRange argument") - } - - if arg.Name == QueryEndTimeRange { - hasEnd = true - end, err = validateQueryTimeString(arg.Value) - } - if err != nil { - return errors.Wrap(err, "invalid EndTimeRange argument") - } - } - - if hasStart && hasEnd { - return validateQueryRange(start, end) - } - return nil -} - -// StartTimeRange and EndTimeRange should be -func validateQueryTimeString(s string) (time.Time, error) { - return time.Parse(lwtime.RFC3339Milli, s) -} - -func validateQueryRange(start, end time.Time) (err error) { - // validate range - if start.After(end) { - err = errors.New("date range should have a start time before the end time") - return - } - return nil -} - -func (svc *QueryService) Execute(request ExecuteQueryRequest) ( - response ExecuteQueryResponse, - err error, -) { - if err = validateQueryArguments(request.Arguments); err != nil { - return - } - err = svc.client.RequestEncoderDecoder("POST", apiV2QueriesExecute, request, &response) - return -} - -func (svc *QueryService) ExecuteByID(request ExecuteQueryByIDRequest) ( - response ExecuteQueryResponse, - err error, -) { - if request.QueryID == "" { - err = errors.New("query ID must be provided") - return - } - queryID := request.QueryID - request.QueryID = "" // omit for POST - - if err = validateQueryArguments(request.Arguments); err != nil { - return - } - err = svc.client.RequestEncoderDecoder( - "POST", - fmt.Sprintf("%s/%s/execute", apiV2Queries, url.QueryEscape(queryID)), - request, - &response, - ) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/lql_validate.go b/vendor/github.com/lacework/go-sdk/api/lql_validate.go deleted file mode 100644 index 1349242bd..000000000 --- a/vendor/github.com/lacework/go-sdk/api/lql_validate.go +++ /dev/null @@ -1,32 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -type ValidateQuery struct { - QueryText string `json:"queryText"` -} - -func (svc *QueryService) Validate(vq ValidateQuery) ( - response QueryResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder( - "POST", apiV2QueriesValidate, vq, &response) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/metrics.go b/vendor/github.com/lacework/go-sdk/api/metrics.go deleted file mode 100644 index dbc7f7f3b..000000000 --- a/vendor/github.com/lacework/go-sdk/api/metrics.go +++ /dev/null @@ -1,110 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2024, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "os" - "runtime" -) - -const DisableTelemetry = "LW_TELEMETRY_DISABLE" - -// MetricsService is a service that sends events to Lacework APIv2 Server metrics endpoint -type MetricsService struct { - client *Client -} - -func (svc *MetricsService) Send(event Honeyvent) (response HoneyEventResponse, err error) { - if disabled := os.Getenv(DisableTelemetry); disabled != "" { - return HoneyEventResponse{Data: []Honeyvent{{TraceID: "Telemetry Disabled"}}}, nil - } - - event.setAccountDetails(*svc.client) - err = svc.client.RequestEncoderDecoder("POST", apiV2HoneyMetrics, event, &response) - return -} - -func NewHoneyvent(version, feature, dataset string) Honeyvent { - event := Honeyvent{ - Os: runtime.GOOS, - Arch: runtime.GOARCH, - TraceID: newID(), - Version: version, - Dataset: dataset, - Feature: feature, - } - - return event -} - -func (h *Honeyvent) setAccountDetails(client Client) { - if h.Account == "" { - h.Account = client.account - } - if h.Subaccount == "" { - h.Subaccount = client.subaccount - } -} - -// Honeyvent defines what a Honeycomb event looks like for the Lacework CLI -type Honeyvent struct { - Version string `json:"version"` - CfgVersion int `json:"config_version"` - Os string `json:"os"` - Arch string `json:"arch"` - Command string `json:"command,omitempty"` - Args []string `json:"args,omitempty"` - Flags []string `json:"flags,omitempty"` - Account string `json:"account,omitempty"` - Subaccount string `json:"subaccount,omitempty"` - Profile string `json:"profile,omitempty"` - ApiKey string `json:"api_key,omitempty"` - Feature string `json:"feature,omitempty"` - FeatureData interface{} `json:"feature.data,omitempty"` - DurationMs int64 `json:"duration_ms,omitempty"` - Error string `json:"error,omitempty"` - InstallMethod string `json:"install_method,omitempty"` - Component string `json:"component,omitempty"` - Dataset string `json:"dataset,omitempty"` - - // tracing data for multiple events, this is useful for specific features - // within the Lacework CLI such as daily version check, polling mechanism, etc. - TraceID string `json:"trace.trace_id,omitempty"` - SpanID string `json:"trace.span_id,omitempty"` - ParentID string `json:"trace.parent_id,omitempty"` - ContextID string `json:"trace.context_id,omitempty"` -} - -type HoneyEventResponse struct { - Data []Honeyvent `json:"data"` - Ok bool `json:"ok"` - Message string `json:"message"` -} - -func (e *Honeyvent) AddFeatureField(key string, value interface{}) { - if e.FeatureData == nil { - e.FeatureData = map[string]interface{}{key: value} - return - } - - if v, ok := e.FeatureData.(map[string]interface{}); ok { - v[key] = value - e.FeatureData = v - } -} diff --git a/vendor/github.com/lacework/go-sdk/api/organization_info.go b/vendor/github.com/lacework/go-sdk/api/organization_info.go deleted file mode 100644 index c15f77521..000000000 --- a/vendor/github.com/lacework/go-sdk/api/organization_info.go +++ /dev/null @@ -1,53 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "github.com/lacework/go-sdk/lwdomain" - -// OrganizationInfoService is the service that interacts with -// the OrganizationInfo schema from the Lacework APIv2 Server -type OrganizationInfoService struct { - client *Client -} - -func (svc *OrganizationInfoService) Get() ( - response OrganizationInfoResponse, - err error, -) { - err = svc.client.RequestDecoder("GET", - apiV2OrganizationInfo, - nil, - &response, - ) - return -} - -type OrganizationInfoResponse struct { - Data []OrganizationInfo `json:"data"` -} - -type OrganizationInfo struct { - OrgAccount bool `json:"orgAccount"` - OrgAccountURL string `json:"orgAccountUrl,omitempty"` -} - -func (r OrganizationInfo) AccountName() string { - d, _ := lwdomain.New(r.OrgAccountURL) - return d.String() -} diff --git a/vendor/github.com/lacework/go-sdk/api/policy.go b/vendor/github.com/lacework/go-sdk/api/policy.go deleted file mode 100644 index e5a832332..000000000 --- a/vendor/github.com/lacework/go-sdk/api/policy.go +++ /dev/null @@ -1,364 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/json" - "fmt" - "net/url" - "reflect" - "time" - - "github.com/lacework/go-sdk/internal/array" - "github.com/pkg/errors" - "gopkg.in/yaml.v3" -) - -// PolicyService is a service that interacts with the Custom Policies -// endpoints from the Lacework Server -type PolicyService struct { - client *Client - Exceptions *policyExceptionsService -} - -func NewV2PolicyService(c *Client) *PolicyService { - return &PolicyService{c, - &policyExceptionsService{c}, - } -} - -type policyType int -type policyTypes map[policyType]string - -const ( - PolicyTypeCompliance policyType = iota - PolicyTypeManual - PolicyTypeViolation -) - -var ValidPolicyTypes = policyTypes{ - PolicyTypeCompliance: "Compliance", - PolicyTypeManual: "Manual", - PolicyTypeViolation: "Violation", -} - -func (p policyType) String() string { - return ValidPolicyTypes[p] -} - -func (pt policyTypes) String() (types []string) { - for _, v := range pt { - types = append(types, v) - } - return -} - -// ValidPolicySeverities is a list of all valid policy severities -var ValidPolicySeverities = []string{"critical", "high", "medium", "low", "info"} - -type NewPolicy struct { - PolicyID string `json:"policyId,omitempty" yaml:"policyId,omitempty" ` - PolicyType string `json:"policyType" yaml:"policyType"` - QueryID string `json:"queryId" yaml:"queryId"` - Title string `json:"title" yaml:"title"` - Enabled bool `json:"enabled" yaml:"enabled"` - Description string `json:"description" yaml:"description"` - Remediation string `json:"remediation" yaml:"remediation"` - Severity string `json:"severity" yaml:"severity"` - Limit int `json:"limit,omitempty" yaml:"limit,omitempty"` - EvalFrequency string `json:"evalFrequency,omitempty" yaml:"evalFrequency,omitempty"` - AlertEnabled bool `json:"alertEnabled" yaml:"alertEnabled"` - AlertProfile string `json:"alertProfile,omitempty" yaml:"alertProfile,omitempty"` - Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` -} - -type newPoliciesYAML struct { - Policies []NewPolicy `yaml:"policies"` -} - -func ParseNewPolicy(s string) (NewPolicy, error) { - var policy NewPolicy - var err error - - // valid json - if err = json.Unmarshal([]byte(s), &policy); err == nil { - return policy, err - } - // nested yaml - var policies newPoliciesYAML - - if err = yaml.Unmarshal([]byte(s), &policies); err == nil { - if len(policies.Policies) > 0 { - return policies.Policies[0], err - } - } - // straight yaml - policy = NewPolicy{} - err = yaml.Unmarshal([]byte(s), &policy) - if err == nil && !reflect.DeepEqual(policy, NewPolicy{}) { // empty string unmarshals w/o error - return policy, nil - } - // invalid policy - return policy, errors.New("policy must be valid JSON or YAML") -} - -/* - In order to properly PATCH we need to omit items that aren't specified. - -For booleans and integers Golang will omit zero values false and 0 respectively. -This would prevent someone from toggling something to disabled or 0 respectively. -As such we are using pointers instead of primitives for booleans and integers in this struct -*/ -type UpdatePolicy struct { - PolicyID string `json:"policyId,omitempty" yaml:"policyId,omitempty"` - PolicyType string `json:"policyType,omitempty" yaml:"policyType,omitempty"` - QueryID string `json:"queryId,omitempty" yaml:"queryId,omitempty"` - Title string `json:"title,omitempty" yaml:"title,omitempty"` - Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Remediation string `json:"remediation,omitempty" yaml:"remediation,omitempty"` - Severity string `json:"severity,omitempty" yaml:"severity,omitempty"` - Limit *int `json:"limit,omitempty" yaml:"limit,omitempty"` - EvalFrequency string `json:"evalFrequency,omitempty" yaml:"evalFrequency,omitempty"` - AlertEnabled *bool `json:"alertEnabled,omitempty" yaml:"alertEnabled,omitempty"` - AlertProfile string `json:"alertProfile,omitempty" yaml:"alertProfile,omitempty"` - Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` -} - -type updatePoliciesYAML struct { - Policies []UpdatePolicy `yaml:"policies"` -} - -func ParseUpdatePolicy(s string) (UpdatePolicy, error) { - var policy UpdatePolicy - var err error - - // valid json - if err = json.Unmarshal([]byte(s), &policy); err == nil { - return policy, err - } - // nested yaml - var policies updatePoliciesYAML - - if err = yaml.Unmarshal([]byte(s), &policies); err == nil { - if len(policies.Policies) > 0 { - return policies.Policies[0], err - } - } - // straight yaml - policy = UpdatePolicy{} - err = yaml.Unmarshal([]byte(s), &policy) - if err == nil && !reflect.DeepEqual(policy, UpdatePolicy{}) { // empty string unmarshals w/o error - return policy, nil - } - // invalid policy - return policy, errors.New("policy must be valid JSON or YAML") -} - -type ExceptionConfigMap map[string][]PolicyExceptionConfigurationConstraints -type Policy struct { - PolicyID string `json:"policyId" yaml:"policyId"` - PolicyType string `json:"policyType" yaml:"-"` - QueryID string `json:"queryId" yaml:"queryId"` - Title string `json:"title" yaml:"title"` - Enabled bool `json:"enabled" yaml:"enabled"` - Description string `json:"description" yaml:"description"` - Remediation string `json:"remediation" yaml:"remediation"` - Severity string `json:"severity" yaml:"severity"` - Limit int `json:"limit" yaml:"limit"` - EvalFrequency string `json:"evalFrequency" yaml:"evalFrequency"` - AlertEnabled bool `json:"alertEnabled" yaml:"alertEnabled"` - AlertProfile string `json:"alertProfile" yaml:"alertProfile"` - Tags []string `json:"tags" yaml:"tags"` - Owner string `json:"owner" yaml:"-"` - LastUpdateTime string `json:"lastUpdateTime" yaml:"-"` - LastUpdateUser string `json:"lastUpdateUser" yaml:"-"` - ExceptionConfiguration ExceptionConfigMap `json:"exceptionConfiguration" yaml:"-"` -} - -type PolicyExceptionConfigurationConstraints struct { - DataType string `json:"dataType" yaml:"dataType"` - FieldKey string `json:"fieldKey" yaml:"fieldKey"` - MultiValue bool `json:"multiValue" yaml:"multiValue"` -} - -func (p *Policy) HasTag(t string) bool { - return array.ContainsStr(p.Tags, t) -} - -type PolicyResponse struct { - Data Policy `json:"data"` - Message string `json:"message"` -} - -type PolicyTagsResponse struct { - Data []string `json:"data"` - Message string `json:"message"` -} - -type PoliciesResponse struct { - Data []Policy `json:"data"` - Message string `json:"message"` -} - -func (svc *PolicyService) Create(np NewPolicy) ( - response PolicyResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder("POST", apiV2Policies, np, &response) - return -} - -func (svc *PolicyService) List() ( - response PoliciesResponse, - err error, -) { - err = svc.client.RequestDecoder("GET", apiV2Policies, nil, &response) - return -} - -func (svc *PolicyService) ListTags() ( - response PolicyTagsResponse, - err error, -) { - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf("%s/Tags", apiV2Policies), - nil, - &response, - ) - return -} - -func (svc *PolicyService) Get(policyID string) ( - response PolicyResponse, - err error, -) { - if policyID == "" { - err = errors.New("policy ID must be provided") - return - } - err = svc.client.RequestDecoder( - "GET", - fmt.Sprintf("%s/%s", apiV2Policies, url.QueryEscape(policyID)), - nil, - &response, - ) - return -} - -func (svc *PolicyService) Update(up UpdatePolicy) ( - response PolicyResponse, - err error, -) { - if up.PolicyID == "" { - err = errors.New("policy ID must be provided") - return - } - var policyID = up.PolicyID - up.PolicyID = "" // omit this for PATCH - - err = svc.client.RequestEncoderDecoder( - "PATCH", - fmt.Sprintf("%s/%s", apiV2Policies, url.QueryEscape(policyID)), - up, - &response, - ) - return -} - -func (svc *PolicyService) Delete(policyID string) ( - response PolicyResponse, - err error, -) { - if policyID == "" { - err = errors.New("policy ID must be provided") - return - } - err = svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf("%s/%s", apiV2Policies, url.QueryEscape(policyID)), - nil, - &response, - ) - return -} - -type BulkUpdatePolicy struct { - PolicyID string `json:"policyId,omitempty" yaml:"policyId,omitempty"` - Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` - Severity string `json:"severity,omitempty" yaml:"severity,omitempty"` -} - -type BulkUpdatePolicies []BulkUpdatePolicy - -// UpdateMany supports updating the state(enabled/disabled) and severity of more than one -// policy using the policy bulk update api -func (svc *PolicyService) UpdateMany(policies BulkUpdatePolicies) ( - response BulkPolicyUpdateResponse, - err error, -) { - if len(policies) == 0 { - err = errors.New("a list of policies must be provided") - return - } - - err = svc.client.RequestEncoderDecoder( - "PATCH", - apiV2Policies, - policies, - &response, - ) - return -} - -type BulkPolicyUpdateResponse struct { - Data []BulkPolicyUpdateResponseData `json:"data"` -} - -type BulkPolicyUpdateResponseData struct { - EvaluatorId string `json:"evaluatorId,omitempty"` - PolicyId string `json:"policyId"` - PolicyType string `json:"policyType"` - QueryId string `json:"queryId,omitempty"` - QueryText string `json:"queryText,omitempty"` - Title string `json:"title"` - Enabled bool `json:"enabled,omitempty"` - Description string `json:"description"` - Remediation string `json:"remediation"` - Severity string `json:"severity"` - Limit int `json:"limit,omitempty"` - EvalFrequency string `json:"evalFrequency,omitempty"` - AlertEnabled bool `json:"alertEnabled,omitempty"` - AlertProfile string `json:"alertProfile,omitempty"` - Owner string `json:"owner"` - LastUpdateTime time.Time `json:"lastUpdateTime"` - LastUpdateUser string `json:"lastUpdateUser"` - Tags []string `json:"tags"` - InfoLink string `json:"infoLink,omitempty"` - ExceptionConfiguration struct { - ConstraintFields []struct { - FieldKey string `json:"fieldKey"` - DataType string `json:"dataType"` - MultiValue bool `json:"multiValue"` - } `json:"constraintFields"` - } `json:"exceptionConfiguration,omitempty"` - References []string `json:"references,omitempty"` - AdditionalInformation string `json:"additionalInformation,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/policy_exceptions.go b/vendor/github.com/lacework/go-sdk/api/policy_exceptions.go deleted file mode 100644 index ba83e3825..000000000 --- a/vendor/github.com/lacework/go-sdk/api/policy_exceptions.go +++ /dev/null @@ -1,111 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "errors" - "fmt" -) - -// policyExceptionsService is the service that interacts with -// the Exceptions schema from the Lacework APIv2 Server -type policyExceptionsService struct { - client *Client -} - -// List returns a list of the Policy Exceptions for a policy ID. -func (svc policyExceptionsService) List(policyID string) (response PolicyExceptionsResponse, err error) { - if policyID == "" { - return response, errors.New("specify a policy ID") - } - err = svc.client.RequestDecoder("GET", fmt.Sprintf(apiV2PolicyExceptions, policyID), nil, &response) - return -} - -// Get returns a raw response of the Policy Exception with the matching policy ID and exception ID. -func (svc policyExceptionsService) Get(policyID string, exceptionID string, response interface{}) error { - if exceptionID == "" || policyID == "" { - return errors.New("specify exception and policy IDs") - } - apiPath := fmt.Sprintf(apiV2PolicyExceptionsFromExceptionID, exceptionID, policyID) - return svc.client.RequestDecoder("GET", apiPath, nil, &response) -} - -func (svc policyExceptionsService) Delete(policyID string, exceptionID string) error { - if exceptionID == "" || policyID == "" { - return errors.New("specify exception and policy IDs") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2PolicyExceptionsFromExceptionID, exceptionID, policyID), - nil, - nil, - ) -} - -// Create creates a single Policy Exception -func (svc *policyExceptionsService) Create(policyID string, policy PolicyException) ( - response PolicyExceptionResponse, - err error, -) { - if policyID == "" { - return response, errors.New("specify a policy ID") - } - err = svc.client.RequestEncoderDecoder("POST", fmt.Sprintf(apiV2PolicyExceptions, policyID), - policy, &response) - return -} - -// Update updates a single Policy Exception -func (svc policyExceptionsService) Update(policyID string, exception PolicyException) ( - response PolicyExceptionResponse, err error, -) { - if exception.ExceptionID == "" || policyID == "" { - return response, errors.New("specify exception and policy IDs") - } - apiPath := fmt.Sprintf(apiV2PolicyExceptionsFromExceptionID, exception.ExceptionID, policyID) - // Request is invalid if it contains the ExceptionID, LastUpdatedTime or LastUpdatedUser fields. - exception.ExceptionID = "" - exception.LastUpdateUser = "" - exception.LastUpdateTime = "" - - err = svc.client.RequestEncoderDecoder("PATCH", apiPath, exception, &response) - return -} - -type PolicyExceptionResponse struct { - Data PolicyException `json:"data"` -} - -type PolicyExceptionsResponse struct { - Data []PolicyException `json:"data"` -} -type PolicyException struct { - ExceptionID string `json:"exceptionId,omitempty"` - Description string `json:"description"` - Constraints []PolicyExceptionConstraint `json:"constraints"` - LastUpdateTime string `json:"lastUpdateTime,omitempty"` - LastUpdateUser string `json:"lastUpdateUser,omitempty"` -} - -type PolicyExceptionConstraint struct { - FieldKey string `json:"fieldKey"` - FieldValues []any `json:"fieldValues"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/reader.go b/vendor/github.com/lacework/go-sdk/api/reader.go deleted file mode 100644 index 931264872..000000000 --- a/vendor/github.com/lacework/go-sdk/api/reader.go +++ /dev/null @@ -1,33 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "bytes" - "encoding/json" - "io" -) - -// jsonReader takes any arbitrary type and synthesizes a streaming encoder -func jsonReader(v interface{}) (r io.Reader, err error) { - buf := new(bytes.Buffer) - err = json.NewEncoder(buf).Encode(v) - r = bytes.NewReader(buf.Bytes()) - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/report_rule_notification_types.go b/vendor/github.com/lacework/go-sdk/api/report_rule_notification_types.go deleted file mode 100644 index 77a29d756..000000000 --- a/vendor/github.com/lacework/go-sdk/api/report_rule_notification_types.go +++ /dev/null @@ -1,269 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/json" - "fmt" - - "github.com/fatih/structs" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" -) - -type reportRuleNotification interface { - ToMap() map[string]bool -} - -type ReportRuleNotifications []reportRuleNotification - -// Enable all Gcp report rules -var AllGcpReportRuleNotifications = new(GcpReportRuleNotifications).allNotifications() - -// Enable all Aws report rules -var AllAwsReportRuleNotifications = new(AwsReportRuleNotifications).allNotifications() - -// Enable all Azure report rules -var AllAzureReportRuleNotifications = new(AzureReportRuleNotifications).allNotifications() - -// Enable all Daily report rules -var AllDailyReportRuleNotifications = new(DailyEventsReportRuleNotifications).allNotifications() - -// Enable all Weekly report rules -var AllWeeklyReportRuleNotifications = new(WeeklyEventsReportRuleNotifications).allNotifications() - -// Enable all report rules -var AllReportRuleNotifications = new(ReportRuleNotificationTypes).allNotifications() - -func TransformReportRuleNotification(notificationsMap map[string]bool, notificationType reportRuleNotification) error { - jsonMap, err := json.Marshal(notificationsMap) - if err != nil { - return err - } - - err = json.Unmarshal(jsonMap, ¬ificationType) - if err != nil { - return err - } - - return nil -} - -func NewReportRuleNotificationTypes(types []reportRuleNotification) (ReportRuleNotificationTypes, error) { - notificationsTypes := ReportRuleNotificationTypes{} - notificationsMap := make(map[string]bool) - - for _, notificationType := range types { - m := structs.Map(notificationType) - for k, v := range m { - if _, ok := notificationsMap[k]; ok { - return ReportRuleNotificationTypes{}, errors.New(fmt.Sprintf("notification types contains a duplicate type: %s", k)) - } - notificationsMap[k] = v.(bool) - } - } - - err := mapstructure.Decode(notificationsMap, ¬ificationsTypes) - if err != nil { - return ReportRuleNotificationTypes{}, errors.New("unable to set report rule notification types") - } - - return notificationsTypes, nil -} - -func reportRuleNotificationToMap(notificationType reportRuleNotification) map[string]bool { - notificationsMap := make(map[string]bool) - m := structs.Map(notificationType) - for k, v := range m { - if v.(bool) { - notificationsMap[k] = true - } else { - notificationsMap[k] = false - } - } - return notificationsMap -} - -type GcpReportRuleNotifications struct { - GcpCis bool `json:"gcpCis"` - GcpHipaa bool `json:"gcpHipaa"` - GcpHipaaRev2 bool `json:"gcpHipaaRev2"` - GcpIso27001 bool `json:"gcpIso27001"` - GcpCis12 bool `json:"gcpCis12"` - GcpK8s bool `json:"gcpK8s"` - GcpPci bool `json:"gcpPci"` - GcpPciRev2 bool `json:"gcpPciRev2"` - GcpSoc bool `json:"gcpSoc"` - GcpSocRev2 bool `json:"gcpSocRev2"` -} - -func (gcp GcpReportRuleNotifications) allNotifications() GcpReportRuleNotifications { - return GcpReportRuleNotifications{ - GcpCis: true, - GcpHipaa: true, - GcpHipaaRev2: true, - GcpIso27001: true, - GcpCis12: true, - GcpK8s: true, - GcpPci: true, - GcpPciRev2: true, - GcpSoc: true, - GcpSocRev2: true, - } -} - -func (gcp GcpReportRuleNotifications) ToMap() map[string]bool { - return reportRuleNotificationToMap(gcp) -} - -type AwsReportRuleNotifications struct { - AwsCisS3 bool `json:"awsCisS3"` - AwsHipaa bool `json:"hipaa"` - AwsIso2700 bool `json:"iso2700"` - AwsNist80053Rev4 bool `json:"nist800-53Rev4"` - AwsNist800171Rev2 bool `json:"nist800-171Rev2"` - AwsPci bool `json:"pci"` - AwsSoc bool `json:"soc"` - AwsSocRev2 bool `json:"awsSocRev2"` -} - -func (aws AwsReportRuleNotifications) allNotifications() AwsReportRuleNotifications { - return AwsReportRuleNotifications{ - AwsCisS3: true, - AwsHipaa: true, - AwsIso2700: true, - AwsNist80053Rev4: true, - AwsNist800171Rev2: true, - AwsPci: true, - AwsSoc: true, - AwsSocRev2: true, - } -} - -func (aws AwsReportRuleNotifications) ToMap() map[string]bool { - return reportRuleNotificationToMap(aws) -} - -type AzureReportRuleNotifications struct { - AzureCis bool `json:"azureCis"` - AzureCis131 bool `json:"azureCis131"` - AzurePci bool `json:"azurePci"` - AzureSoc bool `json:"azureSoc"` -} - -func (az AzureReportRuleNotifications) allNotifications() AzureReportRuleNotifications { - return AzureReportRuleNotifications{ - AzureCis: true, - AzureCis131: true, - AzurePci: true, - AzureSoc: true, - } -} - -func (az AzureReportRuleNotifications) ToMap() map[string]bool { - return reportRuleNotificationToMap(az) -} - -type DailyEventsReportRuleNotifications struct { - AgentEvents bool `json:"agentEvents"` - OpenShiftCompliance bool `json:"openShiftCompliance"` - OpenShiftComplianceEvents bool `json:"openShiftComplianceEvents"` - PlatformEvents bool `json:"platformEvents"` - AwsCloudtrailEvents bool `json:"awsCloudtrailEvents"` - AwsComplianceEvents bool `json:"awsComplianceEvents"` - AzureComplianceEvents bool `json:"azureComplianceEvents"` - AzureActivityLogEvents bool `json:"azureActivityLogEvents"` - GcpAuditTrailEvents bool `json:"gcpAuditTrailEvents"` - GcpComplianceEvents bool `json:"gcpComplianceEvents"` -} - -func (daily DailyEventsReportRuleNotifications) allNotifications() DailyEventsReportRuleNotifications { - return DailyEventsReportRuleNotifications{ - AgentEvents: true, - OpenShiftCompliance: true, - OpenShiftComplianceEvents: true, - PlatformEvents: true, - AwsCloudtrailEvents: true, - AwsComplianceEvents: true, - AzureComplianceEvents: true, - AzureActivityLogEvents: true, - GcpAuditTrailEvents: true, - GcpComplianceEvents: true, - } -} - -func (daily DailyEventsReportRuleNotifications) ToMap() map[string]bool { - return reportRuleNotificationToMap(daily) -} - -type WeeklyEventsReportRuleNotifications struct { - TrendReport bool `json:"trendReport"` -} - -func (weekly WeeklyEventsReportRuleNotifications) allNotifications() WeeklyEventsReportRuleNotifications { - return WeeklyEventsReportRuleNotifications{ - TrendReport: true, - } -} - -func (weekly WeeklyEventsReportRuleNotifications) ToMap() map[string]bool { - return reportRuleNotificationToMap(weekly) -} - -func (all ReportRuleNotificationTypes) allNotifications() ReportRuleNotificationTypes { - return ReportRuleNotificationTypes{ - AgentEvents: true, - AwsCisS3: true, - AwsCloudtrailEvents: true, - AwsComplianceEvents: true, - AwsHipaa: true, - AwsIso2700: true, - AwsNist80053Rev4: true, - AwsNist800171Rev2: true, - AwsPci: true, - AwsSoc: true, - AwsSocRev2: true, - AzureActivityLogEvents: true, - AzureCis: true, - AzureCis131: true, - AzureComplianceEvents: true, - AzurePci: true, - AzureSoc: true, - GcpAuditTrailEvents: true, - GcpCis: true, - GcpComplianceEvents: true, - GcpHipaa: true, - GcpHipaaRev2: true, - GcpIso27001: true, - GcpCis12: true, - GcpK8s: true, - GcpPci: true, - GcpPciRev2: true, - GcpSoc: true, - GcpSocRev2: true, - OpenShiftCompliance: true, - OpenShiftComplianceEvents: true, - PlatformEvents: true, - TrendReport: true, - } -} - -func (all ReportRuleNotificationTypes) ToMap() map[string]bool { - return reportRuleNotificationToMap(all) -} diff --git a/vendor/github.com/lacework/go-sdk/api/report_rules.go b/vendor/github.com/lacework/go-sdk/api/report_rules.go deleted file mode 100644 index 6f784849c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/report_rules.go +++ /dev/null @@ -1,305 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" -) - -// ReportRulesService is the service that interacts with -// the ReportRules schema from the Lacework APIv2 Server -type ReportRulesService struct { - client *Client -} - -type reportRuleSeverity int - -type ReportRuleSeverities []reportRuleSeverity - -const ReportRuleEventType = "Report" - -func (sevs ReportRuleSeverities) toInt() []int { - var res []int - for _, i := range sevs { - res = append(res, int(i)) - } - return res -} - -func (sevs ReportRuleSeverities) ToStringSlice() []string { - var res []string - for _, i := range sevs { - switch i { - case ReportRuleSeverityCritical: - res = append(res, "Critical") - case ReportRuleSeverityHigh: - res = append(res, "High") - case ReportRuleSeverityMedium: - res = append(res, "Medium") - case ReportRuleSeverityLow: - res = append(res, "Low") - case ReportRuleSeverityInfo: - res = append(res, "Info") - default: - continue - } - } - return res -} - -func NewReportRuleSeverities(sevSlice []string) ReportRuleSeverities { - var res ReportRuleSeverities - for _, i := range sevSlice { - sev := convertReportRuleSeverity(i) - if sev != ReportRuleSeverityUnknown { - res = append(res, sev) - } - } - return res -} - -func NewReportRuleSeveritiesFromIntSlice(sevSlice []int) ReportRuleSeverities { - var res ReportRuleSeverities - for _, i := range sevSlice { - sev := convertReportRuleSeverityInt(i) - if sev != ReportRuleSeverityUnknown { - res = append(res, sev) - } - } - return res -} - -func convertReportRuleSeverity(sev string) reportRuleSeverity { - switch strings.ToLower(sev) { - case "critical": - return ReportRuleSeverityCritical - case "high": - return ReportRuleSeverityHigh - case "medium": - return ReportRuleSeverityMedium - case "low": - return ReportRuleSeverityLow - case "info": - return ReportRuleSeverityInfo - default: - return ReportRuleSeverityUnknown - } -} - -func convertReportRuleSeverityInt(sev int) reportRuleSeverity { - switch sev { - case 1: - return ReportRuleSeverityCritical - case 2: - return ReportRuleSeverityHigh - case 3: - return ReportRuleSeverityMedium - case 4: - return ReportRuleSeverityLow - case 5: - return ReportRuleSeverityInfo - default: - return ReportRuleSeverityUnknown - } -} - -const ( - ReportRuleSeverityCritical reportRuleSeverity = 1 - ReportRuleSeverityHigh reportRuleSeverity = 2 - ReportRuleSeverityMedium reportRuleSeverity = 3 - ReportRuleSeverityLow reportRuleSeverity = 4 - ReportRuleSeverityInfo reportRuleSeverity = 5 - ReportRuleSeverityUnknown reportRuleSeverity = 0 -) - -// NewReportRule returns an instance of the ReportRule struct -// -// Basic usage: Initialize a new ReportRule struct, then -// -// use the new instance to do CRUD operations -// -// client, err := api.NewClient("account") -// if err != nil { -// return err -// } -// -// reportRule := api.NewReportRule( -// "Foo", -// api.ReportRuleConfig{ -// Description: "My Report Rule" -// Severities: api.ReportRuleSeverities{api.ReportRuleSeverityHigh, -// EmailAlertChannels: []string{"TECHALLY_000000000000AAAAAAAAAAAAAAAAAAAA"}, -// ResourceGroups: []string{"TECHALLY_111111111111AAAAAAAAAAAAAAAAAAAA"} -// ReportNotificationTypes: api.WeeklyEventsReportRuleNotifications{TrendReport: true}, -// }, -// }, -// ) -// -// client.V2.ReportRules.Create(reportRule) -func NewReportRule(name string, rule ReportRuleConfig) (ReportRule, error) { - notifications, err := NewReportRuleNotificationTypes(rule.NotificationTypes) - if err != nil { - return ReportRule{}, err - } - - return ReportRule{ - EmailAlertChannels: rule.EmailAlertChannels, - Type: ReportRuleEventType, - Filter: ReportRuleFilter{ - Name: name, - Enabled: 1, - Description: rule.Description, - Severity: rule.Severities.toInt(), - ResourceGroups: rule.ResourceGroups, - }, - ReportNotificationTypes: notifications, - }, nil -} - -func (rule ReportRuleFilter) Status() string { - if rule.Enabled == 1 { - return "Enabled" - } - return "Disabled" -} - -// List returns a list of Report Rules -func (svc *ReportRulesService) List() (response ReportRulesResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2ReportRules, nil, &response) - return -} - -// Create creates a single Report Rule -func (svc *ReportRulesService) Create(rule ReportRule) ( - response ReportRuleResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder("POST", apiV2ReportRules, rule, &response) - return -} - -// Delete deletes a Report Rule that matches the provided guid -func (svc *ReportRulesService) Delete(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2ReportRuleFromGUID, guid), - nil, - nil, - ) -} - -// Update updates a single Report Rule of the provided guid. -func (svc *ReportRulesService) Update(data ReportRule) ( - response ReportRuleResponse, - err error, -) { - if data.Guid == "" { - err = errors.New("specify a Guid") - return - } - apiPath := fmt.Sprintf(apiV2ReportRuleFromGUID, data.Guid) - err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) - return -} - -// Get returns a raw response of the Report Rule with the matching guid. -func (svc *ReportRulesService) Get(guid string, response interface{}) error { - if guid == "" { - return errors.New("specify a Guid") - } - apiPath := fmt.Sprintf(apiV2ReportRuleFromGUID, guid) - return svc.client.RequestDecoder("GET", apiPath, nil, &response) -} - -type ReportRuleConfig struct { - EmailAlertChannels []string - Description string - Severities ReportRuleSeverities - NotificationTypes []reportRuleNotification - ResourceGroups []string -} - -type ReportRule struct { - Guid string `json:"mcGuid,omitempty"` - Type string `json:"type"` - EmailAlertChannels []string `json:"intgGuidList"` - Filter ReportRuleFilter `json:"filters"` - ReportNotificationTypes ReportRuleNotificationTypes `json:"reportNotificationTypes"` -} - -type ReportRuleNotificationTypes struct { - AgentEvents bool `json:"agentEvents"` - AwsCisS3 bool `json:"awsCisS3"` - AwsCloudtrailEvents bool `json:"awsCloudtrailEvents"` - AwsComplianceEvents bool `json:"awsComplianceEvents"` - AwsHipaa bool `json:"hipaa"` - AwsIso2700 bool `json:"iso2700"` - AwsNist80053Rev4 bool `json:"nist800-53Rev4"` - AwsNist800171Rev2 bool `json:"nist800-171Rev2"` - AwsPci bool `json:"pci"` - AwsSoc bool `json:"soc"` - AwsSocRev2 bool `json:"awsSocRev2"` - AzureActivityLogEvents bool `json:"azureActivityLogEvents"` - AzureCis bool `json:"azureCis"` - AzureCis131 bool `json:"azureCis131"` - AzureComplianceEvents bool `json:"azureComplianceEvents"` - AzurePci bool `json:"azurePci"` - AzureSoc bool `json:"azureSoc"` - GcpAuditTrailEvents bool `json:"gcpAuditTrailEvents"` - GcpCis bool `json:"gcpCis"` - GcpComplianceEvents bool `json:"gcpComplianceEvents"` - GcpHipaa bool `json:"gcpHipaa"` - GcpHipaaRev2 bool `json:"gcpHipaaRev2"` - GcpIso27001 bool `json:"gcpIso27001"` - GcpCis12 bool `json:"gcpCis12"` - GcpK8s bool `json:"gcpK8s"` - GcpPci bool `json:"gcpPci"` - GcpPciRev2 bool `json:"gcpPciRev2"` - GcpSoc bool `json:"gcpSoc"` - GcpSocRev2 bool `json:"gcpSocRev2"` - OpenShiftCompliance bool `json:"openShiftCompliance"` - OpenShiftComplianceEvents bool `json:"openShiftComplianceEvents"` - PlatformEvents bool `json:"platformEvents"` - TrendReport bool `json:"trendReport"` -} - -type ReportRuleFilter struct { - Name string `json:"name"` - Enabled int `json:"enabled"` - Description string `json:"description,omitempty"` - Severity []int `json:"severity"` - ResourceGroups []string `json:"resourceGroups,omitempty"` - CreatedOrUpdatedTime string `json:"createdOrUpdatedTime,omitempty"` - CreatedOrUpdatedBy string `json:"createdOrUpdatedBy,omitempty"` -} - -type ReportRuleResponse struct { - Data ReportRule `json:"data"` -} - -type ReportRulesResponse struct { - Data []ReportRule `json:"data"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/reports.go b/vendor/github.com/lacework/go-sdk/api/reports.go deleted file mode 100644 index 5fbc130ba..000000000 --- a/vendor/github.com/lacework/go-sdk/api/reports.go +++ /dev/null @@ -1,123 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// ReportsService is a service that interacts with the Reports -// endpoints from the Lacework APIv2 Server -type ReportsService struct { - client *Client - Aws *awsReportsService - Azure *azureReportsService - Gcp *gcpReportsService -} - -func NewReportsService(c *Client) *ReportsService { - return &ReportsService{c, - &awsReportsService{c}, - &azureReportsService{c}, - &gcpReportsService{c}, - } -} - -// The method by which a report can be retrieved from v2/Reports/ api -// can be 'reportName' or 'reportType' -type reportFilter int - -const ( - ReportFilterType reportFilter = iota - ReportFilterName -) - -// reportFilterTypes is the list of available report filter types -var reportFilterTypes = map[reportFilter]string{ - ReportFilterType: "reportType", - ReportFilterName: "reportName", -} - -func (r reportFilter) String() string { - return reportFilterTypes[r] -} - -type ReportSummary struct { - NumRecommendations int `json:"NUM_RECOMMENDATIONS"` - NumSeverity2NonCompliance int `json:"NUM_SEVERITY_2_NON_COMPLIANCE"` - NumSeverity4NonCompliance int `json:"NUM_SEVERITY_4_NON_COMPLIANCE"` - NumSeverity1NonCompliance int `json:"NUM_SEVERITY_1_NON_COMPLIANCE"` - NumCompliant int `json:"NUM_COMPLIANT"` - NumSeverity3NonCompliance int `json:"NUM_SEVERITY_3_NON_COMPLIANCE"` - AssessedResourceCount int `json:"ASSESSED_RESOURCE_COUNT"` - NumSuppressed int `json:"NUM_SUPPRESSED"` - NumSeverity5NonCompliance int `json:"NUM_SEVERITY_5_NON_COMPLIANCE"` - NumNotComplinace int `json:"NUM_NOT_COMPLIANT"` - ViolatedResourceCount int `json:"VIOLATED_RESOURCE_COUNT"` - SuppressedResourceCount int `json:"SUPPRESSED_RESOURCE_COUNT"` -} - -type RecommendationV2 struct { - AccountID string `json:"ACCOUNT_ID"` - AccountAlias string `json:"ACCOUNT_ALIAS"` - Service string `json:"SERVICE"` - StartTime int64 `json:"START_TIME"` - Suppressions []string `json:"SUPPRESSIONS"` - InfoLink string `json:"INFO_LINK"` - AssessedResourceCount int `json:"ASSESSED_RESOURCE_COUNT"` - Status string `json:"STATUS"` - RecID string `json:"REC_ID"` - Category string `json:"CATEGORY"` - Title string `json:"TITLE"` - Violations []ComplianceViolationV2 `json:"VIOLATIONS"` - ResourceCount int `json:"RESOURCE_COUNT"` - Severity int `json:"SEVERITY"` -} - -type ComplianceViolationV2 struct { - Region string `json:"region"` - Resource string `json:"resource"` - Reasons []string `json:"reasons"` -} - -func (r *RecommendationV2) SeverityString() string { - switch r.Severity { - case 1: - return "Critical" - case 2: - return "High" - case 3: - return "Medium" - case 4: - return "Low" - case 5: - return "Info" - default: - return "Unknown" - } -} - -type CloudComplianceReportV2 interface { - GetComplianceRecommendation(recommendationID string) (*RecommendationV2, bool) -} - -// ValidComplianceStatus is a list of all valid compliance status -var ValidComplianceStatus = []string{ - "non-compliant", - "requires-manual-assessment", - "suppressed", - "compliant", - "could-not-assess", -} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_aws.go b/vendor/github.com/lacework/go-sdk/api/reports_aws.go deleted file mode 100644 index f793a3db1..000000000 --- a/vendor/github.com/lacework/go-sdk/api/reports_aws.go +++ /dev/null @@ -1,174 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "io" - "os" - "sort" - "time" - - "github.com/pkg/errors" -) - -// v2AwsReportsService is a service that interacts with the APIv2 -type awsReportsService struct { - client *Client -} - -const ComplianceReportDefaultAws = "CIS Amazon Web Services Foundations Benchmark v1.4.0" - -type AwsReportConfig struct { - AccountID string - Value string - Parameter reportFilter -} - -type AwsReportType int - -func (report AwsReportType) String() string { - return awsReportTypes[report] -} - -func NewAwsReportType(report string) (AwsReportType, error) { - for k, v := range awsReportTypes { - if v == report { - return k, nil - } - } - return NONE_AWS_REPORT, errors.Errorf("no report type found for %s", report) -} - -func AwsReportTypes() []string { - reportTypes := make([]string, 0, len(awsReportTypes)) - - for _, report := range awsReportTypes { - reportTypes = append(reportTypes, report) - } - - sort.Strings(reportTypes) - return reportTypes -} - -var awsReportTypes = map[AwsReportType]string{AWS_CIS_S3: "AWS_CIS_S3", NIST_800_53_Rev4: "NIST_800-53_Rev4", - NIST_800_171_Rev2: "NIST_800-171_Rev2", ISO_2700: "ISO_2700", HIPAA: "HIPAA", SOC: "SOC", - AWS_SOC_Rev2: "AWS_SOC_Rev2", PCI: "PCI", AWS_CIS_14: "AWS_CIS_14", AWS_CMMC_1_02: "AWS_CMMC_1.02", - AWS_ISO_27001_2013: "AWS_ISO_27001:2013", AWS_NIST_CSF: "AWS_NIST_CSF", AWS_HIPAA: "AWS_HIPAA", - AWS_NIST_800_53_rev5: "AWS_NIST_800-53_rev5", AWS_NIST_800_171_rev2: "AWS_NIST_800-171_rev2", - AWS_PCI_DSS_3_2_1: "AWS_PCI_DSS_3.2.1", AWS_SOC_2: "AWS_SOC_2", LW_AWS_SEC_ADD_1_0: "LW_AWS_SEC_ADD_1_0", - AWS_CIS_1_4_ISO_IEC_27002_2022: "AWS_CIS_1_4_ISO_IEC_27002_2022", AWS_CYBER_ESSENTIALS_2_2: "AWS_Cyber_Essentials_2_2", - AWS_CSA_CCM_4_0_5: "AWS_CSA_CCM_4_0_5"} - -const ( - NONE_AWS_REPORT AwsReportType = iota - AWS_CIS_S3 - NIST_800_53_Rev4 - NIST_800_171_Rev2 - ISO_2700 - HIPAA - SOC - AWS_SOC_Rev2 - PCI - AWS_CIS_14 - AWS_CMMC_1_02 - AWS_HIPAA - AWS_ISO_27001_2013 - AWS_NIST_CSF - AWS_NIST_800_171_rev2 - AWS_NIST_800_53_rev5 - AWS_PCI_DSS_3_2_1 - AWS_SOC_2 - LW_AWS_SEC_ADD_1_0 - AWS_CIS_1_4_ISO_IEC_27002_2022 - AWS_CYBER_ESSENTIALS_2_2 - AWS_CSA_CCM_4_0_5 -) - -// Get returns an AwsReportResponse -func (svc *awsReportsService) Get(reportCfg AwsReportConfig) (response AwsReportResponse, err error) { - if reportCfg.AccountID == "" { - return AwsReportResponse{}, errors.New("specify an account id") - } - - apiPath := fmt.Sprintf(apiV2Reports, reportCfg.AccountID, "json", reportCfg.Parameter.String(), reportCfg.Value) - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} - -func (svc *awsReportsService) DownloadPDF(filepath string, config AwsReportConfig) error { - if config.AccountID == "" { - return errors.New("account id is required") - } - - // if name is set in config, fetch report by case - apiPath := fmt.Sprintf(apiV2Reports, config.AccountID, "pdf", config.Parameter.String(), config.Value) - - request, err := svc.client.NewRequest("GET", apiPath, nil) - if err != nil { - return err - } - - response, err := svc.client.Do(request) - if err != nil { - return err - } - defer response.Body.Close() - - err = checkErrorInResponse(response) - if err != nil { - return err - } - - // Create the file - out, err := os.Create(filepath) - if err != nil { - return err - } - defer out.Close() - - // Write the body to file - _, err = io.Copy(out, response.Body) - return err -} - -func (aws AwsReport) GetComplianceRecommendation(recommendationID string) (*RecommendationV2, bool) { - for _, r := range aws.Recommendations { - if r.RecID == recommendationID { - return &r, true - } - } - return nil, false -} - -type AwsReportResponse struct { - Data []AwsReport `json:"data"` - Ok bool `json:"ok"` - Message string `json:"message"` -} - -type AwsReport struct { - ReportType string `json:"reportType"` - ReportTitle string `json:"reportTitle"` - Recommendations []RecommendationV2 `json:"recommendations"` - Summary []ReportSummary `json:"summary"` - AccountID string `json:"accountId"` - AccountAlias string `json:"accountAlias"` - ReportTime time.Time `json:"reportTime"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_azure.go b/vendor/github.com/lacework/go-sdk/api/reports_azure.go deleted file mode 100644 index b58a08e86..000000000 --- a/vendor/github.com/lacework/go-sdk/api/reports_azure.go +++ /dev/null @@ -1,183 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "io" - "os" - "sort" - "time" - - "github.com/pkg/errors" -) - -// v2AzureReportsService is a service that interacts with the APIv2 -type azureReportsService struct { - client *Client -} - -const ComplianceReportDefaultAzure = "CIS Microsoft Azure Foundations Benchmark v1.5.0" - -type AzureReportConfig struct { - TenantID string - SubscriptionID string - Value string - Parameter reportFilter -} - -type AzureReportType int - -func (report AzureReportType) String() string { - return azureReportTypes[report] -} - -func NewAzureReportType(report string) (AzureReportType, error) { - for k, v := range azureReportTypes { - if v == report { - return k, nil - } - } - return NONE_AZURE_REPORT, errors.Errorf("no report type found for %s", report) -} - -func AzureReportTypes() []string { - reportTypes := make([]string, 0, len(azureReportTypes)) - - for _, report := range azureReportTypes { - reportTypes = append(reportTypes, report) - } - - sort.Strings(reportTypes) - return reportTypes -} - -var azureReportTypes = map[AzureReportType]string{ - AZURE_CIS: "AZURE_CIS", - AZURE_CIS_131: "AZURE_CIS_131", - AZURE_SOC: "AZURE_SOC", - AZURE_SOC_Rev2: "AZURE_SOC_Rev2", - AZURE_PCI: "AZURE_PCI", - AZURE_PCI_Rev2: "AZURE_PCI_Rev2", - AZURE_ISO_27001: "AZURE_ISO_27001", - AZURE_NIST_CSF: "AZURE_NIST_CSF", - AZURE_NIST_800_53_REV5: "AZURE_NIST_800_53_REV5", - AZURE_NIST_800_171_REV2: "AZURE_NIST_800_171_REV2", - AZURE_HIPAA: "AZURE_HIPAA", -} - -const ( - NONE_AZURE_REPORT AzureReportType = iota - AZURE_CIS - AZURE_CIS_131 - AZURE_SOC - AZURE_SOC_Rev2 - AZURE_PCI - AZURE_PCI_Rev2 - AZURE_ISO_27001 - AZURE_NIST_CSF - AZURE_NIST_800_53_REV5 - AZURE_NIST_800_171_REV2 - AZURE_HIPAA -) - -// Get returns an AzureReportResponse -func (svc *azureReportsService) Get(reportCfg AzureReportConfig) (response AzureReportResponse, err error) { - if reportCfg.SubscriptionID == "" { - return AzureReportResponse{}, errors.New("specify an account id") - } - - apiPath := fmt.Sprintf(apiV2ReportsSecondaryQuery, - reportCfg.TenantID, - reportCfg.SubscriptionID, - "json", - reportCfg.Parameter.String(), - reportCfg.Value, - ) - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} - -func (svc *azureReportsService) DownloadPDF(filepath string, config AzureReportConfig) error { - if config.TenantID == "" || config.SubscriptionID == "" { - return errors.New("tenant_id and subscription_id are required") - } - - apiPath := fmt.Sprintf(apiV2ReportsSecondaryQuery, - config.TenantID, - config.SubscriptionID, - "pdf", - config.Parameter.String(), - config.Value, - ) - - request, err := svc.client.NewRequest("GET", apiPath, nil) - if err != nil { - return err - } - - response, err := svc.client.Do(request) - if err != nil { - return err - } - defer response.Body.Close() - - err = checkErrorInResponse(response) - if err != nil { - return err - } - - // Create the file - out, err := os.Create(filepath) - if err != nil { - return err - } - defer out.Close() - - // Write the body to file - _, err = io.Copy(out, response.Body) - return err -} - -func (azure AzureReport) GetComplianceRecommendation(recommendationID string) (*RecommendationV2, bool) { - for _, r := range azure.Recommendations { - if r.RecID == recommendationID { - return &r, true - } - } - return nil, false -} - -type AzureReportResponse struct { - Data []AzureReport `json:"data"` - Ok bool `json:"ok"` - Message string `json:"message"` -} - -type AzureReport struct { - ReportType string `json:"reportType"` - ReportTitle string `json:"reportTitle"` - Recommendations []RecommendationV2 `json:"recommendations"` - Summary []ReportSummary `json:"summary"` - ReportTime time.Time `json:"reportTime"` - SubscriptionName string `json:"subscriptionName"` - SubscriptionID string `json:"SubscriptionID"` - TenantName string `json:"tenantName"` - TenantID string `json:"tenantId"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_definitions.go b/vendor/github.com/lacework/go-sdk/api/reports_definitions.go deleted file mode 100644 index b15466bbc..000000000 --- a/vendor/github.com/lacework/go-sdk/api/reports_definitions.go +++ /dev/null @@ -1,237 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "time" - - "github.com/pkg/errors" -) - -// ReportDefinitionsService is a service that interacts with the ReportDefinitions -// endpoints from the Lacework APIv2 Server -type ReportDefinitionsService struct { - client *Client -} - -// The report definition type. At present "COMPLIANCE" is the only supported type for custom report definitions -type reportDefinitionType int - -const ( - ReportDefinitionTypeCompliance reportDefinitionType = iota -) - -func (reportType reportDefinitionType) String() string { - return reportDefinitionTypes[reportType] -} - -var reportDefinitionTypes = map[reportDefinitionType]string{ - ReportDefinitionTypeCompliance: "COMPLIANCE", -} - -// The report definition subtype. Supported values are "AWS", "Azure", "GCP" -type reportDefinitionSubType int - -const ( - ReportDefinitionSubTypeAws reportDefinitionSubType = iota - ReportDefinitionSubTypeGcp - ReportDefinitionSubTypeAzure -) - -func (subType reportDefinitionSubType) String() string { - return reportDefinitionSubTypes[subType] -} - -func ReportDefinitionSubTypes() (values []string) { - for _, v := range reportDefinitionSubTypes { - values = append(values, v) - } - return -} - -var reportDefinitionSubTypes = map[reportDefinitionSubType]string{ - ReportDefinitionSubTypeAws: "AWS", - ReportDefinitionSubTypeGcp: "GCP", - ReportDefinitionSubTypeAzure: "Azure", -} - -// List returns a ReportDefinitionResponse -func (svc *ReportDefinitionsService) List() (response ReportDefinitionsResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2ReportDefinitions, nil, &response) - return -} - -// Get returns a ReportDefinitionResponse -func (svc *ReportDefinitionsService) Get(guid string) (response ReportDefinitionResponse, err error) { - if guid == "" { - return ReportDefinitionResponse{}, errors.New("specify a report definition guid") - } - apiPath := fmt.Sprintf(apiV2ReportDefinitionsFromGUID, guid) - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} - -// GetVersions returns a list of all versions of a reportDefinition -func (svc *ReportDefinitionsService) GetVersions(guid string) (response ReportDefinitionsResponse, err error) { - if guid == "" { - return ReportDefinitionsResponse{}, errors.New("specify a report definition guid") - } - apiPath := fmt.Sprintf(apiV2ReportDefinitionsVersions, guid) - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} - -// Delete a ReportDefinition -func (svc *ReportDefinitionsService) Delete(guid string) error { - if guid == "" { - return errors.New("specify a report definition guid") - } - - return svc.client.RequestDecoder("DELETE", fmt.Sprintf(apiV2ReportDefinitionsFromGUID, guid), nil, nil) -} - -func (svc *ReportDefinitionsService) Create(report ReportDefinition) (response ReportDefinitionResponse, err error) { - err = svc.client.RequestEncoderDecoder("POST", apiV2ReportDefinitions, report, &response) - return -} - -func (svc *ReportDefinitionsService) Update(guid string, report ReportDefinitionUpdate) ( - response ReportDefinitionResponse, err error, -) { - if guid == "" { - return response, errors.New("specify a report definition guid") - } - - err = svc.client.RequestEncoderDecoder("PATCH", fmt.Sprintf(apiV2ReportDefinitionsFromGUID, guid), report, &response) - return -} - -func (svc *ReportDefinitionsService) Revert(guid string, version int) (response ReportDefinitionResponse, err error) { - if guid == "" { - return response, errors.New("specify a report definition guid") - } - - apiPath := fmt.Sprintf(apiV2ReportDefinitionsRevert, guid, version) - err = svc.client.RequestEncoderDecoder("PATCH", apiPath, "", &response) - return -} - -// NewReportDefinition creates a new report definition for Create function -func NewReportDefinition(cfg ReportDefinitionConfig) ReportDefinition { - return ReportDefinition{ - ReportName: cfg.ReportName, - DisplayName: cfg.DisplayName, - ReportType: cfg.ReportType, - SubReportType: cfg.SubReportType, - ReportDefinitionDetails: ReportDefinitionDetails{Sections: cfg.Sections}, - } -} - -// NewReportDefinitionUpdate creates a new report definition for Update function -func NewReportDefinitionUpdate(cfg ReportDefinitionConfig) ReportDefinitionUpdate { - return ReportDefinitionUpdate{ - ReportName: cfg.ReportName, - DisplayName: cfg.DisplayName, - ReportDefinitionDetails: &ReportDefinitionDetails{Sections: cfg.Sections}, - } -} - -var ReportDefinitionSubtypes = []string{"AWS", "Azure", "GCP"} - -type ReportDefinitionConfig struct { - ReportName string `json:"reportName" yaml:"reportName"` - DisplayName string `json:"displayName" yaml:"displayName"` - ReportType string `json:"reportType" yaml:"reportType"` - SubReportType string `json:"subReportType" yaml:"subReportType"` - Sections []ReportDefinitionSection `json:"sections,omitempty" yaml:"sections,omitempty"` -} - -// ReportDefinitionUpdate represents fields allowed for update request -type ReportDefinitionUpdate struct { - ReportName string `json:"reportName,omitempty" yaml:"reportName,omitempty"` - DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty"` - ReportDefinitionDetails *ReportDefinitionDetails `json:"reportDefinition,omitempty" yaml:"reportDefinition,omitempty"` -} - -type ReportDefinitionsResponse struct { - Data []ReportDefinition `json:"data"` -} - -type ReportDefinitionResponse struct { - Data ReportDefinition `json:"data"` -} - -type ReportDefinition struct { - ReportDefinitionGuid string `json:"reportDefinitionGuid,omitempty" yaml:"reportDefinitionGuid,omitempty"` - ReportName string `json:"reportName" yaml:"reportName"` - DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty"` - ReportType string `json:"reportType" yaml:"reportType"` - ReportNotificationType string `json:"reportNotificationType,omitempty" yaml:"reportNotificationType,omitempty"` - SubReportType string `json:"subReportType" yaml:"subReportType"` - - ReportDefinitionDetails ReportDefinitionDetails `json:"reportDefinition" yaml:"reportDefinition"` - Props *ReportDefinitionProps `json:"props,omitempty" yaml:"props,omitempty"` - DistributionType string `json:"distributionType,omitempty" yaml:"distributionType,omitempty"` - AlertChannels []string `json:"alertChannels,omitempty" yaml:"alertChannels,omitempty"` - Frequency string `json:"frequency,omitempty" yaml:"frequency,omitempty"` - Version int `json:"version,omitempty" yaml:"version,omitempty"` - UpdateType string `json:"updateType,omitempty" yaml:"updateType,omitempty"` - CreatedBy string `json:"createdBy,omitempty" yaml:"createdBy,omitempty"` - CreatedTime *time.Time `json:"createdTime,omitempty" yaml:"createdTime,omitempty"` - Enabled int `json:"enabled,omitempty" yaml:"enabled,omitempty"` -} - -// IsCustom returns true if report definition is user created, not created by SYSTEM -func (report ReportDefinition) IsCustom() bool { - return report.CreatedBy != "SYSTEM" -} - -func (report ReportDefinition) Config() ReportDefinitionConfig { - return ReportDefinitionConfig{ - ReportName: report.ReportName, - ReportType: report.ReportType, - DisplayName: report.DisplayName, - SubReportType: report.SubReportType, - Sections: report.ReportDefinitionDetails.Sections, - } -} - -type ReportDefinitionDetails struct { - Sections []ReportDefinitionSection `json:"sections"` - Overrides []ReportDefinitionOverrides `json:"overrides,omitempty" yaml:"overrides,omitempty"` -} - -type ReportDefinitionOverrides struct { - Policy string `json:"policy" yaml:"policy"` - Title string `json:"title" yaml:"title"` -} - -type ReportDefinitionSection struct { - Category string `json:"category" yaml:"category"` - Title string `json:"title" yaml:"title"` - Policies []string `json:"policies" yaml:"policies"` -} - -type ReportDefinitionProps struct { - Engine string `json:"engine,omitempty" yaml:"engine,omitempty"` - ReleaseLabel string `json:"releaseLabel,omitempty" yaml:"releaseLabel,omitempty"` - ResourceGroups []string `json:"resourceGroups,omitempty" yaml:"resourceGroups,omitempty"` - Integrations []string `json:"integrations,omitempty" yaml:"integrations,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_distributions.go b/vendor/github.com/lacework/go-sdk/api/reports_distributions.go deleted file mode 100644 index 294738444..000000000 --- a/vendor/github.com/lacework/go-sdk/api/reports_distributions.go +++ /dev/null @@ -1,205 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/pkg/errors" -) - -// ReportDistributionsService is a service that interacts with the ReportDistributions -// endpoints from the Lacework APIv2 Server -type ReportDistributionsService struct { - client *Client -} - -// The report distribution frequency type -type reportDistributionFrequency int - -const ( - ReportDistributionFrequencyDaily reportDistributionFrequency = iota - ReportDistributionFrequencyWeekly - ReportDistributionFrequencyBiweekly - ReportDistributionFrequencyMonthly -) - -func (frequency reportDistributionFrequency) String() string { - return reportDistributionTypes[frequency] -} - -var reportDistributionTypes = map[reportDistributionFrequency]string{ - ReportDistributionFrequencyDaily: "daily", - ReportDistributionFrequencyWeekly: "weekly", - ReportDistributionFrequencyBiweekly: "biweekly", - ReportDistributionFrequencyMonthly: "monthly", -} - -func ReportDistributionFrequencies() (frequencies []string) { - for _, v := range reportDistributionTypes { - frequencies = append(frequencies, v) - } - return -} - -// The report distribution violation type -type reportDistributionViolation int - -const ( - ReportDistributionViolationCompliant reportDistributionViolation = iota - ReportDistributionViolationNonCompliant - ReportDistributionViolationSuppressed - ReportDistributionViolationCouldNotAssess - ReportDistributionViolationManual -) - -func (subType reportDistributionViolation) String() string { - return reportDistributionSubTypes[subType] -} - -func ReportDistributionViolations() (values []string) { - for _, v := range reportDistributionSubTypes { - values = append(values, v) - } - return -} - -var reportDistributionSubTypes = map[reportDistributionViolation]string{ - ReportDistributionViolationCompliant: "Compliant", - ReportDistributionViolationNonCompliant: "NonCompliant", - ReportDistributionViolationSuppressed: "Suppressed", - ReportDistributionViolationCouldNotAssess: "CouldNotAssess", - ReportDistributionViolationManual: "Manual", -} - -// The report distribution scope type -type reportDistributionScope int - -const ( - ReportDistributionScopeResourceGroup reportDistributionScope = iota - ReportDistributionScopeCloudIntegration -) - -func (scope reportDistributionScope) String() string { - return reportDistributionScopeTypes[scope] -} - -func ReportDistributionScopes() (values []string) { - for _, v := range reportDistributionScopeTypes { - values = append(values, v) - } - return -} - -var reportDistributionScopeTypes = map[reportDistributionScope]string{ - ReportDistributionScopeResourceGroup: "Resource Group", - ReportDistributionScopeCloudIntegration: "Cloud Account Integration", -} - -// List returns a ReportDistributionResponse -func (svc *ReportDistributionsService) List() (response ReportDistributionsResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2ReportDistributions, nil, &response) - return -} - -// Get returns a ReportDistributionResponse -func (svc *ReportDistributionsService) Get(guid string) (response ReportDistributionResponse, err error) { - if guid == "" { - return ReportDistributionResponse{}, errors.New("specify a report distribution guid") - } - apiPath := fmt.Sprintf(apiV2ReportDistributionsFromGUID, guid) - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} - -// Delete a ReportDistribution -func (svc *ReportDistributionsService) Delete(guid string) error { - if guid == "" { - return errors.New("specify a report distribution guid") - } - - return svc.client.RequestDecoder("DELETE", fmt.Sprintf(apiV2ReportDistributionsFromGUID, guid), nil, nil) -} - -func (svc *ReportDistributionsService) Create(report ReportDistribution) ( - response ReportDistributionResponse, err error, -) { - err = svc.client.RequestEncoderDecoder("POST", apiV2ReportDistributions, report, &response) - return -} - -func (svc *ReportDistributionsService) Update(guid string, report ReportDistributionUpdate) ( - response ReportDistributionResponse, err error, -) { - if guid == "" { - return response, errors.New("specify a report distribution guid") - } - - err = svc.client.RequestEncoderDecoder("PATCH", - fmt.Sprintf(apiV2ReportDistributionsFromGUID, guid), report, &response) - return -} - -func (distribution *ReportDistribution) UpdateConfig() ReportDistributionUpdate { - return ReportDistributionUpdate{ - DistributionName: distribution.DistributionName, - Data: distribution.Data, - AlertChannels: distribution.AlertChannels, - Frequency: distribution.Frequency, - } -} - -type ReportDistributionsResponse struct { - Data []ReportDistribution `json:"data"` -} - -type ReportDistributionResponse struct { - Data ReportDistribution `json:"data"` -} - -type ReportDistribution struct { - ReportDistributionGuid string `json:"reportDistributionGuid,omitempty"` - ReportDefinitionGuid string `json:"reportDefinitionGuid"` - DistributionName string `json:"distributionName"` - Data ReportDistributionData `json:"data"` - AlertChannels []string `json:"alertChannels"` - Frequency string `json:"frequency"` -} - -type ReportDistributionData struct { - Severities []string `json:"severities"` - Violations []string `json:"violations"` - ResourceGroups []string `json:"resourceGroups"` - Integrations []ReportDistributionIntegration `json:"integrations"` -} - -type ReportDistributionIntegration struct { - TenantID string `json:"tenantId,omitempty"` - SubscriptionID string `json:"subscriptionId,omitempty"` - AccountID string `json:"accountId,omitempty"` - OrganizationID string `json:"organizationId,omitempty"` - ProjectID string `json:"projectId,omitempty"` -} - -type ReportDistributionUpdate struct { - DistributionName string `json:"distributionName,omitempty"` - Data ReportDistributionData `json:"data,omitempty"` - AlertChannels []string `json:"alertChannels,omitempty"` - Frequency string `json:"frequency,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/reports_gcp.go b/vendor/github.com/lacework/go-sdk/api/reports_gcp.go deleted file mode 100644 index 681e15db3..000000000 --- a/vendor/github.com/lacework/go-sdk/api/reports_gcp.go +++ /dev/null @@ -1,204 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "io" - "os" - "sort" - "time" - - "github.com/pkg/errors" -) - -// v2GcpReportsService is a service that interacts with the APIv2 -type gcpReportsService struct { - client *Client -} - -const ComplianceReportDefaultGcp = "GCP CIS Benchmark 1.3" - -type GcpReportConfig struct { - OrganizationID string - ProjectID string - Value string - Parameter reportFilter -} - -type GcpReportType int - -func (report GcpReportType) String() string { - return gcpReportTypes[report] -} - -func NewGcpReportType(report string) (GcpReportType, error) { - for k, v := range gcpReportTypes { - if v == report { - return k, nil - } - } - return NONE_GCP_REPORT, errors.Errorf("no report type found for %s", report) -} - -func GcpReportTypes() []string { - reportTypes := make([]string, 0, len(gcpReportTypes)) - - for _, report := range gcpReportTypes { - reportTypes = append(reportTypes, report) - } - sort.Strings(reportTypes) - return reportTypes -} - -var gcpReportTypes = map[GcpReportType]string{ - GCP_HIPAA: "GCP_HIPAA", - GCP_CIS: "GCP_CIS", - GCP_SOC: "GCP_SOC", - GCP_CIS12: "GCP_CIS12", - GCP_K8S: "GCP_K8S", - GCP_PCI_Rev2: "GCP_PCI_Rev2", - GCP_SOC_Rev2: "GCP_SOC_Rev2", - GCP_HIPAA_Rev2: "GCP_HIPAA_Rev2", - GCP_ISO_27001: "GCP_ISO_27001", - GCP_NIST_CSF: "GCP_NIST_CSF", - GCP_NIST_800_53_REV4: "GCP_NIST_800_53_REV4", - GCP_NIST_800_171_REV2: "GCP_NIST_800_171_REV2", - GCP_PCI: "GCP_PCI", - GCP_CIS13: "GCP_CIS13", - GCP_CIS_1_3_0_NIST_800_171_rev2: "GCP_CIS_1_3_0_NIST_800_171_rev2", - GCP_CIS_1_3_0_NIST_800_53_rev5: "GCP_CIS_1_3_0_NIST_800_53_rev5", - GCP_CIS_1_3_0_NIST_CSF: "GCP_CIS_1_3_0_NIST_CSF", - GCP_PCI_DSS_3_2_1: "GCP_PCI_DSS_3_2_1", - GCP_HIPAA_2013: "GCP_HIPAA_2013", - GCP_ISO_27001_2013: "GCP_ISO_27001_2013", - GCP_CMMC_1_02: "GCP_CMMC_1_02", - GCP_SOC_2: "GCP_SOC_2", -} - -const ( - NONE_GCP_REPORT GcpReportType = iota - GCP_HIPAA - GCP_CIS - GCP_SOC - GCP_CIS12 - GCP_K8S - GCP_PCI_Rev2 - GCP_SOC_Rev2 - GCP_HIPAA_Rev2 - GCP_ISO_27001 - GCP_NIST_CSF - GCP_NIST_800_53_REV4 - GCP_NIST_800_171_REV2 - GCP_PCI - GCP_CIS13 - GCP_CIS_1_3_0_NIST_800_171_rev2 - GCP_CIS_1_3_0_NIST_800_53_rev5 - GCP_CIS_1_3_0_NIST_CSF - GCP_PCI_DSS_3_2_1 - GCP_HIPAA_2013 - GCP_ISO_27001_2013 - GCP_CMMC_1_02 - GCP_SOC_2 -) - -// Get returns a GcpReportResponse -func (svc *gcpReportsService) Get(reportCfg GcpReportConfig) (response GcpReportResponse, err error) { - if reportCfg.ProjectID == "" || reportCfg.OrganizationID == "" { - return GcpReportResponse{}, errors.New("project id and org id are required") - } - - apiPath := fmt.Sprintf(apiV2ReportsSecondaryQuery, - reportCfg.OrganizationID, - reportCfg.ProjectID, - "json", - reportCfg.Parameter.String(), - reportCfg.Value, - ) - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} - -func (svc *gcpReportsService) DownloadPDF(filepath string, config GcpReportConfig) error { - if config.ProjectID == "" || config.OrganizationID == "" { - return errors.New("project id and org id are required") - } - - apiPath := fmt.Sprintf(apiV2ReportsSecondaryQuery, - config.OrganizationID, - config.ProjectID, - "pdf", - config.Parameter.String(), - config.Value, - ) - - request, err := svc.client.NewRequest("GET", apiPath, nil) - if err != nil { - return err - } - - response, err := svc.client.Do(request) - if err != nil { - return err - } - defer response.Body.Close() - - err = checkErrorInResponse(response) - if err != nil { - return err - } - - // Create the file - out, err := os.Create(filepath) - if err != nil { - return err - } - defer out.Close() - - // Write the body to file - _, err = io.Copy(out, response.Body) - return err -} - -func (gcp GcpReport) GetComplianceRecommendation(recommendationID string) (*RecommendationV2, bool) { - for _, r := range gcp.Recommendations { - if r.RecID == recommendationID { - return &r, true - } - } - return nil, false -} - -type GcpReportResponse struct { - Data []GcpReport `json:"data"` - Ok bool `json:"ok"` - Message string `json:"message"` -} - -type GcpReport struct { - ReportType string `json:"reportType"` - ReportTitle string `json:"reportTitle"` - Recommendations []RecommendationV2 `json:"recommendations"` - Summary []ReportSummary `json:"summary"` - ReportTime time.Time `json:"reportTime"` - OrganizationName string `json:"organizationName"` - OrganizationID string `json:"organizationId"` - ProjectName string `json:"projectName"` - ProjectID string `json:"projectId"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/resource_groups.go b/vendor/github.com/lacework/go-sdk/api/resource_groups.go deleted file mode 100644 index 35b15aa9a..000000000 --- a/vendor/github.com/lacework/go-sdk/api/resource_groups.go +++ /dev/null @@ -1,262 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - _ "embed" - "encoding/json" - "fmt" - "time" - - "github.com/pkg/errors" -) - -type resourceGroupType int - -const ( - // type that defines a non-existing Resource Group - NoneResourceGroup resourceGroupType = iota - AwsResourceGroup - AzureResourceGroup - ContainerResourceGroup - GcpResourceGroup - MachineResourceGroup - OciResourceGroup - KubernetesResourceGroup -) - -// query templates -var ( - NoneResourceGroupQueryTemplate string = "" - //go:embed _templates/resource_groups/aws.json - AwsResourceGroupQueryTemplate string - //go:embed _templates/resource_groups/azure.json - AzureResourceGroupQueryTemplate string - //go:embed _templates/resource_groups/container.json - ContainerResourceGroupQueryTemplate string - //go:embed _templates/resource_groups/gcp.json - GcpResourceGroupQueryTemplate string - //go:embed _templates/resource_groups/machine.json - MachineResourceGroupQueryTemplate string - //go:embed _templates/resource_groups/oci.json - OciResourceGroupQueryTemplate string - //go:embed _templates/resource_groups/kubernetes.json - KubernetesResourceGroupQueryTemplate string -) - -type resourceGroupContext struct { - resourceGroupType string - queryTemplate string -} - -// ResourceGroupTypes is the list of available Resource Group types -var ResourceGroupTypes = map[resourceGroupType]resourceGroupContext{ - NoneResourceGroup: {resourceGroupType: "None", queryTemplate: NoneResourceGroupQueryTemplate}, - AwsResourceGroup: {resourceGroupType: "AWS", queryTemplate: AwsResourceGroupQueryTemplate}, - AzureResourceGroup: {resourceGroupType: "AZURE", queryTemplate: AzureResourceGroupQueryTemplate}, - ContainerResourceGroup: {resourceGroupType: "CONTAINER", queryTemplate: ContainerResourceGroupQueryTemplate}, - GcpResourceGroup: {resourceGroupType: "GCP", queryTemplate: GcpResourceGroupQueryTemplate}, - MachineResourceGroup: {resourceGroupType: "MACHINE", queryTemplate: MachineResourceGroupQueryTemplate}, - OciResourceGroup: {resourceGroupType: "OCI", queryTemplate: OciResourceGroupQueryTemplate}, - KubernetesResourceGroup: {resourceGroupType: "KUBERNETES", queryTemplate: KubernetesResourceGroupQueryTemplate}, -} - -func NewResourceGroup(name string, iType resourceGroupType, - description string, query *RGQuery) ResourceGroupData { - return ResourceGroupData{ - Name: name, - Type: iType.String(), - Enabled: 1, - Query: query, - Description: description, - } -} - -func (svc *ResourceGroupsService) List() (response ResourceGroupsResponse, err error) { - var rawResponse ResourceGroupsResponse - err = svc.client.RequestDecoder("GET", apiV2ResourceGroups, nil, &rawResponse) - if err != nil { - return rawResponse, err - } - - return rawResponse, nil -} - -func (svc *ResourceGroupsService) Create(group ResourceGroupData) ( - response ResourceGroupResponse, - err error, -) { - err = svc.create(group, &response) - return -} - -func (svc *ResourceGroupsService) Update(data *ResourceGroupData) ( - response ResourceGroupResponse, - err error, -) { - if data == nil { - err = errors.New("resource group must not be empty") - return - } - guid := data.ID() - data.ResetResourceGUID() - - err = svc.update(guid, data, &response) - if err != nil { - return - } - - return -} - -func (group *ResourceGroupData) ResetResourceGUID() { - group.ResourceGroupGuid = "" - group.UpdatedBy = "" - group.UpdatedTime = nil - group.CreatedBy = "" - group.CreatedTime = nil - group.IsDefaultBoolean = nil -} - -func (svc *ResourceGroupsService) Delete(guid string) error { - if guid == "" { - return errors.New("specify a resourceGuid") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2ResourceGroupsFromGUID, guid), - nil, - nil, - ) -} - -func (svc *ResourceGroupsService) Get(guid string, response interface{}) error { - var rawResponse ResourceGroupResponse - err := svc.get(guid, &rawResponse) - if err != nil { - return err - } - - j, err := json.Marshal(rawResponse) - if err != nil { - return err - } - - err = json.Unmarshal(j, &response) - if err != nil { - return err - } - return nil -} - -func (svc *ResourceGroupsService) create(data interface{}, response interface{}) error { - return svc.client.RequestEncoderDecoder("POST", apiV2ResourceGroups, data, response) -} - -func (svc *ResourceGroupsService) get(guid string, response interface{}) error { - if guid == "" { - return errors.New("specify an resource group guid") - } - apiPath := fmt.Sprintf(apiV2ResourceGroupsFromGUID, guid) - return svc.client.RequestDecoder("GET", apiPath, nil, response) -} - -func (svc *ResourceGroupsService) update(guid string, data interface{}, response interface{}) error { - if guid == "" { - return errors.New("specify a resource group guid") - } - - apiPath := fmt.Sprintf(apiV2ResourceGroupsFromGUID, guid) - return svc.client.RequestEncoderDecoder("PATCH", apiPath, data, response) -} - -type ResourceGroupsService struct { - client *Client -} - -type RGExpression struct { - Operator string `json:"operator"` - Children []*RGChild `json:"children"` -} - -type RGChild struct { - Operator string `json:"operator,omitempty"` - FilterName string `json:"filterName,omitempty"` - Children []*RGChild `json:"children,omitempty"` -} - -type RGFilter struct { - Field string `json:"field"` - Operation string `json:"operation"` - Values []string `json:"values"` - Key string `json:"key,omitempty"` -} - -type RGQuery struct { - Filters map[string]*RGFilter `json:"filters"` - Expression *RGExpression `json:"expression"` -} - -// String returns the string representation of a Resource Group type -func (i resourceGroupType) String() string { - return ResourceGroupTypes[i].resourceGroupType -} - -// QueryTemplate returns the resource group type's query template -func (i resourceGroupType) QueryTemplate() string { - return ResourceGroupTypes[i].queryTemplate -} - -// FindResourceGroupType looks up inside the list of available resource group types -// the matching type from the provided string, if none, returns NoneResourceGroup -func FindResourceGroupType(typ string) (resourceGroupType, bool) { - for i, ctx := range ResourceGroupTypes { - if typ == ctx.resourceGroupType { - return i, true - } - } - return NoneResourceGroup, false -} - -func (group *ResourceGroupData) ID() string { - return group.ResourceGroupGuid -} - -type ResourceGroupResponse struct { - Data ResourceGroupData `json:"data"` -} - -type ResourceGroupsResponse struct { - Data []ResourceGroupData `json:"data"` -} - -type ResourceGroupData struct { - Name string `json:"name,omitempty"` - Query *RGQuery `json:"query,omitempty"` - Description string `json:"description,omitempty"` - ResourceGroupGuid string `json:"resourceGroupGuid,omitempty"` - CreatedTime *time.Time `json:"createdTime,omitempty"` - CreatedBy string `json:"createdBy,omitempty"` - UpdatedTime *time.Time `json:"updatedTime,omitempty"` - UpdatedBy string `json:"updatedBy,omitempty"` - IsDefaultBoolean *bool `json:"isDefaultBoolean,omitempty"` - Type string `json:"resourceType"` - Enabled int `json:"enabled"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/schemas.go b/vendor/github.com/lacework/go-sdk/api/schemas.go deleted file mode 100644 index 59b98114c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/schemas.go +++ /dev/null @@ -1,44 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// SchemasService is the service that retrieves schemas for v2 -type SchemasService struct { - client *Client - Services map[integrationSchema]V2Service -} - -type integrationSchema int - -const ( - None integrationSchema = iota - AlertChannels - AlertProfiles - AlertRules - ContainerRegistries - CloudAccounts - ResourceGroups - ReportRules - TeamMembers - VulnerabilityExceptions -) - -func (svc *SchemasService) GetService(schemaName integrationSchema) V2Service { - return svc.Services[schemaName] -} diff --git a/vendor/github.com/lacework/go-sdk/api/team_members.go b/vendor/github.com/lacework/go-sdk/api/team_members.go deleted file mode 100644 index 067f94fc1..000000000 --- a/vendor/github.com/lacework/go-sdk/api/team_members.go +++ /dev/null @@ -1,295 +0,0 @@ -// -// Author:: Vatasha White () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/pkg/errors" -) - -type TeamMembersService struct { - client *Client -} - -// NewTeamMember returns an instance of the Team Member struct -// -// Basic usage: Initialize a new TeamMember struct and then use the new instance to perform CRUD operations. -// -// client, err := api.NewClient("account") -// if err != nil { -// return err -// } -// -// teamMember := api.NewTeamMember( -// "FooBar", -// api.TeamMemberProps{ -// Company: "ACME Inc", -// FirstName: "Foo", -// LastName: "Bar" -// }, -// }, -// -// ) -// -// client.V2.TeamMembers.Create(teamMember) -func NewTeamMember(username string, props TeamMemberProps) TeamMember { - return TeamMember{ - Props: props, - UserEnabled: 1, - UserName: username, - } -} - -// NewTeamMemberOrg returns an instance of the team member org struct -// -// Basic usage: Initialize a new TeamMemberOrg struct and then use the new instance to perform CRUD operations. -// -// client, err := api.NewClient("account") -// if err != nil { -// return err -// } -// -// teamMember := api.NewTeamMemberOrg( -// "FooBar", -// api.TeamMemberProps{ -// Company: "ACME Inc", -// FirstName: "Foo", -// LastName: "Bar" -// }, -// }, -// -// ) -// -// client.V2.TeamMembers.CreateOrg(teamMember) -func NewTeamMemberOrg(username string, props TeamMemberProps) TeamMemberOrg { - return TeamMemberOrg{ - Props: props, - UserEnabled: 1, - UserName: username, - OrgAdmin: false, - OrgUser: true, - AdminRoleAccounts: []string{}, - UserRoleAccounts: []string{}, - } -} - -// List returns a list of team members -func (svc *TeamMembersService) List() (res TeamMembersResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2TeamMembers, nil, &res) - return -} - -// Create creates a single team member -func (svc *TeamMembersService) Create(tm TeamMember) (res TeamMemberResponse, err error) { - if svc.client.OrgAccess() { - return res, errors.New("client configured to manage org-level datasets, use CreateOrg()") - } - err = svc.client.RequestEncoderDecoder("POST", apiV2TeamMembers, tm, &res) - return -} - -// CreateOrg creates a single team member at the org level -// TODO Move all ORG stuff into a different file -func (svc *TeamMembersService) CreateOrg(tm TeamMemberOrg) (res TeamMemberOrgResponse, err error) { - if !svc.client.OrgAccess() { - return res, errors.New("client configured to manage account-level datasets, use Create()") - } - err = svc.client.RequestEncoderDecoder("POST", apiV2TeamMembers, tm, &res) - return -} - -// Delete deletes a single team member at the account level with the corresponding guid -func (svc *TeamMembersService) Delete(guid string) error { - if svc.client.OrgAccess() { - return errors.New("client configured to manage org-level datasets, use DeleteOrg()") - } - if guid == "" { - return errors.New("please specify a guid") - } - return svc.client.RequestDecoder("DELETE", fmt.Sprintf(apiV2TeamMembersFromGUID, guid), nil, nil) -} - -// DeleteOrg deletes a single team member at the org level with the corresponding guid -func (svc *TeamMembersService) DeleteOrg(guid string) error { - if !svc.client.OrgAccess() { - return errors.New("client configured to manage account-level datasets, use Delete()") - } - if guid == "" { - return errors.New("please specify a guid") - } - return svc.client.RequestDecoder("DELETE", fmt.Sprintf(apiV2TeamMembersFromGUID, guid), nil, nil) -} - -// Update updates a single team member at the account-level with the corresponding guid -func (svc *TeamMembersService) Update(tm TeamMember) (res TeamMemberResponse, err error) { - if svc.client.OrgAccess() { - return res, errors.New("client configured to manage org-level datasets, use UpdateOrg()") - } - if tm.UserGuid == "" { - err = errors.New("please specify a guid") - return - } - userGuid := tm.UserGuid - // Omit userGuid for patch requests - tm.UserGuid = "" - // Omit userName for patch requests as it cannot be modified - tm.UserName = "" - err = svc.client.RequestEncoderDecoder("PATCH", fmt.Sprintf(apiV2TeamMembersFromGUID, userGuid), tm, &res) - return -} - -// UpdateOrg updates a single team member at the org-level with the corresponding username -func (svc *TeamMembersService) UpdateOrg(tm TeamMemberOrg) (res TeamMemberOrgResponse, err error) { - if !svc.client.OrgAccess() { - err = errors.New("client configured to manage account-level datasets, use Update()") - return - } - if tm.UserName == "" { - err = errors.New("please specify a username") - return - } - tms, errSearch := svc.SearchUsername(tm.UserName) - if errSearch != nil || len(tms.Data) == 0 { - err = errors.Wrap(err, "unable to find user with specified username") - return - } - tm.UserGuid = tms.Data[0].UserGuid - return svc.UpdateOrgById(tm) -} - -// UpdateOrgById updates a single team member at the org-level with the corresponding guid -func (svc *TeamMembersService) UpdateOrgById(tm TeamMemberOrg) (res TeamMemberOrgResponse, err error) { - if !svc.client.OrgAccess() { - err = errors.New("client configured to manage account-level datasets, use Update()") - return - } - if tm.UserGuid == "" { - err = errors.New("please specify a user guid") - return - } - userGuid := tm.UserGuid - // Omit UserGuid from the patch body as it cannot be modified - tm.UserGuid = "" - // Omit userEnabled field from the patch body as it cannot be modified - tm.UserEnabled = 0 - // Omit userName field from the patch body as it cannot be modified - tm.UserName = "" - // Omit Company field from the patch body as it cannot be modified - tm.Props.Company = "" - - err = svc.client.RequestEncoderDecoder("PATCH", fmt.Sprintf(apiV2TeamMembersFromGUID, userGuid), tm, &res) - return -} - -// Get returns a response of the team member -func (svc *TeamMembersService) Get(guid string, res interface{}) error { - if guid == "" { - return errors.New("please specify a guid") - } - return svc.client.RequestDecoder("GET", fmt.Sprintf(apiV2TeamMembersFromGUID, guid), nil, &res) - -} - -func (svc *TeamMembersService) SearchUsername(username string) (res TeamMembersResponse, err error) { - if username == "" { - err = errors.New("specify a username to search for a team member") - return - } - err = svc.client.RequestEncoderDecoder("POST", - apiV2TeamMembersSearch, - SearchFilter{ - Filters: []Filter{ - { - Field: "userName", - Expression: "eq", - Value: username, - }, - }, - }, - &res, - ) - return -} - -type TeamMemberProps struct { - AccountAdmin bool `json:"accountAdmin,omitempty"` - //Company is empty for patch requests on updateOrg as it cannot be modified - Company string `json:"company,omitempty"` - CreatedTime string `json:"createdTime,omitempty"` - FirstName string `json:"firstName"` - JitCreated bool `json:"jitCreated,omitempty"` - LastLoginTime interface{} `json:"lastLoginTime,omitempty"` - LastName string `json:"lastName"` - LastSessionCreatedTime interface{} `json:"lastSessionCreatedTime,omitempty"` - OrgAdmin bool `json:"orgAdmin,omitempty"` - OrgUser bool `json:"orgUser,omitempty"` - UpdatedBy string `json:"updatedBy,omitempty"` - UpdatedTime interface{} `json:"updatedTime,omitempty"` -} - -// TeamMember is for a standalone team member without org access -type TeamMember struct { - CustGuid string `json:"custGuid,omitempty"` - Props TeamMemberProps `json:"props"` - UserEnabled int `json:"userEnabled"` - UserGuid string `json:"userGuid,omitempty"` - UserName string `json:"userName,omitempty"` -} - -type TeamMemberResponse struct { - Data TeamMember `json:"data"` -} - -type TeamMembersResponse struct { - Data []TeamMember `json:"data"` -} - -// TeamMemberOrg is for an organizational team member -type TeamMemberOrg struct { - AdminRoleAccounts []string `json:"adminRoleAccounts"` - OrgAdmin bool `json:"orgAdmin"` - OrgUser bool `json:"orgUser"` - Props TeamMemberProps `json:"props"` - UserEnabled int `json:"userEnabled,omitempty"` - UserGuid string `json:"userGuid,omitempty"` - UserName string `json:"userName,omitempty"` - UserRoleAccounts []string `json:"userRoleAccounts"` -} - -type TeamMemberAccount struct { - AccountName string `json:"accountName"` - Admin bool `json:"admin"` - CustGuid string `json:"custGuid"` - UserEnabled int `json:"userEnabled"` - UserGuid string `json:"userGuid"` -} - -type TeamMemberOrgData struct { - Accounts []TeamMemberAccount `json:"accounts"` - OrgAccount bool `json:"orgAccount"` - OrgAdmin bool `json:"orgAdmin"` - OrgUser bool `json:"orgUser"` - Url string `json:"url"` - UserName string `json:"userName"` -} - -type TeamMemberOrgResponse struct { - Data TeamMemberOrgData `json:"data"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/user_profile.go b/vendor/github.com/lacework/go-sdk/api/user_profile.go deleted file mode 100644 index cba37c3bf..000000000 --- a/vendor/github.com/lacework/go-sdk/api/user_profile.go +++ /dev/null @@ -1,84 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "strings" - - "github.com/lacework/go-sdk/lwdomain" -) - -// UserProfileService is the service that interacts with the UserProfile -// schema from the Lacework APIv2 Server -type UserProfileService struct { - client *Client -} - -func (svc *UserProfileService) Get() (response UserProfileResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2UserProfile, nil, &response) - return -} - -type UserProfileResponse struct { - Data []UserProfile `json:"data"` -} - -type UserProfile struct { - Username string `json:"username"` - OrgAccount bool `json:"orgAccount"` - URL string `json:"url"` - OrgAdmin bool `json:"orgAdmin"` - OrgUser bool `json:"orgUser"` - Accounts []Account `json:"accounts"` -} - -func (p *UserProfile) OrgAccountName() string { - d, err := lwdomain.New(p.URL) - if err != nil { - return p.URL - } - return d.Account -} - -func (p *UserProfile) SubAccountNames() []string { - names := make([]string, 0) - orgAccountName := p.OrgAccountName() - for _, acc := range p.Accounts { - accName := strings.ToLower(acc.AccountName) - if accName == orgAccountName { - continue - } - if acc.Enabled() { - names = append(names, accName) - } - } - return names -} - -type Account struct { - Admin bool `json:"admin"` - AccountName string `json:"accountName"` - CustGUID string `json:"custGuid"` - UserGUID string `json:"userGuid"` - UserEnabled int `json:"userEnabled"` -} - -func (a *Account) Enabled() bool { - return a.UserEnabled == 1 -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2.go b/vendor/github.com/lacework/go-sdk/api/v2.go deleted file mode 100644 index cb44328f1..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2.go +++ /dev/null @@ -1,265 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "net/url" - - "github.com/pkg/errors" - "go.uber.org/zap" -) - -// V2Endpoints groups all APIv2 endpoints available, they are grouped by -// schema which matches with our service architecture -type V2Endpoints struct { - client *Client - - // Every schema must have its own service - UserProfile *UserProfileService - AlertChannels *AlertChannelsService - Alert *v2alertProfilesService - AlertRules *AlertRulesService - ReportRules *ReportRulesService - CloudAccounts *CloudAccountsService - Components *ComponentsService - ComponentData *ComponentDataService - ContainerRegistries *ContainerRegistriesService - Configs *v2ConfigService - FeatureFlags *FeatureFlagsService - ResourceGroups *ResourceGroupsService - AgentAccessTokens *AgentAccessTokensService - AgentInfo *AgentInfoService - Inventory *InventoryService - ComplianceEvaluations *ComplianceEvaluationService - Query *QueryService - OrganizationInfo *OrganizationInfoService - Policy *PolicyService - Reports *ReportsService - ReportDefinitions *ReportDefinitionsService - Metrics *MetricsService - ReportDistributions *ReportDistributionsService - Entities *EntitiesService - Schemas *SchemasService - Datasources *DatasourcesService - DataExportRules *DataExportRulesService - TeamMembers *TeamMembersService - VulnerabilityExceptions *VulnerabilityExceptionsService - Vulnerabilities *v2VulnerabilitiesService - Alerts *AlertsService - Suppressions *SuppressionsServiceV2 - Recommendations *RecommendationsServiceV2 -} - -func NewV2Endpoints(c *Client) *V2Endpoints { - v2 := &V2Endpoints{c, - &UserProfileService{c}, - &AlertChannelsService{c}, - NewV2AlertProfilesService(c), - &AlertRulesService{c}, - &ReportRulesService{c}, - &CloudAccountsService{c}, - &ComponentsService{c}, - &ComponentDataService{c}, - &ContainerRegistriesService{c}, - NewV2ConfigService(c), - &FeatureFlagsService{c}, - &ResourceGroupsService{c}, - &AgentAccessTokensService{c}, - &AgentInfoService{c}, - &InventoryService{c}, - &ComplianceEvaluationService{c}, - &QueryService{c}, - &OrganizationInfoService{c}, - NewV2PolicyService(c), - NewReportsService(c), - &ReportDefinitionsService{c}, - &MetricsService{c}, - &ReportDistributionsService{c}, - &EntitiesService{c}, - &SchemasService{c, map[integrationSchema]V2Service{}}, - &DatasourcesService{c}, - &DataExportRulesService{c}, - &TeamMembersService{c}, - &VulnerabilityExceptionsService{c}, - NewV2VulnerabilitiesService(c), - &AlertsService{c}, - &SuppressionsServiceV2{c, - &AwsSuppressionsV2{c}, - &AzureSuppressionsV2{c}, - &GcpSuppressionsV2{c}, - }, - &RecommendationsServiceV2{c, - &AwsRecommendationsV2{c}, - &AzureRecommendationsV2{c}, - &GcpRecommendationsV2{c}, - }, - } - - v2.Schemas.Services = map[integrationSchema]V2Service{ - AlertChannels: &AlertChannelsService{c}, - AlertRules: &AlertRulesService{c}, - CloudAccounts: &CloudAccountsService{c}, - ContainerRegistries: &ContainerRegistriesService{c}, - ResourceGroups: &ResourceGroupsService{c}, - TeamMembers: &TeamMembersService{c}, - ReportRules: &ReportRulesService{c}, - VulnerabilityExceptions: &VulnerabilityExceptionsService{c}, - } - return v2 -} - -type V2Service interface { - Get(string, interface{}) error - Delete(string) error -} - -type V2CommonIntegration struct { - Data v2CommonIntegrationData `json:"data"` -} - -// V2RawType is the interface that should be implemented when -// a struct is a response that contains v2CommonIntegrationData. -// This include AlertChannelRaw, CloudAccountRaw, ContainerRegistryRaw -type V2RawType interface { - GetData() any - GetCommon() v2CommonIntegrationData -} - -type V2Pagination struct { - Rows int `json:"rows"` - TotalRows int `json:"totalRows"` - Urls struct { - NextPage string `json:"nextPage"` - } `json:"urls"` -} - -// v2PageMetadata is used to compute the total pages and the page number -// when reading pages using the client.NextPage() function -type v2PageMetadata struct { - totalPages int - pageNumber int -} - -func (m v2PageMetadata) PageNumber() int { - return m.pageNumber -} -func (m v2PageMetadata) TotalPages() int { - return m.totalPages -} -func (m *v2PageMetadata) SetTotalPages(total int) { - m.totalPages = total -} -func (m *v2PageMetadata) PageRead() { - m.pageNumber++ -} - -// Pageable is the interface that structs should implement to become -// pageable and be able to use the client.NextPage() function -type Pageable interface { - PageInfo() *V2Pagination - ResetPaging() - - // all these functions are automatically implemented when attaching - // the v2PageMetadata type into any Pageable struct, so attaching that - // struct is a requirement - PageRead() - SetTotalPages(int) - TotalPages() int - PageNumber() int -} - -// NextPage -// -// Use this function to access the next page from an API v2 endpoint, the provided -// response must implement the Pageable interface and when it is passed, it will -// be overwritten, if the response doesn't have paging information this function -// returns false and not error -// -// Usage: To iterate over all pages -// -// ```go -// var ( -// -// response = api.MachineDetailEntityResponse{} -// err = client.V2.Entities.Search(&response, api.SearchFilter{}) -// -// ) -// -// for { -// // Use information from response.Data -// fmt.Printf("Data from page: %d\n", len(response.Data)) -// -// pageOk, err := client.NextPage(&response) -// if err != nil { -// fmt.Printf("Unable to access next page, error '%s'", err.Error()) -// break -// } -// -// if pageOk { -// continue -// } -// break -// } -// -// ``` -func (c *Client) NextPage(p Pageable) (bool, error) { - if p == nil { - return false, nil - } - pagination := p.PageInfo() - if pagination == nil { - c.log.Info("pagination information not found") - return false, nil - } - - if pagination.Urls.NextPage == "" { - return false, nil - } - - if p.PageNumber() == 0 { - // first page, initialize pagination metadata - p.SetTotalPages(pagination.TotalRows / pagination.Rows) - p.PageRead() - } - - c.log.Info("pagination", - zap.Int("page_number", p.PageNumber()), - zap.Int("total_pages", p.TotalPages()), - zap.Int("rows", pagination.Rows), - zap.Int("total_rows", pagination.TotalRows), - zap.String("next_page", pagination.Urls.NextPage), - ) - - pageURL, err := url.Parse(pagination.Urls.NextPage) - if err != nil { - return false, errors.Wrap(err, "unable to part next page url") - } - // some NextPage values have query parameters which should be concatenated - path := pageURL.Path - if len(pageURL.Query()) > 0 { - path += fmt.Sprintf("?%s", pageURL.Query().Encode()) - } - - p.ResetPaging() - c.log.Info("pagination reset") - err = c.RequestDecoder("GET", path, nil, p) - p.PageRead() - return true, err -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_configs.go b/vendor/github.com/lacework/go-sdk/api/v2_configs.go deleted file mode 100644 index 3338ce3df..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_configs.go +++ /dev/null @@ -1,34 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// v2ConfigService is a service that interacts with the Configs -// endpoints from the Lacework APIv2 Server -type v2ConfigService struct { - client *Client - Azure *v2AzureConfigService - Gcp *v2GcpConfigService -} - -func NewV2ConfigService(c *Client) *v2ConfigService { - return &v2ConfigService{c, - &v2AzureConfigService{c}, - &v2GcpConfigService{c}, - } -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_configs_azure.go b/vendor/github.com/lacework/go-sdk/api/v2_configs_azure.go deleted file mode 100644 index e3b38c237..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_configs_azure.go +++ /dev/null @@ -1,56 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "fmt" - -// v2AzureConfigService is a service that interacts with the APIv2 -// vulnerabilities endpoints for hosts -type v2AzureConfigService struct { - client *Client -} - -// List returns a list of Azure tenants and subscriptions -func (svc *v2AzureConfigService) List() (response AzureConfigsResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2ConfigsAzure, nil, &response) - if err != nil { - return - } - - return -} - -// ListSubscriptions returns a list of Azure subscriptions for a given tenant -func (svc *v2AzureConfigService) ListSubscriptions(tenantID string) (response AzureConfigsResponse, err error) { - err = svc.client.RequestDecoder("GET", fmt.Sprintf(apiV2ConfigsAzureSubscriptions, tenantID), nil, &response) - if err != nil { - return - } - - return -} - -type AzureConfigsResponse struct { - Data []AzureConfigData `json:"data"` -} - -type AzureConfigData struct { - Tenant string `json:"tenant"` - Subscriptions []string `json:"subscriptions"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_configs_gcp.go b/vendor/github.com/lacework/go-sdk/api/v2_configs_gcp.go deleted file mode 100644 index 2f12a272a..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_configs_gcp.go +++ /dev/null @@ -1,56 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "fmt" - -// v2GcpConfigService is a service that interacts with the APIv2 -// vulnerabilities endpoints for hosts -type v2GcpConfigService struct { - client *Client -} - -// List returns a list of Gcp organizations and projects -func (svc *v2GcpConfigService) List() (response GcpConfigsResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2ConfigsGcp, nil, &response) - if err != nil { - return - } - - return -} - -// ListProjects returns a list of Gcp projects for a given organization -func (svc *v2GcpConfigService) ListProjects(orgID string) (response GcpConfigsResponse, err error) { - err = svc.client.RequestDecoder("GET", fmt.Sprintf(apiV2ConfigsGcpProjects, orgID), nil, &response) - if err != nil { - return - } - - return -} - -type GcpConfigsResponse struct { - Data []GcpConfigData `json:"data"` -} - -type GcpConfigData struct { - Organization string `json:"organization"` - Projects []string `json:"projects"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_recommendations.go b/vendor/github.com/lacework/go-sdk/api/v2_recommendations.go deleted file mode 100644 index 5fe6620bc..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_recommendations.go +++ /dev/null @@ -1,143 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" -) - -// RecommendationsServiceV2 is a service that interacts with the V2 Recommendations -// endpoints from the Lacework Server -type RecommendationsServiceV2 struct { - client *Client - Aws recommendationServiceV2 - Azure recommendationServiceV2 - Gcp recommendationServiceV2 -} - -type recommendationServiceV2 interface { - List() ([]RecV2, error) - Patch(recommendations RecommendationStateV2) (RecommendationResponseV2, error) - GetReport(reportType string) ([]RecV2, error) -} - -type RecommendationTypeV2 string - -const ( - AwsRecommendation RecommendationTypeV2 = "aws" - AzureRecommendation RecommendationTypeV2 = "azure" - GcpRecommendation RecommendationTypeV2 = "gcp" -) - -func (svc *RecommendationsServiceV2) list(cloudType RecommendationTypeV2) ([]RecV2, error) { - var response RecommendationResponseV2 - err := svc.client.RequestDecoder("GET", fmt.Sprintf(apiRecommendations, cloudType), nil, &response) - return response.RecommendationList(), err -} - -func (svc *RecommendationsServiceV2) patch(cloudType RecommendationTypeV2, recommendations RecommendationStateV2) ( - response RecommendationResponseV2, - err error, -) { - err = svc.client.RequestEncoderDecoder("PATCH", fmt.Sprintf(apiRecommendations, cloudType), recommendations, &response) - return -} - -type RecommendationStateV2 map[string]string - -type RecommendationDataV2 map[string]RecommendationEnabledV2 - -type RecV2 struct { - ID string - State bool -} - -type RecommendationEnabledV2 struct { - Enabled bool `json:"enabled"` -} - -type RecommendationResponseV2 struct { - Data []RecommendationDataV2 `json:"data"` - Ok bool `json:"ok"` - Message string `json:"message"` -} - -func (res *RecommendationResponseV2) RecommendationList() (recommendations []RecV2) { - if len(res.Data) > 0 { - for k, v := range res.Data[0] { - recommendations = append(recommendations, RecV2{k, v.Enabled}) - } - } - return -} - -type ReportSchema struct { - Name string `json:"name"` - RecommendationIDs map[string]string `json:"recommendationIDs"` -} - -func NewRecommendationV2State(recommendations []RecV2, state bool) RecommendationStateV2 { - request := make(map[string]string) - for _, rec := range recommendations { - if state { - request[rec.ID] = "enable" - - } else { - request[rec.ID] = "disable" - } - } - return request -} - -func NewRecommendationV2(recommendations []RecV2) RecommendationStateV2 { - request := make(map[string]string) - for _, rec := range recommendations { - if rec.State { - request[rec.ID] = "enable" - - } else { - request[rec.ID] = "disable" - } - } - return request -} - -// ReportStatus This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. -func (res *RecommendationResponseV2) ReportStatus() map[string]bool { - var recommendations = make(map[string]bool) - - for _, rec := range res.RecommendationList() { - recommendations[rec.ID] = rec.State - } - - return recommendations -} - -// filterRecommendations This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. -func filterRecommendations(allRecommendations []RecV2, schema ReportSchema) []RecV2 { - var recommendations []RecV2 - - for _, rec := range allRecommendations { - _, ok := schema.RecommendationIDs[rec.ID] - if ok { - recommendations = append(recommendations, rec) - } - } - return recommendations -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_recommendations_aws.go b/vendor/github.com/lacework/go-sdk/api/v2_recommendations_aws.go deleted file mode 100644 index 6542e84a6..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_recommendations_aws.go +++ /dev/null @@ -1,70 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/json" - "errors" - - "github.com/lacework/go-sdk/internal/databox" -) - -// AwsRecommendationsV2 is a service that interacts with the V2 Recommendations -// endpoints from the Lacework Server -type AwsRecommendationsV2 struct { - client *Client -} - -func (svc *AwsRecommendationsV2) List() ([]RecV2, error) { - return svc.client.V2.Recommendations.list(AwsRecommendation) -} - -func (svc *AwsRecommendationsV2) Patch(recommendations RecommendationStateV2) (RecommendationResponseV2, error) { - return svc.client.V2.Recommendations.patch(AwsRecommendation, recommendations) -} - -// GetReport This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. -// Scoped to Lacework Account/Subaccount -func (svc *AwsRecommendationsV2) GetReport(reportType string) ([]RecV2, error) { - report := struct { - Ids map[string]string `json:"recommendation_ids"` - }{} - - schemaBytes, ok := databox.Get("/reports/aws/cis.json") - if !ok { - return []RecV2{}, errors.New( - "compliance report schema not found", - ) - } - - err := json.Unmarshal(schemaBytes, &report) - if err != nil { - return []RecV2{}, err - } - - schema := ReportSchema{reportType, report.Ids} - - // fetch all aws recommendations - allRecommendations, err := svc.client.V2.Recommendations.Aws.List() - if err != nil { - return []RecV2{}, err - } - filteredRecommendations := filterRecommendations(allRecommendations, schema) - return filteredRecommendations, nil -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_recommendations_azure.go b/vendor/github.com/lacework/go-sdk/api/v2_recommendations_azure.go deleted file mode 100644 index c08a79eae..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_recommendations_azure.go +++ /dev/null @@ -1,87 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/json" - "errors" - "fmt" - - "github.com/lacework/go-sdk/internal/databox" -) - -// AzureRecommendationsV2 is a service that interacts with the V2 Recommendations -// endpoints from the Lacework Server -type AzureRecommendationsV2 struct { - client *Client -} - -func (svc *AzureRecommendationsV2) List() ([]RecV2, error) { - return svc.client.V2.Recommendations.list(AzureRecommendation) -} - -func (svc *AzureRecommendationsV2) Patch(recommendations RecommendationStateV2) (RecommendationResponseV2, error) { - return svc.client.V2.Recommendations.patch(AzureRecommendation, recommendations) -} - -// GetReport This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. -// Scoped to Lacework Account/Subaccount -func (svc *AzureRecommendationsV2) GetReport(reportType string) ([]RecV2, error) { - var ( - schemaBytes []byte - ok bool - ) - report := struct { - Ids map[string]string `json:"recommendation_ids"` - }{} - - switch reportType { - case "CIS_1_0": - schemaBytes, ok = databox.Get("/reports/azure/cis.json") - if !ok { - return []RecV2{}, errors.New( - "compliance report schema not found", - ) - } - case "CIS_1_3_1": - schemaBytes, ok = databox.Get("/reports/azure/cis_131.json") - if !ok { - return []RecV2{}, errors.New( - "compliance report schema not found", - ) - } - default: - return nil, fmt.Errorf("unable to find recommendations for report type %s", reportType) - } - - err := json.Unmarshal(schemaBytes, &report) - if err != nil { - return []RecV2{}, err - } - - schema := ReportSchema{reportType, report.Ids} - - // fetch all azure recommendations - allRecommendations, err := svc.client.V2.Recommendations.Azure.List() - if err != nil { - return []RecV2{}, err - } - filteredRecommendations := filterRecommendations(allRecommendations, schema) - return filteredRecommendations, nil -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_recommendations_gcp.go b/vendor/github.com/lacework/go-sdk/api/v2_recommendations_gcp.go deleted file mode 100644 index 495ce9b7f..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_recommendations_gcp.go +++ /dev/null @@ -1,87 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/json" - "errors" - "fmt" - - "github.com/lacework/go-sdk/internal/databox" -) - -// GcpRecommendationsV2 is a service that interacts with the V2 Recommendations -// endpoints from the Lacework Server -type GcpRecommendationsV2 struct { - client *Client -} - -func (svc *GcpRecommendationsV2) List() ([]RecV2, error) { - return svc.client.V2.Recommendations.list(GcpRecommendation) -} - -func (svc *GcpRecommendationsV2) Patch(recommendations RecommendationStateV2) (RecommendationResponseV2, error) { - return svc.client.V2.Recommendations.patch(GcpRecommendation, recommendations) -} - -// GetReport This is an experimental feature. Returned RecommendationID's are not guaranteed to be correct. -// Scoped to Lacework Account/Subaccount -func (svc *GcpRecommendationsV2) GetReport(reportType string) ([]RecV2, error) { - var ( - schemaBytes []byte - ok bool - ) - report := struct { - Ids map[string]string `json:"recommendation_ids"` - }{} - - switch reportType { - case "CIS_1_0": - schemaBytes, ok = databox.Get("/reports/gcp/cis.json") - if !ok { - return []RecV2{}, errors.New( - "compliance report schema not found", - ) - } - case "CIS_1_2": - schemaBytes, ok = databox.Get("/reports/gcp/cis_12.json") - if !ok { - return []RecV2{}, errors.New( - "compliance report schema not found", - ) - } - default: - return nil, fmt.Errorf("unable to find recommendations for report type %s", reportType) - } - - err := json.Unmarshal(schemaBytes, &report) - if err != nil { - return []RecV2{}, err - } - - schema := ReportSchema{reportType, report.Ids} - - // fetch all azure recommendations - allRecommendations, err := svc.client.V2.Recommendations.Gcp.List() - if err != nil { - return []RecV2{}, err - } - filteredRecommendations := filterRecommendations(allRecommendations, schema) - return filteredRecommendations, nil -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_search_filters.go b/vendor/github.com/lacework/go-sdk/api/v2_search_filters.go deleted file mode 100644 index ba32115db..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_search_filters.go +++ /dev/null @@ -1,119 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "errors" - "math" - "time" -) - -// SearchFilter is the representation of an advanced search payload -// for retrieving information out of the Lacework APIv2 Server -// -// An advanced example of a SearchFilter to search for an Agent -// Access Token that matches the provider token alias and return -// only the token found: -// -// SearchFilter{ -// Filters: []Filter{ -// Filter{ -// Field: "tokenAlias", -// Expression: "eq", -// Value: "k8s-deployment, -// }, -// }, -// Returns: []string{"accessToken"}, -// } -type SearchFilter struct { - *TimeFilter `json:"timeFilter,omitempty"` - Filters []Filter `json:"filters,omitempty"` - Returns []string `json:"returns,omitempty"` -} - -type Filter struct { - Expression string `json:"expression,omitempty"` - Field string `json:"field,omitempty"` - Value string `json:"value,omitempty"` - Values []string `json:"values,omitempty"` -} - -type TimeFilter struct { - StartTime *time.Time `json:"startTime,omitempty"` - EndTime *time.Time `json:"endTime,omitempty"` -} - -type SearchResponse interface { - GetDataLength() int -} - -type SearchableFilter interface { - GetTimeFilter() *TimeFilter - SetStartTime(*time.Time) - SetEndTime(*time.Time) -} - -// V2ApiMaxSearchHistoryDays defines the maximum number of days in the past api v2 allows to be searched -const V2ApiMaxSearchHistoryDays = 92 - -// V2ApiMaxSearchWindowDays defines the maximum number of days in a single request api v2 allows to be searched -const V2ApiMaxSearchWindowDays = 7 - -type search func(response interface{}, filters SearchableFilter) error - -// WindowedSearchFirst performs a new search of a specific time frame size, -// until response data is found or the max searchable days is reached -func WindowedSearchFirst(fn search, size int, max int, response SearchResponse, filter SearchableFilter) error { - if size > max { - return errors.New("window size cannot be greater than max history") - } - - // if start and end time are the same, adjust the windows - timeDifference := int(math.RoundToEven( - filter.GetTimeFilter().EndTime.Sub(*filter.GetTimeFilter().StartTime).Hours() / 24), - ) - - if timeDifference == 0 { - newStart := filter.GetTimeFilter().StartTime.AddDate(0, 0, -size) - filter.SetStartTime(&newStart) - } - - for i := timeDifference; i < max; i += size { - err := fn(&response, filter) - if err != nil { - return err - } - if response.GetDataLength() != 0 { - return nil - } - - // adjust window - newStart := filter.GetTimeFilter().StartTime.AddDate(0, 0, -size) - newEnd := filter.GetTimeFilter().EndTime.AddDate(0, 0, -size) - - // ensure we do not go over the max allowed searchable days - searchableDays := time.Since(newStart).Hours() / 24 - if int(searchableDays) > max { - newStart = time.Now().AddDate(0, 0, -max) - } - filter.SetStartTime(&newStart) - filter.SetEndTime(&newEnd) - } - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_suppressions.go b/vendor/github.com/lacework/go-sdk/api/v2_suppressions.go deleted file mode 100644 index 9d0033b74..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_suppressions.go +++ /dev/null @@ -1,101 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/mitchellh/mapstructure" -) - -// SuppressionsServiceV2 is a service that interacts with the V2 Suppressions -// endpoints from the Lacework Server -type SuppressionsServiceV2 struct { - client *Client - Aws suppressionServiceV2 - Azure suppressionServiceV2 - Gcp suppressionServiceV2 -} - -type suppressionServiceV2 interface { - List() (map[string]SuppressionV2, error) -} - -type SuppressionTypeV2 string - -const ( - AwsSuppression SuppressionTypeV2 = "aws" - AzureSuppression SuppressionTypeV2 = "azure" - GcpSuppression SuppressionTypeV2 = "gcp" -) - -func (svc *SuppressionsServiceV2) list(cloudType SuppressionTypeV2) (map[string]SuppressionV2, - error) { - var response SuppressionResponseV2 - err := svc.client.RequestDecoder("GET", fmt.Sprintf(apiSuppressions, cloudType), nil, &response) - return response.SuppressionList(), err -} - -type SuppressionResponseV2 struct { - Data []SuppressionDataV2 `json:"data"` - Ok bool `json:"ok"` - Message string `json:"message"` -} - -type SuppressionDataV2 struct { - RecommendationSuppressions map[string]map[string]interface{} `json:"recommendationExceptions"` -} - -type SuppressionV2 struct { - Enabled bool `json:"enabled"` - SuppressionConditions []SuppressionConditions `json:"suppressionConditions"` -} - -type SuppressionConditions struct { - AccountIds []string `json:"accountIds,omitempty"` - OrganizationIds []string `json:"organizationIds,omitempty"` - ProjectIds []string `json:"projectIds,omitempty"` - RegionNames []string `json:"regionNames,omitempty"` - ResourceLabels []map[string]string `json:"resourceLabels,omitempty"` - ResourceGroupNames []string `json:"resourceGroupNames,omitempty"` - ResourceNames []string `json:"resourceNames,omitempty"` - ResourceTags []map[string]string `json:"resourceTags,omitempty"` - SubscriptionIds []string `json:"subscriptionIds,omitempty"` - TenantIds []string `json:"tenantIds,omitempty"` - Comment string `json:"comments,omitempty"` -} - -func (res *SuppressionResponseV2) SuppressionList() (suppressions map[string]SuppressionV2) { - if len(res.Data) > 0 { - suppressions = make(map[string]SuppressionV2) - for _, v := range res.Data { - for key, val := range v.RecommendationSuppressions { - var sup SuppressionV2 - - err := mapstructure.Decode(val, &sup) - if err != nil { - return nil - } - - suppressions[key] = sup - } - } - } - return -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_suppressions_aws.go b/vendor/github.com/lacework/go-sdk/api/v2_suppressions_aws.go deleted file mode 100644 index 461d9b542..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_suppressions_aws.go +++ /dev/null @@ -1,29 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// AwsSuppressionsV2 is a service that interacts with the V2 Suppressions -// endpoints from the Lacework Server -type AwsSuppressionsV2 struct { - client *Client -} - -func (svc *AwsSuppressionsV2) List() (map[string]SuppressionV2, error) { - return svc.client.V2.Suppressions.list(AwsSuppression) -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_suppressions_azure.go b/vendor/github.com/lacework/go-sdk/api/v2_suppressions_azure.go deleted file mode 100644 index 2463db2a6..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_suppressions_azure.go +++ /dev/null @@ -1,29 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// AzureSuppressionsV2 is a service that interacts with the V2 Suppressions -// endpoints from the Lacework Server -type AzureSuppressionsV2 struct { - client *Client -} - -func (svc *AzureSuppressionsV2) List() (map[string]SuppressionV2, error) { - return svc.client.V2.Suppressions.list(AzureSuppression) -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_suppressions_gcp.go b/vendor/github.com/lacework/go-sdk/api/v2_suppressions_gcp.go deleted file mode 100644 index edd9fc9a8..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_suppressions_gcp.go +++ /dev/null @@ -1,29 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -// GcpSuppressionsV2 is a service that interacts with the V2 Suppressions -// endpoints from the Lacework Server -type GcpSuppressionsV2 struct { - client *Client -} - -func (svc *GcpSuppressionsV2) List() (map[string]SuppressionV2, error) { - return svc.client.V2.Suppressions.list(GcpSuppression) -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities.go b/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities.go deleted file mode 100644 index 0c9085b8c..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities.go +++ /dev/null @@ -1,772 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - "time" - - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/lwseverity" -) - -// v2VulnerabilitiesService is a service that interacts with the Vulnerabilities -// endpoints from the Lacework APIv2 Server -type v2VulnerabilitiesService struct { - client *Client - Hosts *v2HostVulnerabilityService - Containers *v2ContainerVulnerabilityService - SoftwarePackages *v2SoftwarePackagesVulnerabilityService -} - -func NewV2VulnerabilitiesService(c *Client) *v2VulnerabilitiesService { - return &v2VulnerabilitiesService{c, - &v2HostVulnerabilityService{c}, - &v2ContainerVulnerabilityService{c}, - &v2SoftwarePackagesVulnerabilityService{c}, - } -} - -// v2ContainerVulnerabilityService is a service that interacts with the APIv2 -// vulnerabilities endpoints for containers -type v2ContainerVulnerabilityService struct { - client *Client -} - -// SearchLastWeek returns a list of VulnerabilityContainer from the last 7 days -func (svc *v2ContainerVulnerabilityService) SearchLastWeek() (VulnerabilitiesContainersResponse, error) { - var ( - now = time.Now().UTC() - before = now.AddDate(0, 0, -7) // 7 days from ago - ) - - return svc.Search(SearchFilter{ - TimeFilter: &TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - }) -} - -// Search returns a list of VulnerabilityContainer from the last 7 days -func (svc *v2ContainerVulnerabilityService) Search(filters SearchFilter) ( - response VulnerabilitiesContainersResponse, err error, -) { - err = svc.client.RequestEncoderDecoder( - "POST", apiV2VulnerabilitiesContainersSearch, - filters, &response, - ) - return -} - -// SearchAllPages iterates over all pages and returns a list of VulnerabilityContainer -func (svc *v2ContainerVulnerabilityService) SearchAllPages(filters SearchFilter) ( - response VulnerabilitiesContainersResponse, err error, -) { - response, err = svc.Search(filters) - if err != nil { - return - } - - var ( - all []VulnerabilityContainer - pageOk bool - ) - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -func (svc *v2ContainerVulnerabilityService) ScanStatus(id string) ( - response VulnerabilitiesContainersScanStatusResponse, err error, -) { - err = svc.client.RequestDecoder("GET", - fmt.Sprintf(apiV2VulnerabilitiesContainersScanStatus, id), - nil, - &response) - return -} - -func (svc *v2ContainerVulnerabilityService) Scan(registry, repository, tagOrHash string) ( - response VulnerabilitiesContainerScanResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder("POST", - apiV2VulnerabilitiesContainersScan, - vulnContainerScanRequest{registry, repository, tagOrHash}, - &response, - ) - return -} - -type vulnContainerScanRequest struct { - Registry string `json:"registry"` - Repository string `json:"repository"` - Tag string `json:"tag"` -} - -type VulnerabilitiesContainersScanStatusResponse struct { - Message string `json:"message"` - Data struct { - EvalGuid string `json:"evalGuid"` - Status string `json:"status"` - } `json:"data"` -} - -func (res *VulnerabilitiesContainersScanStatusResponse) CheckStatus() string { - if res.Data.Status != "" { - return res.Data.Status - } - - return "Unknown" -} - -type VulnerabilitiesContainerScanResponse struct { - Message string `json:"message"` - Data struct { - RequestID string `json:"requestId"` - Status string `json:"status"` - } `json:"data"` -} - -func (res *VulnerabilitiesContainerScanResponse) CheckStatus() string { - if res.Data.Status != "" { - return res.Data.Status - } - - return "Unknown" -} - -type VulnerabilitiesContainersResponse struct { - Data []VulnerabilityContainer `json:"data"` - Paging V2Pagination `json:"paging"` - - v2PageMetadata `json:"-"` -} - -func (r *VulnerabilitiesContainersResponse) FilterSingleVulnIDData(vulnID string) { - var singleVulnData = make([]VulnerabilityContainer, 0) - for _, vuln := range r.Data { - if vuln.VulnID == vulnID { - singleVulnData = append(singleVulnData, vuln) - } - } - r.Data = singleVulnData -} - -func (r VulnerabilitiesContainersResponse) HighestSeverity() string { - var sevs []lwseverity.Severity - - for _, vuln := range r.Data { - if lwseverity.NewSeverity(vuln.Severity) != lwseverity.Unknown { - sevs = append(sevs, lwseverity.NewSeverity(vuln.Severity)) - } - } - - if len(sevs) == 0 { - return lwseverity.Unknown.String() - } - - lwseverity.SortSlice(sevs) - return sevs[0].GetSeverity() -} - -func (r VulnerabilitiesContainersResponse) HighestFixableSeverity() string { - var ( - sevs []int - max int - ) - for _, vuln := range r.Data { - if vuln.FixInfo.FixAvailable == 1 { - sevs = append(sevs, SeverityOrder(vuln.Severity)) - if len(sevs) == 1 { - max = SeverityOrder(vuln.Severity) - } else if SeverityOrder(vuln.Severity) > max { - max = SeverityOrder(vuln.Severity) - } - } - } - - return SeverityInt(max) -} - -func (r VulnerabilitiesContainersResponse) VulnFixableCount(severity string) int32 { - count := 0 - for _, vuln := range r.Data { - if vuln.FixInfo.FixAvailable == 1 && strings.EqualFold(vuln.Severity, severity) { - count = count + 1 - } - } - return int32(count) -} - -func (r VulnerabilitiesContainersResponse) TotalVulnerabilities() int { - count := 0 - for _, vuln := range r.Data { - if vuln.Status == "VULNERABLE" { - count = count + 1 - } - } - return count -} - -// Fulfill Pagination interface (look at api/v2.go) -func (r VulnerabilitiesContainersResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *VulnerabilitiesContainersResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -func (r VulnerabilitiesContainersResponse) CriticalVulnerabilities() int32 { - count := 0 - for _, vuln := range r.Data { - if vuln.Severity == "Critical" { - count = count + 1 - } - } - return int32(count) -} - -func (r VulnerabilitiesContainersResponse) HighVulnerabilities() int32 { - count := 0 - for _, vuln := range r.Data { - if vuln.Severity == "High" { - count = count + 1 - } - } - return int32(count) -} - -func (r VulnerabilitiesContainersResponse) MediumVulnerabilities() int32 { - count := 0 - for _, vuln := range r.Data { - if vuln.Severity == "Medium" { - count = count + 1 - } - } - return int32(count) -} - -func (r VulnerabilitiesContainersResponse) LowVulnerabilities() int32 { - count := 0 - for _, vuln := range r.Data { - if vuln.Severity == "Low" { - count = count + 1 - } - } - return int32(count) -} - -func (r VulnerabilitiesContainersResponse) InfoVulnerabilities() int32 { - count := 0 - for _, vuln := range r.Data { - if vuln.Severity == "Info" { - count = count + 1 - } - } - return int32(count) -} - -func (r VulnerabilitiesContainersResponse) FixableVulnerabilities() int32 { - count := 0 - for _, vuln := range r.Data { - if vuln.FixInfo.FixAvailable == 1 { - count = count + 1 - } - } - return int32(count) -} - -func (r VulnerabilitiesContainersResponse) TotalFixableVulnerabilities() int32 { - count := 0 - for _, vuln := range r.Data { - if vuln.FixInfo.FixAvailable == 1 { - count = count + 1 - } - } - return int32(count) -} - -type ImageInfo struct { - CreatedTime int64 `json:"created_time"` - Digest string `json:"digest"` - ErrorMsg []string `json:"error_msg"` - ID string `json:"id"` - Registry string `json:"registry"` - Repo string `json:"repo"` - Size int `json:"size"` - Status string `json:"status"` - Tags []string `json:"tags"` - Type string `json:"type"` -} - -type VulnerabilityContainer struct { - EvalGUID string `json:"evalGuid"` - EvalCtx struct { - CveBatchInfo []struct { - CveBatchID string `json:"cve_batch_id"` - CveCreatedTime string `json:"cve_created_time"` - } `json:"cve_batch_info"` - ExceptionProps []struct { - Status string `json:"status"` - } `json:"exception_props"` - ImageInfo ImageInfo `json:"image_info"` - IsDailyJob string `json:"isDailyJob"` - IsReeval bool `json:"is_reeval"` - ScanBatchID string `json:"scan_batch_id"` - ScanCreatedTime string `json:"scan_created_time"` - ScanRequestProps struct { - DataFormatVersion string `json:"data_format_version"` - Environment struct { - DockerVersion struct { - ErrorMessage string `json:"error_message"` - } `json:"docker_version"` - } `json:"environment"` - Props struct { - DataFormatVersion string `json:"data_format_version"` - ScannerVersion string `json:"scanner_version"` - } `json:"props"` - ScanCompletionUtcTime int `json:"scanCompletionUtcTime"` - ScanStartTime int `json:"scan_start_time"` - ScannerVersion string `json:"scanner_version"` - } `json:"scan_request_props"` - VulnBatchID string `json:"vuln_batch_id"` - VulnCreatedTime string `json:"vuln_created_time"` - } `json:"evalCtx"` - FeatureKey struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Version string `json:"version"` - } `json:"featureKey"` - FeatureProps struct { - IntroducedIn string `json:"introduced_in"` - Layer string `json:"layer"` - Feed string `json:"feed"` - Src string `json:"src"` - VersionFormat string `json:"version_format"` - } `json:"featureProps"` - FixInfo struct { - CompareResult int `json:"compare_result"` - FixAvailable int `json:"fix_available"` - FixedVersion string `json:"fixed_version"` - } `json:"fixInfo"` - ImageID string `json:"imageId"` - Severity string `json:"severity"` - StartTime time.Time `json:"startTime"` - Status string `json:"status"` - VulnID string `json:"vulnId"` -} - -// v2HostVulnerabilityService is a service that interacts with the APIv2 -// vulnerabilities endpoints for hosts -type v2HostVulnerabilityService struct { - client *Client -} - -// SearchLastWeek returns a list of VulnerabilityHost from the last 7 days -func (svc *v2HostVulnerabilityService) SearchLastWeek() (VulnerabilitiesHostResponse, error) { - var ( - now = time.Now().UTC() - before = now.AddDate(0, 0, -7) // 7 days from ago - ) - - return svc.Search(SearchFilter{ - TimeFilter: &TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - }) -} - -// Search returns a list of VulnerabilityHost from the last 7 days -func (svc *v2HostVulnerabilityService) Search(filters SearchFilter) ( - response VulnerabilitiesHostResponse, err error, -) { - err = svc.client.RequestEncoderDecoder( - "POST", apiV2VulnerabilitiesHostsSearch, - filters, &response, - ) - return -} - -// SearchAllPages iterates over all pages and returns a list of VulnerabilityHost -func (svc *v2HostVulnerabilityService) SearchAllPages(filters SearchFilter) ( - response VulnerabilitiesHostResponse, err error, -) { - response, err = svc.Search(filters) - if err != nil { - return - } - - var ( - all []VulnerabilityHost - pageOk bool - ) - for { - all = append(all, response.Data...) - - pageOk, err = svc.client.NextPage(&response) - if err == nil && pageOk { - continue - } - break - } - - response.ResetPaging() - response.Data = all - return -} - -type VulnerabilitiesHostResponse struct { - Data []VulnerabilityHost `json:"data"` - Paging V2Pagination `json:"paging"` - - v2PageMetadata `json:"-"` -} - -// Fulfill Pagination interface (look at api/v2.go) -func (r VulnerabilitiesHostResponse) PageInfo() *V2Pagination { - return &r.Paging -} -func (r *VulnerabilitiesHostResponse) ResetPaging() { - r.Paging = V2Pagination{} - r.Data = nil -} - -type VulnerabilityHost struct { - CveProps struct { - CveBatchID string `json:"cve_batch_id"` - Description string `json:"description"` - Link string `json:"link"` - Metadata *VulnerabilityHostMetadata `json:"metadata,omitempty"` - } `json:"cveProps"` - EvalCtx struct { - ExceptionProps []interface{} `json:"exception_props"` - Hostname string `json:"hostname"` - McEvalGUID string `json:"mc_eval_guid"` - CollectorType string `json:"collector_type"` - } `json:"evalCtx"` - FeatureKey struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - PackageActive int `json:"package_active"` - VersionInstalled string `json:"version_installed"` - } `json:"featureKey"` - FixInfo struct { - CompareResult string `json:"compare_result"` - EvalStatus string `json:"eval_status"` - FixAvailable string `json:"fix_available"` - FixedVersion string `json:"fixed_version"` - FixedVersionComparisonInfos []struct { - CurrFixVer string `json:"curr_fix_ver"` - IsCurrFixVerGreaterThanOtherFixVer string `json:"is_curr_fix_ver_greater_than_other_fix_ver"` - OtherFixVer string `json:"other_fix_ver"` - } `json:"fixed_version_comparison_infos"` - FixedVersionComparisonScore int `json:"fixed_version_comparison_score"` - VersionInstalled string `json:"version_installed"` - } `json:"fixInfo"` - MachineTags any `json:"machineTags"` - Props VulnerabilityHostProps `json:"props"` - Mid int `json:"mid"` - Severity string `json:"severity"` - StartTime time.Time `json:"startTime"` - EndTime time.Time `json:"endTime"` - EvalGUID string `json:"evalGuid"` - Status string `json:"status"` - VulnID string `json:"vulnId"` -} - -func (v *VulnerabilityHost) GetMachineTags() (machineTags VulnerabilityHostMachineTags, err error) { - jsonTags, err := json.Marshal(v.MachineTags) - if err != nil { - return - } - - err = json.Unmarshal(jsonTags, &machineTags) - return -} - -func (v *VulnerabilityHost) GetMachineTagsRaw() (map[string]interface{}, error) { - jsonTags, err := json.Marshal(v.MachineTags) - if err != nil { - return nil, err - } - - var rawTags map[string]interface{} - - if err := json.Unmarshal(jsonTags, &rawTags); err != nil { - return nil, err - } - - return rawTags, nil -} - -type VulnerabilityHostMachineTags struct { - Account string `json:"Account"` - AmiID string `json:"AmiId"` - Env string `json:"Env"` - ExternalIP string `json:"ExternalIp"` - Hostname string `json:"Hostname"` - InstanceID string `json:"InstanceId"` - InternalIP string `json:"InternalIp"` - LwTokenShort string `json:"LwTokenShort"` - Name string `json:"Name"` - SubnetID string `json:"SubnetId"` - VMInstanceType string `json:"VmInstanceType"` - VMProvider string `json:"VmProvider"` - VpcID string `json:"VpcId"` - Zone string `json:"Zone"` - AlphaEksctlIoNodegroupName string `json:"alpha.eksctl.io/nodegroup-name"` - AlphaEksctlIoNodegroupType string `json:"alpha.eksctl.io/nodegroup-type"` - Arch string `json:"arch"` - AwsAutoscalingGroupName string `json:"aws:autoscaling:groupName"` - AwsEc2FleetID string `json:"aws:ec2:fleet-id"` - AwsEc2LaunchtemplateID string `json:"aws:ec2launchtemplate:id"` - AwsEc2LaunchtemplateVersion string `json:"aws:ec2launchtemplate:version"` - EksClusterName string `json:"eks:cluster-name"` - EksNodegroupName string `json:"eks:nodegroup-name"` - K8SIoClusterAutoscalerEnabled int `json:"k8s.io/cluster-autoscaler/enabled"` - K8SIoClusterAutoscalerTechallySandbox string `json:"k8s.io/cluster-autoscaler/techally-sandbox"` - KubernetesIoClusterTechallySandbox string `json:"kubernetes.io/cluster/techally-sandbox"` - LwKubernetesCluster string `json:"lw_KubernetesCluster"` - Os string `json:"os"` - LwInternetExposure string `json:"lw_InternetExposure"` - - //gcp - GCEtags any `json:"GCEtags"` - InstanceName string `json:"InstanceName"` - NumericProjectId string `json:"NumericProjectId"` - ProjectId string `json:"ProjectId"` -} - -func SeverityOrder(severity string) int { - switch strings.ToLower(severity) { - case "critical": - return 1 - case "high": - return 2 - case "medium": - return 3 - case "low": - return 4 - case "info": - return 5 - default: - return 6 - } -} - -func SeverityInt(sev int) string { - switch sev { - case 1: - return "Critical" - case 2: - return "High" - case 3: - return "Medium" - case 4: - return "Low" - case 5: - return "Info" - default: - return "Unknown" - } -} - -func (v *VulnerabilityHost) PackageActive() string { - if v.FeatureKey.PackageActive == 0 { - return "" - } - return "ACTIVE" -} - -func (v *VulnerabilityHost) CvssV2() string { - if v.CveProps.Metadata == nil { - return "0" - } - score := v.CveProps.Metadata.NVD.CVSSv2.Score - return strconv.FormatFloat(score, 'f', 1, 64) -} - -func (v *VulnerabilityHost) CvssV3() string { - if v.CveProps.Metadata == nil { - return "0" - } - score := v.CveProps.Metadata.NVD.CVSSv3.Score - return strconv.FormatFloat(score, 'f', 1, 64) -} - -type VulnerabilityHostMetadata struct { - NVD struct { - CVSSv2 struct { - PublishedDateTime string `json:"PublishedDateTime"` - Score float64 `json:"Score"` - Vectors string `json:"Vectors"` - } `json:"CVSSv2"` - CVSSv3 struct { - ExploitabilityScore float64 `json:"ExploitabilityScore"` - ImpactScore float64 `json:"ImpactScore"` - Score float64 `json:"Score"` - Vectors string `json:"Vectors"` - } `json:"CVSSv3"` - } `json:"NVD"` -} - -type VulnerabilityHostProps struct { - FirstTimeSeen *time.Time `json:"first_time_seen,omitempty"` - IsDailyJob int `json:"isDailyJob,omitempty"` - LastUpdatedTime *time.Time `json:"last_updated_time,omitempty"` -} - -func (v *VulnerabilityHost) HasFix() bool { - return v.FixInfo.FixAvailable == "1" -} - -func (hosts *VulnerabilitiesHostResponse) VulnerabilityCounts() HostVulnCounts { - var ( - hostCounts = HostVulnCounts{} - cves []string - ) - - for _, h := range hosts.Data { - // avoid counting duplicates - if h.VulnID == "" || array.ContainsStr(cves, h.VulnID) { - continue - } - cves = append(cves, h.VulnID) - - switch h.Severity { - case "Critical": - hostCounts.Critical++ - hostCounts.Total++ - if h.HasFix() { - hostCounts.CritFixable++ - hostCounts.TotalFixable++ - } - case "High": - hostCounts.High++ - hostCounts.Total++ - if h.HasFix() { - hostCounts.HighFixable++ - hostCounts.TotalFixable++ - } - case "Medium": - hostCounts.Medium++ - hostCounts.Total++ - if h.HasFix() { - hostCounts.MedFixable++ - hostCounts.TotalFixable++ - } - case "Low": - hostCounts.Low++ - hostCounts.Total++ - if h.HasFix() { - hostCounts.LowFixable++ - hostCounts.TotalFixable++ - } - default: - hostCounts.Info++ - hostCounts.Total++ - if h.HasFix() { - hostCounts.InfoFixable++ - hostCounts.TotalFixable++ - } - } - } - - return hostCounts -} - -// VulnerabilityAssessment is used to provide common functions that are -// required by host or container vulnerability assessments, this is used -// to treat them both as equal -type VulnerabilityAssessment interface { - HighestSeverity() string - HighestFixableSeverity() string - TotalFixableVulnerabilities() int32 -} - -type HostVulnCounts struct { - Critical int32 - CritFixable int32 - High int32 - HighFixable int32 - Medium int32 - MedFixable int32 - Low int32 - LowFixable int32 - Info int32 - InfoFixable int32 - Total int32 - TotalFixable int32 -} - -// HighestSeverity returns the highest severity level vulnerability -func (h *HostVulnCounts) HighestSeverity() string { - if h.Critical != 0 { - return "critical" - } - if h.High != 0 { - return "high" - } - if h.Medium != 0 { - return "medium" - } - if h.Low != 0 { - return "low" - } - return "unknown" -} - -// HighestFixableSeverity returns the highest fixable severity level vulnerability -func (h *HostVulnCounts) HighestFixableSeverity() string { - if h.CritFixable != 0 { - return "critical" - } - if h.HighFixable != 0 { - return "high" - } - if h.MedFixable != 0 { - return "medium" - } - if h.LowFixable != 0 { - return "low" - } - return "unknown" -} - -// TotalFixableVulnerabilities returns the total number of vulnerabilities that have a fix available -func (h *HostVulnCounts) TotalFixableVulnerabilities() int32 { - return h.TotalFixable -} diff --git a/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities_software_packages.go b/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities_software_packages.go deleted file mode 100644 index ca8671933..000000000 --- a/vendor/github.com/lacework/go-sdk/api/v2_vulnerabilities_software_packages.go +++ /dev/null @@ -1,205 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import "fmt" - -// v2SoftwarePackagesVulnerabilityService is a service that interacts with the APIv2 -// vulnerabilities endpoints for software packages -type v2SoftwarePackagesVulnerabilityService struct { - client *Client -} - -// Scan on-demand vulnerability assessment of your software packages -func (svc *v2SoftwarePackagesVulnerabilityService) Scan(manifest VulnerabilitiesPackageManifest) ( - response VulnerabilitySoftwarePackagesResponse, err error, -) { - err = svc.client.RequestEncoderDecoder( - "POST", apiV2VulnerabilitiesSoftwarePackagesScan, - manifest, &response, - ) - return -} - -func (v *VulnerabilitySoftwarePackage) HasFix() bool { - return v.FixInfo.FixAvailable == 1 -} - -func (v *VulnerabilitySoftwarePackage) IsVulnerable() bool { - return v.FixInfo.EvalStatus == "VULNERABLE" -} - -func (v *VulnerabilitySoftwarePackage) ScoreString() string { - if v.CveProps.Metadata.Nvd.Cvssv3.Score != 0 { - return fmt.Sprintf("%.1f", v.CveProps.Metadata.Nvd.Cvssv3.Score) - } - - if v.CveProps.Metadata.Nvd.Cvssv2.Score != 0 { - return fmt.Sprintf("%.1f", v.CveProps.Metadata.Nvd.Cvssv2.Score) - } - - return "" -} - -func (v *VulnerabilitySoftwarePackagesResponse) VulnerabilityCounts() HostVulnCounts { - var hostCounts = HostVulnCounts{} - - for _, vuln := range v.Data { - switch vuln.Severity { - case "Critical": - hostCounts.Critical++ - hostCounts.Total++ - if vuln.HasFix() { - hostCounts.CritFixable++ - hostCounts.TotalFixable++ - } - case "High": - hostCounts.High++ - hostCounts.Total++ - if vuln.HasFix() { - hostCounts.HighFixable++ - hostCounts.TotalFixable++ - } - case "Medium": - hostCounts.Medium++ - hostCounts.Total++ - if vuln.HasFix() { - hostCounts.MedFixable++ - hostCounts.TotalFixable++ - } - case "Low": - hostCounts.Low++ - hostCounts.Total++ - if vuln.HasFix() { - hostCounts.LowFixable++ - hostCounts.TotalFixable++ - } - default: - hostCounts.Info++ - hostCounts.Total++ - if vuln.HasFix() { - hostCounts.InfoFixable++ - hostCounts.TotalFixable++ - } - } - } - - return hostCounts -} - -type VulnerabilitySoftwarePackagesResponse struct { - Data []VulnerabilitySoftwarePackage `json:"data"` -} - -type VulnerabilitySoftwarePackage struct { - OsPkgInfo struct { - Namespace string `json:"namespace"` - Os string `json:"os"` - OsVer string `json:"osVer"` - Pkg string `json:"pkg"` - PkgVer string `json:"pkgVer"` - VersionFormat string `json:"versionFormat"` - } `json:"osPkgInfo"` - VulnID string `json:"vulnId"` - Severity string `json:"severity"` - FeatureKey struct { - AffectedRange struct { - End struct { - Inclusive bool `json:"inclusive"` - Value string `json:"value"` - } `json:"end"` - FixVersion string `json:"fixVersion"` - Start struct { - Inclusive bool `json:"inclusive"` - Value string `json:"value"` - } `json:"start"` - } `json:"affectedRange"` - Name string `json:"name"` - Namespace string `json:"namespace"` - } `json:"featureKey"` - CveProps struct { - CveBatchId string `json:"cveBatchId"` - Description string `json:"description"` - Link string `json:"link"` - Metadata struct { - Nvd struct { - Cvssv2 struct { - Publisheddatetime string `json:"publisheddatetime"` - Score float64 `json:"score"` - Vectors string `json:"vectors"` - } `json:"cvssv2"` - Cvssv3 struct { - Exploitabilityscore float64 `json:"exploitabilityscore"` - Impactscore float64 `json:"impactscore"` - Score float64 `json:"score"` - Vectors string `json:"vectors"` - } `json:"cvssv3"` - } `json:"nvd"` - } `json:"metadata"` - } `json:"cveProps"` - FixInfo struct { - CompareResult int `json:"compareResult"` - EvalStatus string `json:"evalStatus"` - FixAvailable int `json:"fixAvailable"` - FixedVersion string `json:"fixedVersion"` - FixedVersionComparisonInfos []struct { - CurrFixVer string `json:"currFixVer"` - IsCurrFixVerGreaterThanOtherFixVer string `json:"isCurrFixVerGreaterThanOtherFixVer"` - OtherFixVer string `json:"otherFixVer"` - } `json:"fixedVersionComparisonInfos"` - FixedVersionComparisonScore int `json:"fixedVersionComparisonScore"` - MaxPrefixMatchingLenScore int `json:"maxPrefixMatchingLenScore"` - VersionInstalled string `json:"versionInstalled"` - } `json:"fixInfo"` - Summary struct { - EvalCreatedTime string `json:"evalCreatedTime"` - EvalStatus string `json:"evalStatus"` - NumFixableVuln int `json:"numFixableVuln"` - NumFixableVulnBySeverity struct { - Critical int `json:"1"` - High int `json:"2"` - Medium int `json:"3"` - Low int `json:"4"` - Info int `json:"5"` - } `json:"numFixableVulnBySeverity"` - NumTotal int `json:"numTotal"` - NumVuln int `json:"numVuln"` - NumVulnBySeverity struct { - Critical int `json:"1"` - High int `json:"2"` - Field3 int `json:"3"` - Medium int `json:"4"` - Info int `json:"5"` - } `json:"numVulnBySeverity"` - } `json:"summary"` - Props struct { - EvalAlgo string `json:"evalAlgo"` - } `json:"props"` -} - -type VulnerabilitiesPackageManifest struct { - OsPkgInfoList []VulnerabilitiesOsPkgInfo `json:"osPkgInfoList"` -} - -type VulnerabilitiesOsPkgInfo struct { - Os string `json:"os"` - OsVer string `json:"osVer"` - Pkg string `json:"pkg"` - PkgVer string `json:"pkgVer"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/version.go b/vendor/github.com/lacework/go-sdk/api/version.go deleted file mode 100644 index 3720821db..000000000 --- a/vendor/github.com/lacework/go-sdk/api/version.go +++ /dev/null @@ -1,10 +0,0 @@ -// Code generated by: scripts/version_updater.sh -// File generated at: 20241017162419 -// -// <<< DO NOT EDIT >>> -// - -package api - -// Version is the semver coming from the VERSION file -const Version = "1.54.1-dev" diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go deleted file mode 100644 index c356321ff..000000000 --- a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go +++ /dev/null @@ -1,474 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - "strings" - "time" - - "github.com/pkg/errors" -) - -// VulnerabilityExceptionsService is the service that interacts with -// the VulnerabilityExceptions schema from the Lacework APIv2 Server -type VulnerabilityExceptionsService struct { - client *Client -} - -// vulnerabilityExceptionResourceScope is an interface for the 2 types of vulnerability exceptions resource scopes: -// 'VulnerabilityExceptionContainerResourceScope' or 'VulnerabilityExceptionHostResourceScope' -type vulnerabilityExceptionResourceScope interface { - Type() vulnerabilityExceptionType - Scope() VulnerabilityExceptionResourceScope -} - -// vulnerabilityExceptionReason represents the types of vulnerability exceptions reasons: -// 'False Positive', 'Accepted Risk', 'Compensating Controls', 'Fix Pending' or 'Other' -type vulnerabilityExceptionReason int - -const ( - VulnerabilityExceptionReasonAcceptedRisk vulnerabilityExceptionReason = iota - VulnerabilityExceptionReasonAcceptedFalsePositive - VulnerabilityExceptionReasonCompensatingControls - VulnerabilityExceptionReasonFixPending - VulnerabilityExceptionReasonOther - VulnerabilityExceptionReasonUnknown -) - -var VulnerabilityExceptionReasons = map[vulnerabilityExceptionReason]string{ - VulnerabilityExceptionReasonAcceptedRisk: "Accepted Risk", - VulnerabilityExceptionReasonAcceptedFalsePositive: "False Positive", - VulnerabilityExceptionReasonCompensatingControls: "Compensating Controls", - VulnerabilityExceptionReasonFixPending: "Fix Pending", - VulnerabilityExceptionReasonOther: "Other", - VulnerabilityExceptionReasonUnknown: "Unknown", -} - -func (i vulnerabilityExceptionReason) String() string { - return VulnerabilityExceptionReasons[i] -} - -func NewVulnerabilityExceptionReason(reason string) vulnerabilityExceptionReason { - switch reason { - case "Accepted Risk": - return VulnerabilityExceptionReasonAcceptedRisk - case "False Positive": - return VulnerabilityExceptionReasonAcceptedFalsePositive - case "Compensating Controls": - return VulnerabilityExceptionReasonCompensatingControls - case "Fix Pending": - return VulnerabilityExceptionReasonFixPending - case "Other": - return VulnerabilityExceptionReasonOther - default: - return VulnerabilityExceptionReasonUnknown - } -} - -// vulnerabilityExceptionType represents the types of vulnerability exceptions 'Host' or 'Container' -type vulnerabilityExceptionType int - -const ( - VulnerabilityExceptionTypeHost vulnerabilityExceptionType = iota - VulnerabilityExceptionTypeContainer -) - -var VulnerabilityExceptionTypes = map[vulnerabilityExceptionType]string{ - VulnerabilityExceptionTypeHost: "Host", - VulnerabilityExceptionTypeContainer: "Container", -} - -func (i vulnerabilityExceptionType) String() string { - return VulnerabilityExceptionTypes[i] -} - -// vulnerabilityExceptionSeverity represents the types of vulnerability severities: -// 'Critical', 'High', 'Medium', 'Low' or 'Info' -type vulnerabilityExceptionSeverity string - -type VulnerabilityExceptionSeverities []vulnerabilityExceptionSeverity - -func (sevs VulnerabilityExceptionSeverities) ToStringSlice() []string { - var res []string - for _, i := range sevs { - switch i { - case VulnerabilityExceptionSeverityCritical: - res = append(res, "Critical") - case VulnerabilityExceptionSeverityHigh: - res = append(res, "High") - case VulnerabilityExceptionSeverityMedium: - res = append(res, "Medium") - case VulnerabilityExceptionSeverityLow: - res = append(res, "Low") - case VulnerabilityExceptionSeverityInfo: - res = append(res, "Info") - default: - continue - } - } - return res -} - -func NewVulnerabilityExceptionSeverities(sevSlice []string) VulnerabilityExceptionSeverities { - var res VulnerabilityExceptionSeverities - for _, i := range sevSlice { - sev := convertVulnerabilityExceptionSeverity(i) - if sev != VulnerabilityExceptionSeverityUnknown { - res = append(res, sev) - } - } - return res -} - -func convertVulnerabilityExceptionSeverity(sev string) vulnerabilityExceptionSeverity { - switch strings.ToLower(sev) { - case "critical": - return VulnerabilityExceptionSeverityCritical - case "high": - return VulnerabilityExceptionSeverityHigh - case "medium": - return VulnerabilityExceptionSeverityMedium - case "low": - return VulnerabilityExceptionSeverityLow - case "info": - return VulnerabilityExceptionSeverityInfo - default: - return VulnerabilityExceptionSeverityUnknown - } -} - -const ( - VulnerabilityExceptionSeverityCritical vulnerabilityExceptionSeverity = "Critical" - VulnerabilityExceptionSeverityHigh vulnerabilityExceptionSeverity = "High" - VulnerabilityExceptionSeverityMedium vulnerabilityExceptionSeverity = "Medium" - VulnerabilityExceptionSeverityLow vulnerabilityExceptionSeverity = "Low" - VulnerabilityExceptionSeverityInfo vulnerabilityExceptionSeverity = "Info" - VulnerabilityExceptionSeverityUnknown vulnerabilityExceptionSeverity = "Unknown" -) - -// NewVulnerabilityException returns an instance of the VulnerabilityException struct -// -// Basic usage: Initialize a new VulnerabilityException struct, then -// -// use the new instance to do CRUD operations -// -// client, err := api.NewClient("account") -// if err != nil { -// return err -// } -// -// exception := api.VulnerabilityExceptionConfig{ -// Type: api.VulnerabilityExceptionTypeHost, -// Description: "This is a vuln exception", -// ExceptionReason: api.VulnerabilityExceptionReasonCompensatingControls, -// Severities: api.VulnerabilityExceptionSeverities{api.VulnerabilityExceptionSeverityCritical}, -// Fixable: true, -// ResourceScope: api.VulnerabilityExceptionContainerResourceScope{ -// ImageID: []string{""}, -// ImageTag: []string{""}, -// Registry: []string{""}, -// Repository: []string{""}, -// Namespace: []string{""}, -// }, -// ExpiryTime: time.Now().AddDate(0, 1, 0), -// } -// -// vulnerabilityException := api.NewVulnerabilityException("vulnerabilityException", exception) -// -// client.V2.VulnerabilityExceptions.Create(vulnerabilityException) -func NewVulnerabilityException(name string, exception VulnerabilityExceptionConfig) VulnerabilityException { - var ( - packages = aggregatePackages(exception.Package) - vulnException = VulnerabilityException{ - Enabled: 1, - ExceptionName: name, - ExceptionReason: exception.ExceptionReason.String(), - Props: VulnerabilityExceptionProps{Description: exception.Description}, - VulnerabilityCriteria: VulnerabilityExceptionCriteria{ - Severity: exception.Severities.ToStringSlice(), - Package: packages, - Cve: exception.Cve, - Fixable: exception.FixableEnabled(), - }, - } - ) - - if !exception.ExpiryTime.IsZero() { - vulnException.ExpiryTime = exception.ExpiryTime.UTC().Format(time.RFC3339) - } - - vulnException.ExceptionType = exception.Type.String() - vulnException.setResourceScope(exception.ResourceScope) - - return vulnException -} - -func aggregatePackages(packages []VulnerabilityExceptionPackage) []map[string][]string { - var packs []map[string][]string - for _, pck := range packages { - var packagesMap = make(map[string][]string) - //aggregate packages with same name - if len(packs) > 0 { - if _, ok := packs[0][pck.Name]; ok { - packs[0][pck.Name] = append(packs[0][pck.Name], pck.Version) - continue - } - } - packagesMap[pck.Name] = []string{pck.Version} - packs = append(packs, packagesMap) - } - return packs -} - -func (exception *VulnerabilityException) setResourceScope(scope vulnerabilityExceptionResourceScope) { - if scope == nil { - return - } - switch scope.Type() { - case VulnerabilityExceptionTypeContainer: - ctr := scope.Scope() - exception.ResourceScope = &VulnerabilityExceptionResourceScope{ - ImageID: ctr.ImageID, - ImageTag: ctr.ImageTag, - Registry: ctr.Registry, - Repository: ctr.Repository, - Namespace: ctr.Namespace, - } - case VulnerabilityExceptionTypeHost: - host := scope.Scope() - exception.ResourceScope = &VulnerabilityExceptionResourceScope{ - Hostname: host.Hostname, - ClusterName: host.ClusterName, - ExternalIP: host.ExternalIP, - Namespace: host.Namespace, - } - default: - exception.ResourceScope = &VulnerabilityExceptionResourceScope{} - } -} - -func (exception VulnerabilityException) Status() string { - if exception.Enabled == 1 { - return "Enabled" - } - return "Disabled" -} - -func (cfg VulnerabilityExceptionConfig) FixableEnabled() []int { - if cfg.Fixable == nil { - return nil - } - - if *cfg.Fixable { - return []int{1} - } - return []int{0} -} - -// List returns a list of Vulnerability Exceptions -func (svc *VulnerabilityExceptionsService) List() (response VulnerabilityExceptionsResponse, err error) { - err = svc.client.RequestDecoder("GET", apiV2VulnerabilityExceptions, nil, &response) - return -} - -// Create creates a single Vulnerability Exception -func (svc *VulnerabilityExceptionsService) Create(vuln VulnerabilityException) ( - response VulnerabilityExceptionResponse, - err error, -) { - err = svc.client.RequestEncoderDecoder("POST", apiV2VulnerabilityExceptions, vuln, &response) - return -} - -// Delete deletes a Vulnerability Exception that matches the provided guid -func (svc *VulnerabilityExceptionsService) Delete(guid string) error { - if guid == "" { - return errors.New("specify an intgGuid") - } - - return svc.client.RequestDecoder( - "DELETE", - fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid), - nil, - nil, - ) -} - -// Update updates a single Vulnerability Exception. -func (svc *VulnerabilityExceptionsService) Update(data VulnerabilityException) ( - response VulnerabilityExceptionResponse, - err error, -) { - if data.Guid == "" { - err = errors.New("specify a Guid") - return - } - apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, data.Guid) - // Request is invalid if it contains the ID field. We set the id field to empty - data.Guid = "" - err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) - return -} - -// Get returns a raw response of the Vulnerability Exception with the matching guid. -func (svc *VulnerabilityExceptionsService) Get(guid string, response interface{}) error { - if guid == "" { - return errors.New("specify a Guid") - } - apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid) - return svc.client.RequestDecoder("GET", apiPath, nil, &response) -} - -type VulnerabilityExceptionConfig struct { - Description string - Type vulnerabilityExceptionType - ExceptionReason vulnerabilityExceptionReason - Severities VulnerabilityExceptionSeverities - Cve []string - Package []VulnerabilityExceptionPackage - Fixable *bool - ResourceScope vulnerabilityExceptionResourceScope - ExpiryTime time.Time -} - -type VulnerabilityExceptionContainerResourceScope struct { - ImageID []string `json:"imageId,omitempty"` - ImageTag []string `json:"imageTag,omitempty"` - Registry []string `json:"registry,omitempty"` - Repository []string `json:"repository,omitempty"` - Namespace []string `json:"namespace,omitempty"` -} - -func (ctr VulnerabilityExceptionContainerResourceScope) Type() vulnerabilityExceptionType { - return VulnerabilityExceptionTypeContainer -} - -func (ctr VulnerabilityExceptionContainerResourceScope) Scope() VulnerabilityExceptionResourceScope { - return VulnerabilityExceptionResourceScope{ - ImageID: ctr.ImageID, - ImageTag: ctr.ImageTag, - Registry: ctr.Registry, - Repository: ctr.Repository, - Namespace: ctr.Namespace, - } -} - -func (host VulnerabilityExceptionHostResourceScope) Scope() VulnerabilityExceptionResourceScope { - return VulnerabilityExceptionResourceScope{ - Hostname: host.Hostname, - ExternalIP: host.ExternalIP, - ClusterName: host.ClusterName, - Namespace: host.Namespace, - } -} - -type VulnerabilityExceptionHostResourceScope struct { - Hostname []string `json:"hostname,omitempty"` - ExternalIP []string `json:"externalIp,omitempty"` - ClusterName []string `json:"clusterName,omitempty"` - Namespace []string `json:"namespace,omitempty"` -} - -func (host VulnerabilityExceptionHostResourceScope) Type() vulnerabilityExceptionType { - return VulnerabilityExceptionTypeHost -} - -type VulnerabilityException struct { - Guid string `json:"exceptionGuid,omitempty"` - Enabled int `json:"state"` - ExceptionName string `json:"exceptionName"` - ExceptionType string `json:"exceptionType"` - ExceptionReason string `json:"exceptionReason"` - Props VulnerabilityExceptionProps `json:"props"` - VulnerabilityCriteria VulnerabilityExceptionCriteria `json:"vulnerabilityCriteria"` - ResourceScope *VulnerabilityExceptionResourceScope `json:"resourceScope,omitempty"` - CreatedTime string `json:"createdTime,omitempty"` - UpdatedTime string `json:"updatedTime,omitempty"` - ExpiryTime string `json:"expiryTime,omitempty"` -} - -type VulnerabilityExceptionProps struct { - Description string `json:"description,omitempty"` - CreatedBy string `json:"createdBy,omitempty"` - UpdatedBy string `json:"updatedBy,omitempty"` -} - -type VulnerabilityExceptionResourceScope struct { - // Container properties - ImageID []string `json:"imageId,omitempty"` - ImageTag []string `json:"imageTag,omitempty"` - Registry []string `json:"registry,omitempty"` - Repository []string `json:"repository,omitempty"` - - // Host properties - Hostname []string `json:"hostname,omitempty"` - ExternalIP []string `json:"externalIp,omitempty"` - ClusterName []string `json:"clusterName,omitempty"` - - // Shared properties - Namespace []string `json:"namespace,omitempty"` -} - -type VulnerabilityExceptionCriteria struct { - Cve []string `json:"cve,omitempty"` - Package []map[string][]string `json:"package,omitempty"` - Severity []string `json:"severity,omitempty"` - Fixable []int `json:"fixable,omitempty"` -} - -type VulnerabilityExceptionResponse struct { - Data VulnerabilityException `json:"data"` -} - -type VulnerabilityExceptionsResponse struct { - Data []VulnerabilityException `json:"data"` -} - -type VulnerabilityExceptionPackage struct { - Name string - Version string -} - -func (vc VulnerabilityExceptionCriteria) FixableEnabled() *bool { - if vc.Fixable == nil || len(vc.Fixable) == 0 { - return nil - } - - if len(vc.Fixable) > 0 { - truePtr := vc.Fixable[0] == 1 - return &truePtr - } - falsePtr := false - return &falsePtr -} - -func NewVulnerabilityExceptionPackages(packageMap []map[string]string) []VulnerabilityExceptionPackage { - var packages []VulnerabilityExceptionPackage - for _, m := range packageMap { - for k, v := range m { - pck := VulnerabilityExceptionPackage{ - Name: k, - Version: v, - } - packages = append(packages, pck) - } - } - return packages -} diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go deleted file mode 100644 index fe273624f..000000000 --- a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go +++ /dev/null @@ -1,91 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/pkg/errors" -) - -func (svc *VulnerabilityExceptionsService) CreateVulnerabilityExceptionsContainer(vuln VulnerabilityException) ( - response VulnerabilityExceptionContainerResponse, err error) { - err = svc.client.RequestEncoderDecoder("POST", apiV2VulnerabilityExceptions, vuln, &response) - return -} - -func (svc *VulnerabilityExceptionsService) GetVulnerabilityExceptionsContainer(guid string) ( - response VulnerabilityExceptionContainerResponse, err error, -) { - if guid == "" { - err = errors.New("specify a Guid") - return - } - apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid) - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} - -func (svc *VulnerabilityExceptionsService) ListVulnerabilityExceptionsContainers() ( - response VulnerabilityExceptionContainerResponse, err error, -) { - err = svc.client.RequestDecoder("GET", apiV2VulnerabilityExceptions, nil, &response) - return -} - -func (svc *VulnerabilityExceptionsService) UpdateVulnerabilityExceptionsContainer( - data VulnerabilityException, id string, -) ( - response VulnerabilityExceptionContainerResponse, - err error, -) { - if id == "" { - err = errors.New("specify a Guid") - return - } - apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, id) - err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) - return -} - -type VulnerabilityExceptionContainerResponse struct { - Data VulnerabilityExceptionContainer `json:"data"` -} - -type VulnerabilityExceptionContainer struct { - Guid string `json:"exceptionGuid,omitempty"` - Enabled int `json:"state"` - ExceptionName string `json:"exceptionName"` - ExceptionType string `json:"exceptionType"` - ExceptionReason string `json:"exceptionReason"` - Props VulnerabilityExceptionProps `json:"props"` - VulnerabilityCriteria VulnerabilityExceptionCriteria `json:"vulnerabilityCriteria"` - ResourceScope VulnerabilityExceptionResourceScopeContainer `json:"resourceScope,omitempty"` - CreatedTime string `json:"createdTime,omitempty"` - UpdatedTime string `json:"updatedTime,omitempty"` - ExpiryTime string `json:"expiryTime,omitempty"` -} - -type VulnerabilityExceptionResourceScopeContainer struct { - ImageID []string `json:"imageId,omitempty"` - ImageTag []string `json:"imageTag,omitempty"` - Registry []string `json:"registry,omitempty"` - Repository []string `json:"repository,omitempty"` - Namespace []string `json:"namespace,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go deleted file mode 100644 index 536c93b56..000000000 --- a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go +++ /dev/null @@ -1,88 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package api - -import ( - "fmt" - - "github.com/pkg/errors" -) - -func (svc *VulnerabilityExceptionsService) CreateVulnerabilityExceptionsHost(vuln VulnerabilityException) ( - response VulnerabilityExceptionHostResponse, err error) { - err = svc.client.RequestEncoderDecoder("POST", apiV2VulnerabilityExceptions, vuln, &response) - return -} - -func (svc *VulnerabilityExceptionsService) GetVulnerabilityExceptionsHost(guid string) ( - response VulnerabilityExceptionHostResponse, err error, -) { - if guid == "" { - err = errors.New("specify a Guid") - return - } - apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid) - err = svc.client.RequestDecoder("GET", apiPath, nil, &response) - return -} - -func (svc *VulnerabilityExceptionsService) ListVulnerabilityExceptionsHosts() ( - response VulnerabilityExceptionHostResponse, err error, -) { - err = svc.client.RequestDecoder("GET", apiV2VulnerabilityExceptions, nil, &response) - return -} - -func (svc *VulnerabilityExceptionsService) UpdateVulnerabilityExceptionsHost(data VulnerabilityException, id string) ( - response VulnerabilityExceptionHostResponse, - err error, -) { - if id == "" { - err = errors.New("specify a Guid") - return - } - apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, id) - err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) - return -} - -type VulnerabilityExceptionHostResponse struct { - Data VulnerabilityExceptionHost `json:"data"` -} - -type VulnerabilityExceptionHost struct { - Guid string `json:"exceptionGuid,omitempty"` - Enabled int `json:"state"` - ExceptionName string `json:"exceptionName"` - ExceptionType string `json:"exceptionType"` - ExceptionReason string `json:"exceptionReason"` - Props VulnerabilityExceptionProps `json:"props"` - VulnerabilityCriteria VulnerabilityExceptionCriteria `json:"vulnerabilityCriteria"` - ResourceScope VulnerabilityExceptionResourceScopeHost `json:"resourceScope,omitempty"` - CreatedTime string `json:"createdTime,omitempty"` - UpdatedTime string `json:"updatedTime,omitempty"` - ExpiryTime string `json:"expiryTime,omitempty"` -} - -type VulnerabilityExceptionResourceScopeHost struct { - Hostname []string `json:"hostname,omitempty"` - ExternalIP []string `json:"externalIp,omitempty"` - ClusterName []string `json:"clusterName,omitempty"` - Namespace []string `json:"namespace,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cdk/client/go/client.go b/vendor/github.com/lacework/go-sdk/cli/cdk/client/go/client.go deleted file mode 100644 index 1cf57c744..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cdk/client/go/client.go +++ /dev/null @@ -1,215 +0,0 @@ -package cdk - -import ( - "context" - "encoding/json" - "os" - "time" - - cdk "github.com/lacework/go-sdk/cli/cdk/go/proto/v1" - "github.com/lacework/go-sdk/lwlogger" - "github.com/pkg/errors" - "go.uber.org/zap" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/protobuf/types/known/timestamppb" -) - -type ComponentCDKClient struct { - logger ComponentCDKLogger - coreClient cdk.CoreClient - conn *grpc.ClientConn - componentVersion string -} - -type ComponentCDKLogger interface { - Infow(msg string, keysAndValues ...interface{}) - Debugf(template string, args ...interface{}) - Debug(args ...interface{}) - Warn(args ...interface{}) -} - -type CDKClientOption func(c *ComponentCDKClient) - -func WithLogger(logger *zap.SugaredLogger) CDKClientOption { - return func(c *ComponentCDKClient) { - c.logger = logger - } -} - -// NewCDKClient creates a new component CDK client -// -// This client provides opinionated access to the services offerred from gRPC in the CDK (caching, metric data, etc) -// -// Note, ensure you are closing the gRPC connection when your component ends using the `Close()` method -// on the ComponentCDKClient -func NewCDKClient(componentVersion string, opts ...CDKClientOption) (*ComponentCDKClient, error) { - // set default logger - defaultLogger := lwlogger.New(os.Getenv("LW_LOG")).Sugar() - client := &ComponentCDKClient{logger: defaultLogger, componentVersion: componentVersion} - - for _, o := range opts { - o(client) - } - - client.logger.Infow("connecting to gRPC server", "address", os.Getenv("LW_CDK_TARGET")) - conn, err := grpc.Dial(os.Getenv("LW_CDK_TARGET"), - // we allow insecure connections since we are connecting to 'localhost' - grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - return nil, errors.Wrap(err, "cannot initilize cdk client") - } - - client.conn = conn - client.coreClient = cdk.NewCoreClient(conn) - return client, nil -} - -// Close terminates the gRPC connection to the CDK service -func (c *ComponentCDKClient) Close() error { - return c.conn.Close() -} - -// SetLogger enables overwriting the built-in logger to a custom logger that satifies the ComponentCDKLogger interface -func (c *ComponentCDKClient) SetLogger(logger ComponentCDKLogger) { - c.logger = logger -} - -type CDKCacheMissError struct { - Err error -} - -func (c *CDKCacheMissError) Error() string { - return c.Err.Error() -} - -// ReadCacheAsset fetch key from cache -// -// when an error is returned, if the reason for the error is a cache miss it will be of type -// CDKCacheMissError which should be handled/treated as non-fatal -// -// Response data is in []byte format and will need to be unmarshalled to the correct data type -func (c *ComponentCDKClient) ReadCacheAsset(key string) ([]byte, error) { - response, err := c.coreClient.ReadCache(context.Background(), &cdk.ReadCacheRequest{ - Key: key, - }) - - if err != nil { - c.logger.Debugf("error reading cache; %s", err.Error()) - return nil, err - } - - if response.Hit { - c.logger.Debug("cache hit", - "type", "data", - ) - return response.Data, nil - } - - c.logger.Debug("cache miss", - "type", "data", - ) - return nil, &CDKCacheMissError{Err: errors.New("cache miss")} -} - -// WriteCacheAsset persists data to the Lacework CLI on-disk cache via the CDK service -// -// Note, data written to the cache is marshalled to JSON first. -// -// If there is an error writing to cache the error is logged but the return will be nil. Errors writing to -// should never be fatal and stop a component. However, if the data supplied cannot be marshalled into JSON -// an actual error will be returned. -func (c *ComponentCDKClient) WriteCacheAsset(key string, expires time.Time, data interface{}) error { - jsonData, err := json.Marshal(data) - if err != nil { - return errors.Wrapf(err, "failed to convert data to be cached") - } - - res, err := c.coreClient.WriteCache(context.Background(), &cdk.WriteCacheRequest{ - Key: key, - Expires: timestamppb.New(expires), - Data: jsonData, - }) - - if res != nil && res.Error { - c.logger.Debugf("error writing to cache; %s", res.Message) - } - - if err != nil { - c.logger.Debugf("error writing to cache; %s", err.Error()) - } - - return nil -} - -// MetricData is used when sending data to Honeycomb -// -// For convience, use the `Metric()` method on the ComponentCDKClient instead of this struct directly -type MetricData struct { - // Feature name in Honeycomb - Feature string - - // Feature data in Honeycomb - FeatureData map[string]string - - // Duration for this span (each MetricData is a unique span) - Duration int64 - - client *ComponentCDKClient -} - -// WithDuration attaches a duration to the given span that will be created in Honeycomb (which is optional) -func (m *MetricData) WithDuration(duration int64) *MetricData { - m.Duration = duration - return m -} - -// Send Writes the MetricData to Honeycomb -func (m *MetricData) Send() error { - return m.client.sendMetricData(m) -} - -// Metric is used to create a new MetricData struct that can be augmented and ultimately sent -// -// now := time.Now() -// c, _ := NewCDKClient("0.0.1") -// _ = c.Metric("example", map[string]string{"example": "data"}).Send() -// _ = c.Metric("example2", map[string]string{"example2": "data"}). -// WithDuration(time.Since(now).Milliseconds()). -// Send() -func (c *ComponentCDKClient) Metric(feature string, featureData map[string]string) *MetricData { - return &MetricData{Feature: feature, FeatureData: featureData, client: c} -} - -func (c *ComponentCDKClient) sendMetricData(data *MetricData) error { - data.FeatureData["version"] = c.componentVersion - - request := &cdk.HoneyventRequest{ - Feature: data.Feature, FeatureData: data.FeatureData, - } - - if data.Duration != 0 { - request.DurationMs = data.Duration - } - - _, err := c.coreClient.Honeyvent(context.Background(), request) - if err != nil { - c.logger.Warn("unable to send telemetry", - "type", "data", "error", err.Error(), - ) - } - - return nil -} - -// MetricError is used to write an error to Honeycomb -func (c *ComponentCDKClient) MetricError(e error) { - _, err := c.coreClient.Honeyvent(context.Background(), &cdk.HoneyventRequest{ - Error: e.Error(), - }) - if err != nil { - c.logger.Warn("unable to send telemetry", - "type", "error", "error", err.Error(), - ) - } -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk.pb.go b/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk.pb.go deleted file mode 100644 index 4ab3a18e6..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk.pb.go +++ /dev/null @@ -1,708 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc v3.12.4 -// source: proto/v1/cdk.proto - -package cdk - -import ( - reflect "reflect" - sync "sync" - - timestamp "github.com/golang/protobuf/ptypes/timestamp" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type PingRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ComponentName string `protobuf:"bytes,1,opt,name=component_name,json=componentName,proto3" json:"component_name,omitempty"` -} - -func (x *PingRequest) Reset() { - *x = PingRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_v1_cdk_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PingRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PingRequest) ProtoMessage() {} - -func (x *PingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_cdk_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. -func (*PingRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_cdk_proto_rawDescGZIP(), []int{0} -} - -func (x *PingRequest) GetComponentName() string { - if x != nil { - return x.ComponentName - } - return "" -} - -type PongReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` -} - -func (x *PongReply) Reset() { - *x = PongReply{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_v1_cdk_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PongReply) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PongReply) ProtoMessage() {} - -func (x *PongReply) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_cdk_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PongReply.ProtoReflect.Descriptor instead. -func (*PongReply) Descriptor() ([]byte, []int) { - return file_proto_v1_cdk_proto_rawDescGZIP(), []int{1} -} - -func (x *PongReply) GetMessage() string { - if x != nil { - return x.Message - } - return "" -} - -// Reply is a generic reply for for rpc definitions that only requires -// acknowledgement of the remote procedure -type Reply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *Reply) Reset() { - *x = Reply{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_v1_cdk_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Reply) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Reply) ProtoMessage() {} - -func (x *Reply) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_cdk_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Reply.ProtoReflect.Descriptor instead. -func (*Reply) Descriptor() ([]byte, []int) { - return file_proto_v1_cdk_proto_rawDescGZIP(), []int{2} -} - -type HoneyventRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Feature string `protobuf:"bytes,1,opt,name=feature,proto3" json:"feature,omitempty"` - FeatureData map[string]string `protobuf:"bytes,2,rep,name=feature_data,json=featureData,proto3" json:"feature_data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` - DurationMs int64 `protobuf:"varint,4,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"` -} - -func (x *HoneyventRequest) Reset() { - *x = HoneyventRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_v1_cdk_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HoneyventRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HoneyventRequest) ProtoMessage() {} - -func (x *HoneyventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_cdk_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HoneyventRequest.ProtoReflect.Descriptor instead. -func (*HoneyventRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_cdk_proto_rawDescGZIP(), []int{3} -} - -func (x *HoneyventRequest) GetFeature() string { - if x != nil { - return x.Feature - } - return "" -} - -func (x *HoneyventRequest) GetFeatureData() map[string]string { - if x != nil { - return x.FeatureData - } - return nil -} - -func (x *HoneyventRequest) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -func (x *HoneyventRequest) GetDurationMs() int64 { - if x != nil { - return x.DurationMs - } - return 0 -} - -// WriteCacheRequest is used to submit data that should be written to cache, included its expiry -type WriteCacheRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // name of the cache key to write - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` - Expires *timestamp.Timestamp `protobuf:"bytes,3,opt,name=expires,proto3" json:"expires,omitempty"` -} - -func (x *WriteCacheRequest) Reset() { - *x = WriteCacheRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_v1_cdk_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WriteCacheRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WriteCacheRequest) ProtoMessage() {} - -func (x *WriteCacheRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_cdk_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WriteCacheRequest.ProtoReflect.Descriptor instead. -func (*WriteCacheRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_cdk_proto_rawDescGZIP(), []int{4} -} - -func (x *WriteCacheRequest) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - -func (x *WriteCacheRequest) GetData() []byte { - if x != nil { - return x.Data - } - return nil -} - -func (x *WriteCacheRequest) GetExpires() *timestamp.Timestamp { - if x != nil { - return x.Expires - } - return nil -} - -// ReadCacheRequest is used to fetch data from the cache -type ReadCacheRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // name of the cache key to read - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` -} - -func (x *ReadCacheRequest) Reset() { - *x = ReadCacheRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_v1_cdk_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ReadCacheRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReadCacheRequest) ProtoMessage() {} - -func (x *ReadCacheRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_cdk_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReadCacheRequest.ProtoReflect.Descriptor instead. -func (*ReadCacheRequest) Descriptor() ([]byte, []int) { - return file_proto_v1_cdk_proto_rawDescGZIP(), []int{5} -} - -func (x *ReadCacheRequest) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - -// ReadCacheResponse is the response type after a ReadCacheRequest is made -type ReadCacheResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // hit is true when data was found, false otherwise - Hit bool `protobuf:"varint,1,opt,name=hit,proto3" json:"hit,omitempty"` - // empty if cache miss (hit == false) - Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` -} - -func (x *ReadCacheResponse) Reset() { - *x = ReadCacheResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_v1_cdk_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ReadCacheResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReadCacheResponse) ProtoMessage() {} - -func (x *ReadCacheResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_cdk_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReadCacheResponse.ProtoReflect.Descriptor instead. -func (*ReadCacheResponse) Descriptor() ([]byte, []int) { - return file_proto_v1_cdk_proto_rawDescGZIP(), []int{6} -} - -func (x *ReadCacheResponse) GetHit() bool { - if x != nil { - return x.Hit - } - return false -} - -func (x *ReadCacheResponse) GetData() []byte { - if x != nil { - return x.Data - } - return nil -} - -// WriteCacheResult is the response type after a WriteCacheRequest is made -type WriteCacheResult struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Error bool `protobuf:"varint,1,opt,name=error,proto3" json:"error,omitempty"` - // message stores the error body if error == true, otherwise empty - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` -} - -func (x *WriteCacheResult) Reset() { - *x = WriteCacheResult{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_v1_cdk_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WriteCacheResult) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WriteCacheResult) ProtoMessage() {} - -func (x *WriteCacheResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_v1_cdk_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WriteCacheResult.ProtoReflect.Descriptor instead. -func (*WriteCacheResult) Descriptor() ([]byte, []int) { - return file_proto_v1_cdk_proto_rawDescGZIP(), []int{7} -} - -func (x *WriteCacheResult) GetError() bool { - if x != nil { - return x.Error - } - return false -} - -func (x *WriteCacheResult) GetMessage() string { - if x != nil { - return x.Message - } - return "" -} - -var File_proto_v1_cdk_proto protoreflect.FileDescriptor - -var file_proto_v1_cdk_proto_rawDesc = []byte{ - 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x64, 0x6b, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x34, 0x0a, - 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x4e, - 0x61, 0x6d, 0x65, 0x22, 0x25, 0x0a, 0x09, 0x50, 0x6f, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x07, 0x0a, 0x05, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x22, 0xf1, 0x01, 0x0a, 0x10, 0x48, 0x6f, 0x6e, 0x65, 0x79, 0x76, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x12, 0x4c, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x48, 0x6f, 0x6e, 0x65, 0x79, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, - 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6f, 0x0a, 0x11, 0x57, 0x72, 0x69, 0x74, 0x65, - 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, - 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x22, 0x24, 0x0a, 0x10, 0x52, 0x65, 0x61, 0x64, - 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x39, - 0x0a, 0x11, 0x52, 0x65, 0x61, 0x64, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x68, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x03, 0x68, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x42, 0x0a, 0x10, 0x57, 0x72, 0x69, - 0x74, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x0a, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0xf9, 0x01, - 0x0a, 0x04, 0x43, 0x6f, 0x72, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, - 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x6e, - 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x09, 0x48, 0x6f, 0x6e, 0x65, - 0x79, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x48, - 0x6f, 0x6e, 0x65, 0x79, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x0d, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, - 0x12, 0x42, 0x0a, 0x09, 0x52, 0x65, 0x61, 0x64, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x18, 0x2e, - 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x43, 0x61, 0x63, 0x68, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x61, 0x64, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0a, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x63, - 0x68, 0x65, 0x12, 0x19, 0x2e, 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x72, 0x69, 0x74, - 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, - 0x63, 0x64, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x63, 0x68, - 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x6c, 0x61, 0x63, - 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x67, 0x6f, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x63, 0x6c, 0x69, - 0x2f, 0x63, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x64, - 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_proto_v1_cdk_proto_rawDescOnce sync.Once - file_proto_v1_cdk_proto_rawDescData = file_proto_v1_cdk_proto_rawDesc -) - -func file_proto_v1_cdk_proto_rawDescGZIP() []byte { - file_proto_v1_cdk_proto_rawDescOnce.Do(func() { - file_proto_v1_cdk_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_v1_cdk_proto_rawDescData) - }) - return file_proto_v1_cdk_proto_rawDescData -} - -var file_proto_v1_cdk_proto_msgTypes = make([]protoimpl.MessageInfo, 9) -var file_proto_v1_cdk_proto_goTypes = []interface{}{ - (*PingRequest)(nil), // 0: cdk.v1.PingRequest - (*PongReply)(nil), // 1: cdk.v1.PongReply - (*Reply)(nil), // 2: cdk.v1.Reply - (*HoneyventRequest)(nil), // 3: cdk.v1.HoneyventRequest - (*WriteCacheRequest)(nil), // 4: cdk.v1.WriteCacheRequest - (*ReadCacheRequest)(nil), // 5: cdk.v1.ReadCacheRequest - (*ReadCacheResponse)(nil), // 6: cdk.v1.ReadCacheResponse - (*WriteCacheResult)(nil), // 7: cdk.v1.WriteCacheResult - nil, // 8: cdk.v1.HoneyventRequest.FeatureDataEntry - (*timestamp.Timestamp)(nil), // 9: google.protobuf.Timestamp -} -var file_proto_v1_cdk_proto_depIdxs = []int32{ - 8, // 0: cdk.v1.HoneyventRequest.feature_data:type_name -> cdk.v1.HoneyventRequest.FeatureDataEntry - 9, // 1: cdk.v1.WriteCacheRequest.expires:type_name -> google.protobuf.Timestamp - 0, // 2: cdk.v1.Core.Ping:input_type -> cdk.v1.PingRequest - 3, // 3: cdk.v1.Core.Honeyvent:input_type -> cdk.v1.HoneyventRequest - 5, // 4: cdk.v1.Core.ReadCache:input_type -> cdk.v1.ReadCacheRequest - 4, // 5: cdk.v1.Core.WriteCache:input_type -> cdk.v1.WriteCacheRequest - 1, // 6: cdk.v1.Core.Ping:output_type -> cdk.v1.PongReply - 2, // 7: cdk.v1.Core.Honeyvent:output_type -> cdk.v1.Reply - 6, // 8: cdk.v1.Core.ReadCache:output_type -> cdk.v1.ReadCacheResponse - 7, // 9: cdk.v1.Core.WriteCache:output_type -> cdk.v1.WriteCacheResult - 6, // [6:10] is the sub-list for method output_type - 2, // [2:6] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name -} - -func init() { file_proto_v1_cdk_proto_init() } -func file_proto_v1_cdk_proto_init() { - if File_proto_v1_cdk_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_proto_v1_cdk_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_v1_cdk_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PongReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_v1_cdk_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Reply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_v1_cdk_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HoneyventRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_v1_cdk_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WriteCacheRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_v1_cdk_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReadCacheRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_v1_cdk_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReadCacheResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_v1_cdk_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WriteCacheResult); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_proto_v1_cdk_proto_rawDesc, - NumEnums: 0, - NumMessages: 9, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_proto_v1_cdk_proto_goTypes, - DependencyIndexes: file_proto_v1_cdk_proto_depIdxs, - MessageInfos: file_proto_v1_cdk_proto_msgTypes, - }.Build() - File_proto_v1_cdk_proto = out.File - file_proto_v1_cdk_proto_rawDesc = nil - file_proto_v1_cdk_proto_goTypes = nil - file_proto_v1_cdk_proto_depIdxs = nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go b/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go deleted file mode 100644 index 3603a28ac..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cdk/go/proto/v1/cdk_grpc.pb.go +++ /dev/null @@ -1,226 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.12.4 -// source: proto/v1/cdk.proto - -package cdk - -import ( - context "context" - - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -// CoreClient is the client API for Core service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type CoreClient interface { - // Sends a ping -> pong between server and client - // - // Component -> CDK Server - Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongReply, error) - // Sends a Honeyvent - Honeyvent(ctx context.Context, in *HoneyventRequest, opts ...grpc.CallOption) (*Reply, error) - // Read from CLI cache - ReadCache(ctx context.Context, in *ReadCacheRequest, opts ...grpc.CallOption) (*ReadCacheResponse, error) - // Write to CLI cache - WriteCache(ctx context.Context, in *WriteCacheRequest, opts ...grpc.CallOption) (*WriteCacheResult, error) -} - -type coreClient struct { - cc grpc.ClientConnInterface -} - -func NewCoreClient(cc grpc.ClientConnInterface) CoreClient { - return &coreClient{cc} -} - -func (c *coreClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongReply, error) { - out := new(PongReply) - err := c.cc.Invoke(ctx, "/cdk.v1.Core/Ping", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreClient) Honeyvent(ctx context.Context, in *HoneyventRequest, opts ...grpc.CallOption) (*Reply, error) { - out := new(Reply) - err := c.cc.Invoke(ctx, "/cdk.v1.Core/Honeyvent", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreClient) ReadCache(ctx context.Context, in *ReadCacheRequest, opts ...grpc.CallOption) (*ReadCacheResponse, error) { - out := new(ReadCacheResponse) - err := c.cc.Invoke(ctx, "/cdk.v1.Core/ReadCache", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *coreClient) WriteCache(ctx context.Context, in *WriteCacheRequest, opts ...grpc.CallOption) (*WriteCacheResult, error) { - out := new(WriteCacheResult) - err := c.cc.Invoke(ctx, "/cdk.v1.Core/WriteCache", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// CoreServer is the server API for Core service. -// All implementations must embed UnimplementedCoreServer -// for forward compatibility -type CoreServer interface { - // Sends a ping -> pong between server and client - // - // Component -> CDK Server - Ping(context.Context, *PingRequest) (*PongReply, error) - // Sends a Honeyvent - Honeyvent(context.Context, *HoneyventRequest) (*Reply, error) - // Read from CLI cache - ReadCache(context.Context, *ReadCacheRequest) (*ReadCacheResponse, error) - // Write to CLI cache - WriteCache(context.Context, *WriteCacheRequest) (*WriteCacheResult, error) - mustEmbedUnimplementedCoreServer() -} - -// UnimplementedCoreServer must be embedded to have forward compatible implementations. -type UnimplementedCoreServer struct { -} - -func (UnimplementedCoreServer) Ping(context.Context, *PingRequest) (*PongReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") -} -func (UnimplementedCoreServer) Honeyvent(context.Context, *HoneyventRequest) (*Reply, error) { - return nil, status.Errorf(codes.Unimplemented, "method Honeyvent not implemented") -} -func (UnimplementedCoreServer) ReadCache(context.Context, *ReadCacheRequest) (*ReadCacheResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ReadCache not implemented") -} -func (UnimplementedCoreServer) WriteCache(context.Context, *WriteCacheRequest) (*WriteCacheResult, error) { - return nil, status.Errorf(codes.Unimplemented, "method WriteCache not implemented") -} -func (UnimplementedCoreServer) mustEmbedUnimplementedCoreServer() {} - -// UnsafeCoreServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to CoreServer will -// result in compilation errors. -type UnsafeCoreServer interface { - mustEmbedUnimplementedCoreServer() -} - -func RegisterCoreServer(s grpc.ServiceRegistrar, srv CoreServer) { - s.RegisterService(&Core_ServiceDesc, srv) -} - -func _Core_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(PingRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServer).Ping(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/cdk.v1.Core/Ping", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServer).Ping(ctx, req.(*PingRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Core_Honeyvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(HoneyventRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServer).Honeyvent(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/cdk.v1.Core/Honeyvent", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServer).Honeyvent(ctx, req.(*HoneyventRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Core_ReadCache_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ReadCacheRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServer).ReadCache(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/cdk.v1.Core/ReadCache", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServer).ReadCache(ctx, req.(*ReadCacheRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Core_WriteCache_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(WriteCacheRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CoreServer).WriteCache(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/cdk.v1.Core/WriteCache", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CoreServer).WriteCache(ctx, req.(*WriteCacheRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// Core_ServiceDesc is the grpc.ServiceDesc for Core service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var Core_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "cdk.v1.Core", - HandlerType: (*CoreServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Ping", - Handler: _Core_Ping_Handler, - }, - { - MethodName: "Honeyvent", - Handler: _Core_Honeyvent_Handler, - }, - { - MethodName: "ReadCache", - Handler: _Core_ReadCache_Handler, - }, - { - MethodName: "WriteCache", - Handler: _Core_WriteCache_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "proto/v1/cdk.proto", -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/access_token.go b/vendor/github.com/lacework/go-sdk/cli/cmd/access_token.go deleted file mode 100644 index c1e77b673..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/access_token.go +++ /dev/null @@ -1,100 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - // duration of the access token in seconds - durationSeconds int - - // accessTokenCmd represents the access-token command - accessTokenCmd = &cobra.Command{ - Use: "access-token", - Short: "Generate temporary API access tokens", - Long: `Generates a temporary API access token that can be used to access the -Lacework API. The token will be valid for the duration that you specify.`, - Args: cobra.NoArgs, - RunE: generateAccessToken, - } -) - -func init() { - // add the access-token command - rootCmd.AddCommand(accessTokenCmd) - - accessTokenCmd.Flags().IntVarP(&durationSeconds, - "duration_seconds", "d", api.DefaultTokenExpiryTime, - "duration in seconds that the access token should remain valid", - ) -} - -func generateAccessToken(_ *cobra.Command, args []string) error { - var ( - response *api.TokenData - err error - ) - - if durationSeconds == api.DefaultTokenExpiryTime { - response, err = cli.LwApi.GenerateToken() - if err != nil { - return errors.Wrap(err, "unable to generate access token") - } - } else { - // if the duration is different from the default, - // regenerate the lacework api client - client, err := api.NewClient(cli.Account, - api.WithLogLevel(cli.Log.Level().CapitalString()), - api.WithExpirationTime(durationSeconds), - api.WithHeader("User-Agent", fmt.Sprintf("Command-Line/%s", Version)), - ) - if err != nil { - return errors.Wrap(err, "unable to generate api client") - } - - response, err = client.GenerateTokenWithKeys(cli.KeyID, cli.Secret) - if err != nil { - return errors.Wrap(err, "unable to generate access token") - } - } - - // cache new token - err = cli.Cache.Write("token", structToString(response)) - if err != nil { - cli.Log.Warnw("unable to write token in cache", - "feature", "cache", - "error", err.Error(), - ) - } - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - - cli.OutputHuman(response.Token) - cli.OutputHuman("\n") - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/account.go b/vendor/github.com/lacework/go-sdk/cli/cmd/account.go deleted file mode 100644 index f2b25b3e6..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/account.go +++ /dev/null @@ -1,88 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/spf13/cobra" -) - -var ( - // accountCmd represents the account command - accountCmd = &cobra.Command{ - Use: "account", - Aliases: []string{"accounts", "acc"}, - Short: "Manage accounts in an organization (org admins only)", - Long: `Manage accounts inside your Lacework organization. - -An organization can contain multiple accounts so you can also manage components -such as alerts, resource groups, team members, and audit logs at a more granular -level inside an organization. A team member may have access to multiple accounts -and can easily switch between them. - -To enroll your Lacework account in an organization follow the documentation: - - https://docs.lacework.com/organization-overview - `, - } - - // accountListCmd represents the list command inside the account command - accountListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all accounts", - Long: `List all accounts in your organization.`, - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - cli.StartProgress(" Loading account information ...") - user, err := cli.LwApi.V2.UserProfile.Get() - cli.StopProgress() - if err != nil { - return err - } - - if cli.JSONOutput() { - return cli.OutputJSON(user.Data) - } - - if len(user.Data) == 0 { - return yikes("unable to load account information.") - } - - profile := user.Data[0] - if !profile.OrgAccount { - cli.OutputHuman("Your account is not enrolled in an organization.\n") - return nil - } - - rows := [][]string{{profile.OrgAccountName()}} - for _, acc := range profile.SubAccountNames() { - rows = append(rows, []string{acc}) - } - - cli.OutputHuman(renderSimpleTable([]string{"Accounts"}, rows)) - cli.OutputHuman("\nUse '--subaccount ' to switch any command to a different account.\n") - return nil - }, - } -) - -func init() { - rootCmd.AddCommand(accountCmd) - accountCmd.AddCommand(accountListCmd) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent.go deleted file mode 100644 index 5fa523475..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/agent.go +++ /dev/null @@ -1,395 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "time" - - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - agentCmdState = struct { - TokenUpdateEnable bool - TokenUpdateDisable bool - TokenUpdateName string - TokenUpdateDesc string - InstallForce bool - InstallSshUser string - InstallSshPort int - InstallAgentToken string - InstallTrustHostKey bool - InstallPassword string - InstallIdentityFile string - InstallTagKey string - InstallTag []string - InstallIncludeRegions []string - InstallDryRun bool - InstallProjectId string - InstallMaxParallelism int - InstallBYORole string - InstallSkipCreatInfra bool - InstallForceReinstall bool - InstallServerURL string - InstallAWSProfile string - }{} - - defaultSshIdentityKey = "~/.ssh/id_rsa" - - agentCmd = &cobra.Command{ - Use: "agent", - Short: "Manage Lacework agents", - Long: `Manage agents and agent access tokens in your account. - -To analyze application, host, and user behavior, Lacework uses a lightweight agent, -which securely forwards collected metadata to the Lacework cloud for analysis. The -agent requires minimal system resources and runs on most 64-bit Linux distributions. - -For a complete list of supported operating systems, visit: - - https://docs.lacework.com/supported-operating-systems`, - } - - agentTokenCmd = &cobra.Command{ - Use: "token", - Aliases: []string{"tokens"}, - Short: "Manage agent access tokens", - Long: `Manage agent access tokens in your account. - -Agent tokens should be treated as secret and not published. A token uniquely identifies -a Lacework customer. If you suspect your token has been publicly exposed or compromised, -generate a new token, update the new token on all machines using the old token. When -complete, the old token can safely be disabled without interrupting Lacework services.`, - } - - agentTokenListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all agent access tokens", - Args: cobra.NoArgs, - RunE: listAgentTokens, - } - - agentTokenCreateCmd = &cobra.Command{ - Use: "create [description]", - Short: "Create a new agent access token", - Args: cobra.RangeArgs(1, 2), - RunE: createAgentToken, - } - - agentTokenShowCmd = &cobra.Command{ - Use: "show ", - Short: "Show details about an agent access token", - Args: cobra.ExactArgs(1), - RunE: showAgentToken, - } - - agentTokenUpdateCmd = &cobra.Command{ - Use: "update ", - Short: "Update an agent access token", - Long: `Update an agent access token. - -To update the token name and description: - - lacework agent token update --name dev --description "k8s deployment for dev" - -To disable a token: - - lacework agent token update --disable - -To enable a token: - - lacework agent token update --enable`, - Args: cobra.ExactArgs(1), - RunE: updateAgentToken, - } - - // TODO hidden for now - agentGenerateCmd = &cobra.Command{ - Use: "generate", - Short: "Generate agent deployment scripts", - Long: `TBA`, - Hidden: true, - RunE: func(_ *cobra.Command, _ []string) error { - return nil - }, - } - - agentInstallCmd = &cobra.Command{ - Use: "install <[user@]host[:port]>", - Short: "Install the datacollector agent on a remote host", - Args: cobra.ExactArgs(1), - Long: `For single host installation of the Lacework agent via Secure Shell (SSH). - -When this command is executed without any additional flag, an interactive prompt will be -launched to help gather the necessary authentication information to access the remote host. - -To authenticate to the remote host with a username and password. - - lacework agent install --ssh_username --ssh_password - -To authenticate to the remote host with an identity file instead. - - lacework agent install -i /path/to/your/key - -To provide an agent access token of your choice, use the command 'lacework agent token list', -select a token and pass it to the '--token' flag. - - lacework agent install -i /path/to/your/key --token - -To authenticate to the remote host on a non-standard SSH port use the '--ssh_port' flag or -pass it directly via the argument. - - lacework agent install - -To explicitly specify the server URL that the agent will connect to: - - lacework agent install --server_url https://your.server.url.lacework.net - -To list all active agents in your environment. - - lacework agent list - -NOTE: New agents could take up to an hour to report back to the platform.`, - RunE: installRemoteAgent, - } - - agentAWSInstallCmd = &cobra.Command{ - Use: "aws-install", - Args: cobra.NoArgs, - Short: "Install the datacollector agent on all remote AWS hosts", - } - - agentGCPInstallCmd = &cobra.Command{ - Use: "gcp-install", - Args: cobra.NoArgs, - Short: "Install the datacollector agent on all remote GCE hosts", - } -) - -func init() { - // add the agent command - rootCmd.AddCommand(agentCmd) - - // add the token sub-command to the agent cmd - agentCmd.AddCommand(agentTokenCmd) - agentCmd.AddCommand(agentInstallCmd) - agentCmd.AddCommand(agentGenerateCmd) - agentCmd.AddCommand(agentListCmd) - agentCmd.AddCommand(agentAWSInstallCmd) - agentCmd.AddCommand(agentGCPInstallCmd) - - // add the list sub-command to the 'agent token' cmd - agentTokenCmd.AddCommand(agentTokenListCmd) - agentTokenCmd.AddCommand(agentTokenCreateCmd) - agentTokenCmd.AddCommand(agentTokenShowCmd) - agentTokenCmd.AddCommand(agentTokenUpdateCmd) - - // add sub-commands to the 'agent aws-install' command for different install methods - agentAWSInstallCmd.AddCommand(agentInstallAWSEC2ICCmd) - agentAWSInstallCmd.AddCommand(agentInstallAWSSSHCmd) - agentAWSInstallCmd.AddCommand(agentInstallAWSSSMCmd) - - // add sub-commands to the 'agent gcp-install' command for different install methods - agentGCPInstallCmd.AddCommand(agentInstallGCPOSLCmd) - - // 'agent token update' flags - agentTokenUpdateCmd.Flags().BoolVar(&agentCmdState.TokenUpdateEnable, - "enable", false, "enable agent access token", - ) - agentTokenUpdateCmd.Flags().BoolVar(&agentCmdState.TokenUpdateDisable, - "disable", false, "disable agent access token", - ) - agentTokenUpdateCmd.Flags().StringVar(&agentCmdState.TokenUpdateName, - "name", "", "new agent access token name", - ) - agentTokenUpdateCmd.Flags().StringVar(&agentCmdState.TokenUpdateDesc, - "description", "", "new agent access token description", - ) - - // 'agent install' flags - agentInstallCmd.Flags().StringVarP(&agentCmdState.InstallIdentityFile, - "identity_file", "i", defaultSshIdentityKey, - "identity (private key) for public key authentication", - ) - agentInstallCmd.Flags().StringVar(&agentCmdState.InstallPassword, - "ssh_password", "", "password for authentication", - ) - agentInstallCmd.Flags().StringVar(&agentCmdState.InstallSshUser, - "ssh_username", "", "username to login with", - ) - agentInstallCmd.Flags().IntVar(&agentCmdState.InstallSshPort, - "ssh_port", 22, "port to connect to on the remote host", - ) - agentInstallCmd.Flags().BoolVar(&agentCmdState.InstallForce, - "force", false, "override any pre-installed agent", - ) - agentInstallCmd.Flags().StringVar(&agentCmdState.InstallAgentToken, - "token", "", "agent access token", - ) - agentInstallCmd.Flags().BoolVar(&agentCmdState.InstallTrustHostKey, - "trust_host_key", false, "automatically add host keys to the ~/.ssh/known_hosts file", - ) - agentInstallCmd.Flags().StringVar(&agentCmdState.InstallServerURL, - "server_url", "https://agent.lacework.net", "server URL that agents will talk to, prefixed with `https://`", - ) -} - -func showAgentToken(_ *cobra.Command, args []string) error { - response, err := cli.LwApi.V2.AgentAccessTokens.Get(args[0]) - if err != nil { - return errors.Wrap(err, "unable to get agent access token") - } - - if cli.JSONOutput() { - return cli.OutputJSON(response.Data) - } - - cli.OutputHuman(buildAgentTokenDetailsTable(response.Data)) - return nil -} - -func updateAgentToken(_ *cobra.Command, args []string) error { - if agentCmdState.TokenUpdateEnable && agentCmdState.TokenUpdateDisable { - return errors.New("specify only one --enable or --disable") - } - - // read the current state - response, err := cli.LwApi.V2.AgentAccessTokens.Get(args[0]) - if err != nil { - return errors.Wrap(err, "unable to get agent access token") - } - actual := response.Data - updated := api.AgentAccessTokenRequest{ - TokenAlias: actual.TokenAlias, - Enabled: actual.Enabled, - Props: &api.AgentAccessTokenProps{ - CreatedTime: actual.Props.CreatedTime, - }, - } - - if agentCmdState.TokenUpdateEnable { - updated.Enabled = 1 - } - - if agentCmdState.TokenUpdateDisable { - updated.Enabled = 0 - } - - if agentCmdState.TokenUpdateName != "" { - updated.TokenAlias = agentCmdState.TokenUpdateName - } - - if agentCmdState.TokenUpdateDesc != "" { - updated.Props.Description = agentCmdState.TokenUpdateDesc - } - - response, err = cli.LwApi.V2.AgentAccessTokens.Update(args[0], updated) - if err != nil { - return errors.Wrap(err, "unable to update the agent access token") - } - - if cli.JSONOutput() { - return cli.OutputJSON(response.Data) - } - - cli.OutputHuman(buildAgentTokenDetailsTable(response.Data)) - return nil -} - -func createAgentToken(_ *cobra.Command, args []string) error { - var desc string - if len(args) == 2 { - desc = args[1] - } - - response, err := cli.LwApi.V2.AgentAccessTokens.Create(args[0], desc) - if err != nil { - return errors.Wrap(err, "unable to create agent access token") - } - - if cli.JSONOutput() { - return cli.OutputJSON(response.Data) - } - - cli.OutputHuman(buildAgentTokenDetailsTable(response.Data)) - return nil -} - -func listAgentTokens(_ *cobra.Command, _ []string) error { - response, err := cli.LwApi.V2.AgentAccessTokens.List() - if err != nil { - return errors.Wrap(err, "unable to list agent access token") - } - - if len(response.Data) == 0 { - cli.OutputHuman( - "There are no agent access tokens. Try creating one with 'lacework agent token create%s'\n", - cli.OutputNonDefaultProfileFlag(), - ) - return nil - } - - if cli.JSONOutput() { - return cli.OutputJSON(response.Data) - } - - cli.OutputHuman( - renderSimpleTable( - []string{"Token", "Name", "State"}, - agentTokensToTable(response.Data), - ), - ) - return nil -} - -func agentTokensToTable(tokens []api.AgentAccessToken) [][]string { - out := [][]string{} - for _, token := range tokens { - out = append(out, []string{ - token.AccessToken, - token.TokenAlias, - token.PrettyState(), - }) - } - return out -} - -func buildAgentTokenDetailsTable(token api.AgentAccessToken) string { - return renderOneLineCustomTable("Agent Access Token Details", - renderSimpleTable([]string{}, - [][]string{ - {"TOKEN", token.AccessToken}, - {"NAME", token.TokenAlias}, - {"DESCRIPTION", token.Props.Description}, - {"VERSION", token.Version}, - {"STATE", token.PrettyState()}, - {"CREATED AT", token.Props.CreatedTime.Format(time.RFC3339)}, - }, - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ic.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ic.go deleted file mode 100644 index 100ed910f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ic.go +++ /dev/null @@ -1,205 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "context" - "fmt" - "sync" - - "github.com/aws/aws-sdk-go-v2/config" - "github.com/gammazero/workerpool" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - agentInstallAWSEC2ICCmd = &cobra.Command{ - Use: "ec2ic", - Args: cobra.NoArgs, - Short: "Use EC2InstanceConnect to securely connect to EC2 instances", - RunE: installAWSEC2IC, - Long: `This command installs the agent on all EC2 instances in an AWS account using EC2InstanceConnect. - -To filter by one or more regions: - - lacework agent aws-install ec2ic --include_regions us-west-2,us-east-2 - -To filter by instance tag: - - lacework agent aws-install ec2ic --tag TagName,TagValue - -To filter by instance tag key: - - lacework agent aws-install ec2ic --tag_key TagName - -To explicitly specify the username for all SSH logins: - - lacework agent aws-install ec2ic --ssh_username - -To provide an agent access token of your choice, use the command 'lacework agent token list', -select a token and pass it to the '--token' flag. This flag must be selected if the -'--noninteractive' flag is set. - - lacework agent aws-install ec2ic --token - -To explicitly specify the server URL that the agent will connect to: - - lacework agent aws-install ec2ic --server_url https://your.server.url.lacework.net - -To specify an AWS credential profile other than 'default': - - lacework agent aws-install ec2ic --credential_profile aws-profile-name - -AWS credentials are read from the following environment variables: -- AWS_ACCESS_KEY_ID -- AWS_SECRET_ACCESS_KEY -- AWS_SESSION_TOKEN (optional) -- AWS_REGION (optional) - -This command will only install the agent on hosts that are supported by -EC2InstanceConnect. The supported AMI types are Amazon Linux 2 and Ubuntu -16.04 and later. There may also be a region restriction. - -This command will automatically add hosts with successful connections to -'~/.ssh/known_hosts' unless specified with '--trust_host_key=false'.`, - } -) - -func init() { - // 'agent aws-install ec2ic' flags - agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallTagKey, - "tag_key", "", "only install agents on infra with this tag key set", - ) - agentInstallAWSEC2ICCmd.Flags().StringSliceVar(&agentCmdState.InstallTag, - "tag", []string{}, "only install agents on infra with this tag", - ) - agentInstallAWSEC2ICCmd.Flags().BoolVar(&agentCmdState.InstallTrustHostKey, - "trust_host_key", true, "automatically add host keys to the ~/.ssh/known_hosts file", - ) - agentInstallAWSEC2ICCmd.Flags().StringSliceVarP(&agentCmdState.InstallIncludeRegions, - "include_regions", "r", []string{}, "list of regions to filter on", - ) - agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallSshUser, - "ssh_username", "", "username to login with", - ) - agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallAgentToken, - "token", "", "agent access token", - ) - agentInstallAWSEC2ICCmd.Flags().IntVarP( - &agentCmdState.InstallMaxParallelism, - "max_parallelism", - "n", - 50, - "maximum number of workers executing AWS API calls, set if rate limits are lower or higher than normal", - ) - agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallServerURL, - "server_url", "https://agent.lacework.net", "server URL that agents will talk to, prefixed with `https://`", - ) - agentInstallAWSEC2ICCmd.Flags().StringVar(&agentCmdState.InstallAWSProfile, - "credential_profile", "default", "AWS credential profile to use", - ) -} - -func installAWSEC2IC(_ *cobra.Command, _ []string) error { - token := agentCmdState.InstallAgentToken - if token == "" { - if cli.InteractiveMode() { - // user didn't provide an agent token - cli.Log.Debugw("agent token not provided, asking user to select one now") - var err error - token, err = selectAgentAccessToken() - if err != nil { - return err - } - } else { - return errors.New("user did not provide or interactively select an agent token") - } - } - - runners, err := awsDescribeInstances(true /* filter on SSH support */) - if err != nil { - return err - } - - cfg, err := config.LoadDefaultConfig(context.Background(), - config.WithSharedConfigProfile(agentCmdState.InstallAWSProfile), - ) - if err != nil { - return err - } - - wg := new(sync.WaitGroup) - wp := workerpool.New(agentCmdState.InstallMaxParallelism) - for _, runner := range runners { - wg.Add(1) - - // In order to use `wp.Submit()`, the input func() must not take any arguments. - // Copy the runner info to dedicated variable in the goroutine to prevent race overwrite - runnerCopyWg := new(sync.WaitGroup) - runnerCopyWg.Add(1) - - wp.Submit(func() { - defer wg.Done() - - threadRunner := *runner - runnerCopyWg.Done() - - cli.Log.Debugw("runner info: ", - "user", threadRunner.Runner.User, - "region", threadRunner.Region, - "az", threadRunner.AvailabilityZone, - "instance_id", threadRunner.InstanceID, - "hostname", threadRunner.Runner.Hostname, - "image name", threadRunner.ImageName, - ) - err := threadRunner.SendAndUseIdentityFile(cfg) - if err != nil { - cli.Log.Debugw("ec2ic key send failed", "err", err, "runner", threadRunner.InstanceID) - return - } - - if err := verifyAccessToRemoteHost(&threadRunner.Runner); err != nil { - cli.Log.Debugw("verifyAccessToRemoteHost failed", "err", err, "runner", threadRunner.InstanceID) - return - } - - if alreadyInstalled := isAgentInstalledOnRemoteHost(&threadRunner.Runner); alreadyInstalled != nil { - cli.Log.Debugw("agent already installed on host, skipping", "runner", threadRunner.InstanceID) - return - } - - cmd := fmt.Sprintf("sudo sh -c \"curl -sSL %s | sh -s -- %s -U %s\"", - agentInstallDownloadURL, token, agentCmdState.InstallServerURL, - ) - err = runInstallCommandOnRemoteHost(&threadRunner.Runner, cmd) - if err != nil { - cli.Log.Debugw("runInstallCommandOnRemoteHost failed", "err", err, "runner", threadRunner.InstanceID) - } - if threadRunner != *runner { - cli.Log.Debugw("mutated runner", "thread_runner", threadRunner, "runner", runner) - } - }) - runnerCopyWg.Wait() - } - wg.Wait() - wp.StopWait() - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssh.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssh.go deleted file mode 100644 index ee8e6b040..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssh.go +++ /dev/null @@ -1,212 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sync" - - "github.com/gammazero/workerpool" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - agentInstallAWSSSHCmd = &cobra.Command{ - Use: "ec2ssh", - Args: cobra.NoArgs, - Short: "Use SSH to securely connect to EC2 instances", - Long: `This command installs the agent on all EC2 instances in an AWS account -using SSH. - -To filter by one or more regions: - - lacework agent aws-install ec2ssh --include_regions us-west-2,us-east-2 - -To filter by instance tag: - - lacework agent aws-install ec2ssh --tag TagName,TagValue - -To filter by instance tag key: - - lacework agent aws-install ec2ssh --tag_key TagName - -To provide an existing access token, use the '--token' flag. This flag is required -when running non-interactively ('--noninteractive' flag). The interactive command -'lacework agent token list' can be used to query existing tokens. - - lacework agent aws-install ec2ssh --token - -To explicitly specify the server URL that the agent will connect to: - - lacework agent aws-install ec2ssh --server_url https://your.server.url.lacework.net - -You will need to provide an SSH authentication method. This authentication method -should work for all instances that your tag or region filters select. Instances must -be routable from your local host. - -To authenticate using username and password: - - lacework agent aws-install ec2ssh --ssh_username --ssh_password - -To authenticate using an identity file: - - lacework agent aws-install ec2ssh -i /path/to/your/key - -To specify an AWS credential profile other than 'default': - - lacework agent aws-install ec2ssh --credential_profile aws-profile-name - -The environment should contain AWS credentials in the following variables: -- AWS_ACCESS_KEY_ID -- AWS_SECRET_ACCESS_KEY -- AWS_SESSION_TOKEN (optional), -- AWS_REGION (optional) - -This command will automatically add hosts with successful connections to -'~/.ssh/known_hosts' unless specified with '--trust_host_key=false'.`, - RunE: installAWSSSH, - } -) - -func init() { - // 'agent aws-install ec2ssh' flags - agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallTagKey, - "tag_key", "", "only install agents on infra with this tag key", - ) - agentInstallAWSSSHCmd.Flags().StringSliceVar(&agentCmdState.InstallTag, - "tag", []string{}, "only select instances with this tag", - ) - agentInstallAWSSSHCmd.Flags().StringVarP(&agentCmdState.InstallIdentityFile, - "identity_file", "i", defaultSshIdentityKey, - "identity (private key) for public key authentication", - ) - agentInstallAWSSSHCmd.Flags().BoolVar(&agentCmdState.InstallTrustHostKey, - "trust_host_key", true, "automatically add host keys to the ~/.ssh/known_hosts file", - ) - agentInstallAWSSSHCmd.Flags().StringSliceVarP(&agentCmdState.InstallIncludeRegions, - "include_regions", "r", []string{}, "list of regions to filter on", - ) - agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallPassword, - "ssh_password", "", "password for authentication", - ) - agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallSshUser, - "ssh_username", "", "username to login with", - ) - agentInstallAWSSSHCmd.Flags().IntVar(&agentCmdState.InstallSshPort, - "ssh_port", 22, "port to connect to on the remote host", - ) - agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallAgentToken, - "token", "", "agent access token", - ) - agentInstallAWSSSHCmd.Flags().IntVarP( - &agentCmdState.InstallMaxParallelism, - "max_parallelism", - "n", - 50, - "maximum number of workers executing AWS API calls, set if rate limits are lower or higher than normal", - ) - agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallServerURL, - "server_url", "https://agent.lacework.net", "server URL that agents will talk to, prefixed with `https://`", - ) - agentInstallAWSSSHCmd.Flags().StringVar(&agentCmdState.InstallAWSProfile, - "credential_profile", "default", "AWS credential profile to use", - ) -} - -func installAWSSSH(_ *cobra.Command, args []string) error { - token := agentCmdState.InstallAgentToken - if token == "" { - if cli.InteractiveMode() { - // user didn't provide an agent token - cli.Log.Debugw("agent token not provided, asking user to select one now") - var err error - token, err = selectAgentAccessToken() - if err != nil { - return err - } - } else { - return errors.New("--token flag is required when --noninteractive flag is set") - } - } - - runners, err := awsDescribeInstances(true /* filter on SSH support */) - if err != nil { - return err - } - - wg := new(sync.WaitGroup) - wp := workerpool.New(agentCmdState.InstallMaxParallelism) - for _, runner := range runners { - wg.Add(1) - - // In order to use `wp.Submit()`, the input func() must not take any arguments. - // Copy the runner info to dedicated variable in the goroutine to prevent race overwrite - runnerCopyWg := new(sync.WaitGroup) - runnerCopyWg.Add(1) - - wp.Submit(func() { - defer wg.Done() - - threadRunner := *runner - runnerCopyWg.Done() - cli.Log.Debugw("threadRunner info: ", - "user", threadRunner.Runner.User, - "region", threadRunner.Region, - "az", threadRunner.AvailabilityZone, - "instance_id", threadRunner.InstanceID, - "hostname", threadRunner.Runner.Hostname, - "image name", threadRunner.ImageName, - ) - - err := threadRunner.Runner.UseIdentityFile(agentCmdState.InstallIdentityFile) - if err != nil { - cli.Log.Warnw("unable to use provided identity file", "err", err, "thread_runner", threadRunner.InstanceID) - return - } - - if err := verifyAccessToRemoteHost(&threadRunner.Runner); err != nil { - cli.Log.Debugw("verifyAccessToRemoteHost failed", "err", err, "thread_runner", threadRunner.InstanceID) - return - } - - if alreadyInstalled := isAgentInstalledOnRemoteHost(&threadRunner.Runner); alreadyInstalled != nil { - cli.Log.Debugw("agent already installed on host, skipping", "thread_runner", threadRunner.InstanceID) - return - } - - cmd := fmt.Sprintf("sudo sh -c \"curl -sSL %s | sh -s -- %s -U %s\"", - agentInstallDownloadURL, token, agentCmdState.InstallServerURL, - ) - err = runInstallCommandOnRemoteHost(&threadRunner.Runner, cmd) - if err != nil { - cli.Log.Debugw("runInstallCommandOnRemoteHost failed", "thread_runner", threadRunner.InstanceID) - } - if threadRunner != *runner { - cli.Log.Debugw("mutated runner", "thread_runner", threadRunner, "runner", runner) - } - }) - runnerCopyWg.Wait() - } - - wg.Wait() - wp.StopWait() - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssm.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssm.go deleted file mode 100644 index 3d1804675..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_aws-install_ec2ssm.go +++ /dev/null @@ -1,405 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "context" - "fmt" - "sync" - "sync/atomic" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/iam/types" - "github.com/aws/aws-sdk-go-v2/service/ssm" - ssmtypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" - "github.com/gammazero/workerpool" - "github.com/lacework/go-sdk/lwrunner" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - agentInstallAWSSSMCmd = &cobra.Command{ - Use: "ec2ssm", - Args: cobra.NoArgs, - Short: "Use SSM to securely install the Lacework agent on EC2 instances", - RunE: installAWSSSM, - Long: `This command installs the Lacework agent on all EC2 instances in an AWS account using SSM. - -This command will create a role and instance profile with 'SSMManagedInstanceCore' -attached and associate that instance profile with the target instances. If the target -instances already have associated instance profiles, this command will not change -their state. This command will teardown the IAM role and instance profile before exiting. - -This command authenticates with AWS credentials from well-known locations on the user's -machine. The principal associated with these credentials should have the -'AmazonEC2FullAccess', 'IAMFullAccess' and 'AmazonSSMFullAccess' policies attached. - -Target instances must have the SSM agent installed and running for successful -installation. - -To skip IAM role / instance profile creation and instance profile association: - - lacework agent aws-install ec2ssm --skip_iam_role_creation - -To provide a preexisting IAM role with the 'SSMManagedInstanceCore' policy - - lacework agent aws-install ec2ssm --iam_role_name IAMRoleName - -To filter by one or more regions: - - lacework agent aws-install ec2ssm --include_regions us-west-2,us-east-2 - -To filter by instance tag: - - lacework agent aws-install ec2ssm --tag TagName,TagValue - -To filter by instance tag key: - - lacework agent aws-install ec2ssm --tag_key TagName - -To provide an agent access token of your choice, use the command 'lacework agent token list', -select a token and pass it to the '--token' flag. This flag must be selected if the -'--noninteractive' flag is set. - - lacework agent aws-install ec2ssm --token - -To explicitly specify the server URL that the agent will connect to: - - lacework agent aws-install ec2ssm --server_url https://your.server.url.lacework.net - -To specify an AWS credential profile other than 'default': - - lacework agent aws-install ec2ssm --credential_profile aws-profile-name - -AWS credentials are read from the following environment variables: -- AWS_ACCESS_KEY_ID -- AWS_SECRET_ACCESS_KEY -- AWS_SESSION_TOKEN (optional) -- AWS_REGION`, - } -) - -func init() { - // 'agent aws-install ec2ssm' flags - agentInstallAWSSSMCmd.Flags().StringVar(&agentCmdState.InstallTagKey, - "tag_key", "", "only install agents on infra with this tag key set", - ) - agentInstallAWSSSMCmd.Flags().StringSliceVar(&agentCmdState.InstallTag, - "tag", []string{}, "only install agents on infra with this tag", - ) - agentInstallAWSSSMCmd.Flags().StringSliceVarP(&agentCmdState.InstallIncludeRegions, - "include_regions", "r", []string{}, "list of regions to filter on", - ) - agentInstallAWSSSMCmd.Flags().StringVar(&agentCmdState.InstallAgentToken, - "token", "", "agent access token", - ) - agentInstallAWSSSMCmd.Flags().IntVarP( - &agentCmdState.InstallMaxParallelism, - "max_parallelism", - "n", - 50, - "maximum number of workers executing AWS API calls, set if rate limits are lower or higher than normal", - ) - agentInstallAWSSSMCmd.Flags().StringVar( - &agentCmdState.InstallBYORole, - "iam_role_name", - "", - "IAM role name (not ARN) with SSM policy, if not provided then an ephemeral role will be created", - ) - agentInstallAWSSSMCmd.Flags().BoolVar( - &agentCmdState.InstallSkipCreatInfra, - "skip_iam_role_creation", - false, - "set this flag to skip creating an IAM role and instance profile and associating the instance profile."+ - " Assumes all instances are already setup for SSM", - ) - agentInstallAWSSSMCmd.Flags().BoolVarP( - &agentCmdState.InstallDryRun, - "dry_run", - "d", - false, - "set this flag to print out the target instances and exit", - ) - agentInstallAWSSSMCmd.Flags().BoolVarP( - &agentCmdState.InstallForceReinstall, - "force_reinstall", - "f", - false, - "set this flag to force-reinstall the agent, even if already running on the target instance", - ) - agentInstallAWSSSMCmd.Flags().StringVar(&agentCmdState.InstallServerURL, - "server_url", "https://agent.lacework.net", "server URL that agents will talk to, prefixed with `https://`", - ) - agentInstallAWSSSMCmd.Flags().StringVar(&agentCmdState.InstallAWSProfile, - "credential_profile", "default", "AWS credential profile to use", - ) -} - -func installAWSSSM(_ *cobra.Command, _ []string) error { - token := agentCmdState.InstallAgentToken - if token == "" { - if !cli.InteractiveMode() { - return errors.New("agent token not provided. Use '--token' when running in non interactive mode") - } - // user didn't provide an agent token - cli.Log.Debug("agent token not provided, asking user to select one now") - var err error - token, err = selectAgentAccessToken() - if err != nil { - return err - } - } - - runners, err := awsDescribeInstances(false /* filter on SSH support */) - if err != nil { - return err - } - - if agentCmdState.InstallDryRun { - cli.OutputHuman("Dry run, listing target instances for installation\n") - for _, runner := range runners { - cli.Log.Info(runner) - cli.OutputHuman("target instance %v\n", *runner) - } - cli.OutputHuman("Dry run finished, exiting now.\n") - return nil - } - - cfg, err := config.LoadDefaultConfig( - context.Background(), config.WithSharedConfigProfile(agentCmdState.InstallAWSProfile), - ) - if err != nil { - return err - } - - var role types.Role - var instanceProfile types.InstanceProfile - if !agentCmdState.InstallSkipCreatInfra { - cli.StartProgress("Setting up IAM role and instance profile...") - - var err error - role, instanceProfile, err = setupSSMAccess(cfg, agentCmdState.InstallBYORole, token) - defer func() { - cli.StopProgress() - err := teardownSSMAccess(cfg, role, instanceProfile, agentCmdState.InstallBYORole) // clean up after ourselves - if err != nil { - cli.OutputHuman("got an error %v while tearing down IAM role / infra\n", err) - cli.Log.Debugw("IAM infra info after error", - "role", role, - "instance profile", instanceProfile, - "error", err, - ) - } - }() - if err != nil { - cli.StopProgress() - return err - } - cli.StopProgress() - cli.OutputHuman( - "Created role %s with policy %s and instance profile %s, added role to profile\n", - *role.RoleName, - lwrunner.SSMInstancePolicy, - *instanceProfile.InstanceProfileName, - ) - } - - var successfulCount int32 = 0 - totalCount := len(runners) - cli.StartProgress(fmt.Sprintf("Installing agents on %d total instances...", totalCount)) - defer cli.StopProgress() - - wg := new(sync.WaitGroup) - wp := workerpool.New(agentCmdState.InstallMaxParallelism) - for _, runner := range runners { - wg.Add(1) - - // In order to use `wp.Submit()`, the input func() must not take any arguments. - // Copy the runner info to dedicated variable in the goroutine to prevent race overwrite - runnerCopyWg := new(sync.WaitGroup) - runnerCopyWg.Add(1) - - wp.Submit(func() { - defer wg.Done() - - threadRunner := *runner - runnerCopyWg.Done() - - cli.Log.Debugw("runner info", - "user", threadRunner.Runner.User, - "region", threadRunner.Region, - "az", threadRunner.AvailabilityZone, - "instance_id", threadRunner.InstanceID, - "hostname", threadRunner.Runner.Hostname, - ) - - if !agentCmdState.InstallSkipCreatInfra { - // Attach an instance profile with our new role to the instance - associationID, err := threadRunner.AssociateInstanceProfileWithRunner(cfg, instanceProfile) - if err != nil { - cli.OutputHuman( - "Failed to attach instance profile %s to instance %s with error %v\n", - *instanceProfile.InstanceProfileName, - threadRunner.InstanceID, - err, - ) - return - } - defer func(cfg aws.Config, associationID string) { - cli.Log.Debugw("disassociating instance profile from runner", - "association ID", associationID, - "instance_id", threadRunner.InstanceID, - ) - err := threadRunner.DisassociateInstanceProfileFromRunner(cfg, associationID) - if err != nil { - cli.Log.Debugw("failed to disassociate instance profile from runner", - "association ID", associationID, - "instance_id", threadRunner.InstanceID, - "error", err, - ) - } - }(cfg, associationID) - } - - // Establish SSM Command connection to the runner - - // Check if agent is already installed on the host, skip if yes - // Sleep for up to 7min to wait for instance profile to associate with instance - var ssmError error - var commandOutput ssm.GetCommandInvocationOutput - const maxSleepTime int = 8 - for i := 0; i < maxSleepTime; i++ { - const agentVersionCmd = "sudo sh -c '/var/lib/lacework/datacollector -v'" - commandOutput, ssmError = threadRunner.RunSSMCommandOnRemoteHost(cfg, agentVersionCmd) - if ssmError != nil { - cli.Log.Debugw("error when checking if agent already installed on host, retrying", - "ssmError", ssmError, - "instance_id", threadRunner.InstanceID, - ) - } else if commandOutput.Status == ssmtypes.CommandInvocationStatusCancelled || - commandOutput.Status == ssmtypes.CommandInvocationStatusTimedOut { - cli.Log.Debugw("command did not complete successfully, retrying", - "command output", commandOutput, - "instance_id", threadRunner.InstanceID, - ) - } else if commandOutput.Status == ssmtypes.CommandInvocationStatusSuccess { - if agentCmdState.InstallForceReinstall { - cli.OutputHuman( - "Lacework Agent already installed on instance %s, forcing reinstall\n", - threadRunner.InstanceID, - ) - break - } else { - cli.OutputHuman( - "Lacework Agent already installed on instance %s, skipping\n", - threadRunner.InstanceID, - ) - return - } - } else if commandOutput.Status == ssmtypes.CommandInvocationStatusFailed { - cli.Log.Infow("no agent found on host, proceeding to install", - "command output", commandOutput, - "time slept in minutes", i, - "instance_id", threadRunner.InstanceID, - ) - cli.OutputHuman( - "No agent found on instance %s, proceeding to install\n", - threadRunner.InstanceID, - ) - break - } else { - cli.OutputHuman( - "Unexpected SSM command exit %v, stderr %s, skipping instance %s\n", - commandOutput.ResponseCode, - lwrunner.GetSSMCommandInvocationStdErr(commandOutput), - threadRunner.InstanceID, - ) - return - } - - if i < maxSleepTime-1 { // only sleep when we have a next iteration - cli.OutputHuman( - "Waiting for AWS to associate instance profile with instance %s, sleeping 1min, already slept %d min\n", - threadRunner.InstanceID, - i, - ) - time.Sleep(1 * time.Minute) - } - } - if ssmError != nil { // SSM still erroring after 7min of sleep, skip this host - cli.Log.Warnw("error when checking if agent already installed on host, skipping runner", - "SSM error", ssmError, - "command output", commandOutput, - "instance_id", threadRunner.InstanceID, - ) - cli.OutputHuman( - "Error %v when checking if agent already installed on instance %s, skipping\n", - ssmError, - threadRunner.InstanceID, - ) - return - } - - // Install the agent on the host - // No need to sleep because instance profile already associated - const runInstallCmdTmpl = "sudo sh -c 'curl -sSL %s | sh -s -- %s -U %s'" - runInstallCmd := fmt.Sprintf(runInstallCmdTmpl, agentInstallDownloadURL, token, agentCmdState.InstallServerURL) - commandOutput, err := threadRunner.RunSSMCommandOnRemoteHost(cfg, runInstallCmd) - if err != nil { - cli.OutputHuman( - "Install failed on instance %s with error %v, stdout %s, stderr %s\n", - threadRunner.InstanceID, - err, - lwrunner.GetSSMCommandInvocationStdOut(commandOutput), - lwrunner.GetSSMCommandInvocationStdErr(commandOutput), - ) - } else if commandOutput.Status == ssmtypes.CommandInvocationStatusSuccess { - cli.OutputHuman("Lacework agent installed successfully on host %s\n\n", threadRunner.InstanceID) - cli.OutputHuman(fmtSuccessfulAgentInstallString(lwrunner.GetSSMCommandInvocationStdOut(commandOutput))) - atomic.AddInt32(&successfulCount, 1) - } else { - cli.Log.Warnw("Install command did not return `Success` exit status for this instance", - "instance_id", threadRunner.InstanceID, - "output", commandOutput, - ) - cli.OutputHuman( - "Install command failed with %s exit status, %s stdout, %s stderr for instance %s\n", - commandOutput.Status, - lwrunner.GetSSMCommandInvocationStdOut(commandOutput), - lwrunner.GetSSMCommandInvocationStdErr(commandOutput), - threadRunner.InstanceID, - ) - } - }) - runnerCopyWg.Wait() - } - wg.Wait() - wp.StopWait() - - cli.OutputHuman( - "Completed installing the Lacework Agent on %d out of %d instances.\n", - successfulCount, - totalCount, - ) - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_gcp-install-osl.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_gcp-install-osl.go deleted file mode 100644 index b8e01203b..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_gcp-install-osl.go +++ /dev/null @@ -1,215 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sync" - - "github.com/gammazero/workerpool" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - agentInstallGCPOSLCmd = &cobra.Command{ - Use: "osl", - Args: cobra.ExactArgs(1), - Short: "Use OSLogin to securely connect to GCE instances", - RunE: installGCPOSL, - Long: `This command installs the agent on all GCE instances in a GCP organization using OSLogin. - -The username of the GCP user or service account, in the form ` + "`users/`" + `, is a -required argument. - -This command will attempt to query the GCE metadata server for the current project. If this -command is not run on a GCE instance, pass the project ID as: - - lacework agent gcp-install osl --project_id my-project-id - -To filter by one or more regions: - - lacework agent gcp-install osl --include_regions us-west1,europe-west2 - -To filter by instance metadata: - - lacework agent gcp-install osl --metadata MetadataKey,MetadataValue - -To filter by instance metadata key: - - lacework agent gcp-install osl --metadata_key MetadataKey - -To provide an agent access token of your choice, use the command 'lacework agent token list', -select a token and pass it to the '--token' flag. This flag must be selected if the -'--noninteractive' flag is set. - - lacework agent gcp-install osl --token - -To explicitly specify the server URL that the agent will connect to: - - lacework agent gcp-install osl --server_url https://your.server.url.lacework.net - -GCP credentials are read using the following environment variables: -- GOOGLE_APPLICATION_CREDENTIALS - -This command will automatically add hosts with successful connections to -'~/.ssh/known_hosts' unless specified with '--trust_host_key=false'.`, - } -) - -func init() { - // 'agent gcp-install osl' flags - agentInstallGCPOSLCmd.Flags().StringVar( - &agentCmdState.InstallTagKey, - "metadata_key", - "", - "only install agents on infra with this metadata key set", - ) - agentInstallGCPOSLCmd.Flags().StringSliceVar( - &agentCmdState.InstallTag, - "metadata", - []string{}, - "only install agents on infra with this metadata", - ) - agentInstallGCPOSLCmd.Flags().StringSliceVarP( - &agentCmdState.InstallIncludeRegions, - "include_regions", - "r", - []string{}, - "list of regions to filter on", - ) - agentInstallGCPOSLCmd.Flags().BoolVar( - &agentCmdState.InstallTrustHostKey, - "trust_host_key", - true, - "automatically add host keys to the ~/.ssh/known_hosts file", - ) - agentInstallGCPOSLCmd.Flags().IntVarP( - &agentCmdState.InstallMaxParallelism, - "max_parallelism", - "n", - 50, - "maximum number of workers executing GCP API calls, set if rate limits are lower or higher than normal", - ) - agentInstallGCPOSLCmd.Flags().StringVar( - &agentCmdState.InstallProjectId, - "project_id", - "", - "ID of the GCP project, set if metadata server does not provide", - ) - agentInstallGCPOSLCmd.Flags().StringVar( - &agentCmdState.InstallAgentToken, - "token", - "", - "agent access token", - ) - agentInstallGCPOSLCmd.Flags().StringVar( - &agentCmdState.InstallServerURL, - "server_url", - "https://agent.lacework.net", - "server URL that agents will talk to, prefixed with `https://`", - ) -} - -func installGCPOSL(_ *cobra.Command, args []string) error { - token := agentCmdState.InstallAgentToken - if token == "" { - if cli.InteractiveMode() { - // user didn't provide an agent token - cli.Log.Debugw("agent token not provided, asking user to select one now") - var err error - token, err = selectAgentAccessToken() - if err != nil { - return err - } - } else { - return errors.New("user did not provide or interactively select an agent token") - } - } - - var projectID string - if agentCmdState.InstallProjectId != "" { - projectID = agentCmdState.InstallProjectId // prioritize CLI flag - } else if mdProjID, err := gcpGetProjectIDFromMetadataServer(); mdProjID != "" && err == nil { - projectID = mdProjID // if flag not passed, check the metadata server - } else { - return fmt.Errorf("could not find project ID, no metadata server (%v) and ID not passed as flag", err) - } - - runners, err := gcpDescribeInstancesInProject(args[0], projectID) - if err != nil { - return err - } - - wg := new(sync.WaitGroup) - wp := workerpool.New(agentCmdState.InstallMaxParallelism) - for _, runner := range runners { - wg.Add(1) - - // In order to use `wp.Submit()`, the input func() must not take any arguments. - // Copy the runner info to dedicated variable in the goroutine to prevent race overwrite - runnerCopyWg := new(sync.WaitGroup) - runnerCopyWg.Add(1) - - wp.Submit(func() { - defer wg.Done() - - threadRunner := *runner - runnerCopyWg.Done() - - cli.Log.Debugw("runner info: ", - "user", threadRunner.Runner.User, - "zone", threadRunner.AvailabilityZone, - "instance_id", threadRunner.InstanceID, - "hostname", threadRunner.Runner.Hostname, - ) - err := threadRunner.SendAndUseIdentityFile() - if err != nil { - cli.Log.Debugw("osl key send failed", "err", err, "runner", threadRunner.InstanceID) - return - } - - if err := verifyAccessToRemoteHost(&threadRunner.Runner); err != nil { - cli.Log.Debugw("verifyAccessToRemoteHost failed", "err", err, "runner", threadRunner.InstanceID) - return - } - - if alreadyInstalled := isAgentInstalledOnRemoteHost(&threadRunner.Runner); alreadyInstalled != nil { - cli.Log.Debugw("agent already installed on host, skipping", "runner", threadRunner.InstanceID) - return - } - - cmd := fmt.Sprintf("sudo sh -c \"curl -sSL %s | sh -s -- %s -U %s\"", - agentInstallDownloadURL, token, agentCmdState.InstallServerURL, - ) - err = runInstallCommandOnRemoteHost(&threadRunner.Runner, cmd) - if err != nil { - cli.Log.Debugw("runInstallCommandOnRemoteHost failed", "err", err, "runner", threadRunner.InstanceID) - } - if threadRunner != *runner { - cli.Log.Debugw("mutated runner", "thread_runner", threadRunner, "runner", runner) - } - }) - runnerCopyWg.Wait() - } - wg.Wait() - wp.StopWait() - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_install.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_install.go deleted file mode 100644 index 99ed24955..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_install.go +++ /dev/null @@ -1,384 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "bytes" - "fmt" - "net" - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/crypto/ssh" - - "github.com/lacework/go-sdk/lwrunner" -) - -// Official download url for installing Lacework agents -const agentInstallDownloadURL = "https://packages.lacework.net/install.sh" - -func installRemoteAgent(_ *cobra.Command, args []string) error { - var ( - user = agentCmdState.InstallSshUser - port = agentCmdState.InstallSshPort - host = args[0] - authSet = false - ) - - // verify if the user specified the username via "user@host" - if strings.Contains(host, "@") { - userHost := strings.Split(host, "@") - user = userHost[0] - host = userHost[1] - } - - // verify if the user specified the port via "host:port" - if strings.Contains(host, ":") { - userHost := strings.Split(host, ":") - host = userHost[0] - p, err := strconv.Atoi(userHost[1]) - if err != nil { - return errors.Wrap(err, "invalid port") - } - port = p - } - - cli.Log.Debugw("creating runner", "user", user, "host", host) - runner := lwrunner.New(user, host, verifyHostCallback) - - if runner.Port != port { - cli.Log.Debugw("ssh settings", "port", port) - runner.Port = port - } - - if runner.User == "" { - cli.Log.Debugw("ssh username not set") - user, err := askForUsername() - if err != nil { - return err - } - - runner.User = user - cli.Log.Debugw("ssh settings", "user", runner.User) - } - - if agentCmdState.InstallIdentityFile != defaultSshIdentityKey { - cli.Log.Debugw("ssh settings", "identity_file", agentCmdState.InstallIdentityFile) - err := runner.UseIdentityFile(agentCmdState.InstallIdentityFile) - if err != nil { - return errors.Wrap(err, "unable to use provided identity file") - } - authSet = true - } - - if agentCmdState.InstallPassword != "" { - cli.Log.Debugw("ssh settings", "auth", "password_from_flag") - runner.UsePassword(agentCmdState.InstallPassword) - authSet = true - } - - // if no authentication was set - if !authSet { - // try to use the default identity file - identityFile, err := lwrunner.DefaultIdentityFilePath() - if err != nil { - return err - } - - err = runner.UseIdentityFile(identityFile) - if err != nil { - cli.Log.Debugw("unable to use default identity file", "error", err) - - // if the default identity file didn't work, ask the user for auth details - cli.Log.Debugw("ssh auth settings not configured") - if err := askForAuthenticationDetails(runner); err != nil { - return err - } - } - } - - if err := verifyAccessToRemoteHost(runner); err != nil { - return err - } - - if err := isAgentInstalledOnRemoteHost(runner); err != nil { - return err - } - - token := agentCmdState.InstallAgentToken - if token == "" { - // user didn't provide an agent token - cli.Log.Debugw("agent token not provided") - var err error - token, err = selectAgentAccessToken() - if err != nil { - return err - } - } - cmd := fmt.Sprintf("sudo sh -c \"curl -sSL %s | sh -s -- %s -U %s\"", - agentInstallDownloadURL, token, agentCmdState.InstallServerURL) - return runInstallCommandOnRemoteHost(runner, cmd) -} - -func runInstallCommandOnRemoteHost(runner *lwrunner.Runner, cmd string) error { - cli.StartProgress(" Installing agent on the remote host...") - cli.Log.Debugw("exec remote command", "cmd", cmd) - stdout, stderr, err := runner.Exec(cmd) - cli.StopProgress() - cli.Log.Debugw("remote command results", - "cmd", cmd, - "stdout", stdout.String(), - "stderr", stderr.String(), - "error", err, - ) - if err != nil { - return errors.Wrap(formatRunnerError(stdout, stderr, err), "unable to install agent on the remote host") - } - - cli.OutputHuman("Lacework agent installed successfully on host %s\n\n", runner.Hostname) - cli.OutputHuman(fmtSuccessfulAgentInstallString(stdout.String())) - return nil -} - -func fmtSuccessfulAgentInstallString(stdout string) string { - return renderOneLineCustomTable("Installation Details", stdout, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - })) -} - -func isAgentInstalledOnRemoteHost(runner *lwrunner.Runner) error { - agentVersionCmd := "sudo sh -c \"/var/lib/lacework/datacollector -v\"" - - cli.StartProgress(" Verifying previous agent installations...") - cli.Log.Debugw("exec remote command", "cmd", agentVersionCmd) - stdout, stderr, err := runner.Exec(agentVersionCmd) - cli.StopProgress() - cli.Log.Debugw("remote command results", "cmd", agentVersionCmd, - "stdout", stdout.String(), - "stderr", stderr.String(), - "error", err, - ) - - if err != nil { - // if we couldn't run the agent version command it means that - // the agent is not yet installed, so we return nil to continue - // with the agent installation process - return nil - } - - if agentCmdState.InstallForce { - cli.Log.Debugw("forcing previous agent installation on remote host") - return nil - } - - return errors.Errorf("agent already installed on the remote host. %s", stderr.String()) -} - -func verifyAccessToRemoteHost(runner *lwrunner.Runner) error { - accessCmd := "echo we-are-in" - - cli.StartProgress(" Verifying access to the remote host...") - cli.Log.Debugw("exec remote command", "cmd", accessCmd) - stdout, stderr, err := runner.Exec(accessCmd) - cli.StopProgress() - cli.Log.Debugw("remote command results", "cmd", accessCmd, - "stdout", stdout.String(), - "stderr", stderr.String(), - "error", err, - ) - - if err != nil || !strings.Contains(stdout.String(), "we-are-in") { - return errors.Wrap(formatRunnerError(stdout, stderr, err), "unable to connect to the remote host") - } - - return nil -} - -func selectAgentAccessToken() (string, error) { - cli.StartProgress(" Searching for agent access tokens...") - response, err := cli.LwApi.V2.AgentAccessTokens.List() - cli.StopProgress() - if err != nil { - return "", errors.Wrap(err, "unable to list agent access token") - } - - var ( - tokenNames = make([]string, 0) - tokenName = "" - ) - for _, aTkn := range response.Data { - // only display tokens that have a name (a.k.a Alias) - if strings.TrimSpace(aTkn.TokenAlias) != "" { - tokenNames = append(tokenNames, aTkn.TokenAlias) - } - } - - err = survey.AskOne(&survey.Select{ - Message: "Choose an agent access token: ", - Options: tokenNames, - }, &tokenName, survey.WithValidator(survey.Required)) - if err != nil { - return "", errors.Wrap(err, "unable to ask for agent access token") - } - for _, aTkn := range response.Data { - if tokenName == aTkn.TokenAlias { - return aTkn.AccessToken, nil - } - } - - // @afiune this should never happen - return "", errors.New("something went pretty wrong here, contact support@lacework.net") -} - -// ask for the ssh username -func askForUsername() (string, error) { - var user string - - err := survey.AskOne(&survey.Input{ - Message: "SSH username:", - }, &user, survey.WithValidator(survey.Required)) - if err != nil { - return "", errors.Wrap(err, "unable to ask for username") - } - - return user, nil -} - -func verifyHostCallback(host string, remote net.Addr, key ssh.PublicKey) error { - // error if key does not exist inside the default known_hosts file, - // or if host in known_hosts file but key changed! - hostFound, err := lwrunner.CheckKnownHost(host, remote, key, "") - if hostFound && err != nil { - // the host in known_hosts file was found but key mismatch - return err - } - - // handshake because public key already exists - if hostFound && err == nil { - return nil - } - - if agentCmdState.InstallTrustHostKey { - // the user wants to add the new host to known hosts file automatically - return lwrunner.AddKnownHost(host, remote, key, "") - } - - // ask user to check if he/she trust the host public key - if askIsHostTrusted(host, key) { - // add the new host to known hosts file. - return lwrunner.AddKnownHost(host, remote, key, "") - } - - // non trusted key - return errors.New("you typed no, the agent installation was aborted!") -} - -// ask user to check if he/she trust the host public key -func askIsHostTrusted(host string, key ssh.PublicKey) bool { - // about to ask a question to the user - cli.StopProgress() - - var ( - trust = false - question = fmt.Sprintf( - "Unknown Host: %s\nFingerprint: %s\nWould you like to continue connecting?", - host, ssh.FingerprintSHA256(key), - ) - err = survey.AskOne(&survey.Confirm{ - Message: question, - Help: "By typing 'yes', the host will be added to the $HOME/.ssh/known_hosts file.", - }, &trust) - ) - if err != nil { - cli.Log.Debugw("unable to ask if host is trusted", "error", err) - return false - } - return trust -} - -func askForAuthenticationDetails(runner *lwrunner.Runner) error { - authMethod := "" - err := survey.AskOne(&survey.Select{ - Message: "Choose SSH authentication method: ", - Options: []string{"Identity File", "Password"}, - }, &authMethod, survey.WithValidator(survey.Required)) - if err != nil { - return errors.Wrap(err, "unable to ask for authentication method") - } - switch authMethod { - case "Password": - // ask for a password - var password string - err = survey.AskOne(&survey.Password{ - Message: "SSH password:", - }, &password, survey.WithValidator(survey.Required)) - if err != nil { - return errors.Wrap(err, "unable to ask for password") - } - - runner.UsePassword(password) - cli.Log.Debugw("ssh settings", "auth", "password_from_input") - default: - // ask for an identity file - var identityFile string - err = survey.AskOne(&survey.Input{ - Message: "SSH identity file:", - }, &identityFile, survey.WithValidator(survey.Required)) - if err != nil { - return errors.Wrap(err, "unable to ask for identity file") - } - - err = runner.UseIdentityFile(identityFile) - if err != nil { - return errors.Wrap(err, "unable to use provided identity file") - } - cli.Log.Debugw("ssh settings", "identity_file", identityFile) - } - - return nil -} - -func formatRunnerError(stdout, stderr bytes.Buffer, err error) error { - formatted := "" - - if stdout.String() != "" { - formatted = fmt.Sprintf("%s\n\nSTDOUT:\n%s", formatted, stdout.String()) - } - - if stderr.String() != "" { - formatted = fmt.Sprintf("%s\n\nSTDERR:\n%s", formatted, stderr.String()) - } - - if formatted == "" { - return err - } - - if err == nil { - return errors.New(formatted) - } - - return errors.Wrap(err, formatted) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_list.go b/vendor/github.com/lacework/go-sdk/cli/cmd/agent_list.go deleted file mode 100644 index 218301267..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/agent_list.go +++ /dev/null @@ -1,241 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sort" - "strings" - "time" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - agentListCmdState = struct { - // The available filters for the agent list command - AvailableFilters CmdFilters - - // List of filters to apply to the agent list command - Filters []string - }{} - - agentListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all hosts with a running agent", - Long: `List all hosts that have a running agent in your environment. - -You can use 'key:value' pairs to filter the list of hosts with the --filter flag. - - lacework agent list --filter 'os:Linux' --filter 'tags.VpcId:vpc-72225916' - -**NOTE:** The value can be a regular expression such as 'hostname:db-server.*' - -To filter hosts with a running agent version '5.8.0'. - - lacework agent list --filter 'agentVersion:5.8.0.*' --filter 'status:ACTIVE' - -The available keys for this command are: -` + stringSliceToMarkdownList( - agentListCmdState.AvailableFilters.GetFiltersFrom( - api.AgentInfo{}, - ), - ), - PreRunE: func(_ *cobra.Command, _ []string) error { - return validateKeyValuePairs(agentListCmdState.Filters) - }, - RunE: listAgents, - } -) - -func init() { - agentListCmd.Flags().StringSliceVar(&agentListCmdState.Filters, "filter", []string{}, - "filter results by key:value pairs (e.g. 'hostname:db-server.*')", - ) -} - -func listAgents(_ *cobra.Command, _ []string) error { - var ( - progressMsg = "Fetching list of agents" - response = &api.AgentInfoResponse{} - now = time.Now().UTC().Add(-1 * time.Minute) - before = now.AddDate(0, 0, -7) // 7 days from ago - filters = api.SearchFilter{ - TimeFilter: &api.TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - } - ) - - cleanedF := []string{} - if len(agentListCmdState.Filters) != 0 { - filters.Filters = []api.Filter{} - for _, pair := range agentListCmdState.Filters { - - kv := strings.Split(pair, ":") - if len(kv) != 2 || kv[0] == "" || kv[1] == "" { - cli.Log.Warnw("malformed filter, ignoring", - "pair", pair, "expected_format", "key:value", - ) - continue - } - - cleanedF = append(cleanedF, pair) - cli.Log.Infow("adding filter", "key", kv[0], "value", kv[1]) - filters.Filters = append(filters.Filters, api.Filter{ - Field: kv[0], - Expression: cli.lqlOperator, // @afiune we use rlike to allow user to pass regex - Value: kv[1], - }) - } - - if len(cleanedF) != 0 { - progressMsg = fmt.Sprintf( - "%s with filters (%s)", - progressMsg, strings.Join(cleanedF, ", "), - ) - } - - agentListCmdState.Filters = cleanedF - } - - cli.StartProgress(fmt.Sprintf("%s...", progressMsg)) - err := cli.LwApi.V2.AgentInfo.Search(response, filters) - cli.StopProgress() - if err != nil { - if strings.Contains(err.Error(), "Invalid field") { - return errors.Errorf("the provided filter key is invalid.\n\nThe available keys for this command are:\n%s", - stringSliceToMarkdownList(agentListCmdState.AvailableFilters.Filters)) - } - return errors.Wrap(err, "unable to list agents via AgentInfo search") - } - - agents := response.Data - - if response.Paging.Urls.NextPage != "" { - totalPages := response.Paging.TotalRows / response.Paging.Rows - - agents = []api.AgentInfo{} - page := 0 - for { - agents = append(agents, response.Data...) - - cli.StartProgress(fmt.Sprintf("%s [%.0f%%]...", progressMsg, float32(page)/float32(totalPages)*100)) - pageOk, err := cli.LwApi.NextPage(response) - if err == nil && pageOk { - page += 1 - continue - } - break - } - response.ResetPaging() - response.Data = agents - } - - cli.StartProgress("Crunching agent data...") - // Sort agents by last updated time (last time they check-in) - sort.Slice(agents, func(i, j int) bool { - return agents[i].LastUpdate.After(agents[j].LastUpdate) - }) - cli.StopProgress() - - if cli.JSONOutput() { - return cli.OutputJSON(agents) - } - - if len(agents) == 0 { - if len(agentListCmdState.Filters) != 0 { - cli.OutputHuman("No agents found with the provided filter(s).\n") - } else { - cli.OutputHuman( - "There are no agents running in your account.\n\nTry installing one with 'lacework agent install %s'\n", - cli.OutputNonDefaultProfileFlag(), - ) - } - return nil - } - - cli.OutputHuman( - renderSimpleTable( - []string{ - "MID", "Status", "Agent Version", "Hostname", "Name", - "Internal IP", "External IP", "OS Arch", "Last Check-in", "Created Time", - }, - agentInfoToListAgentTable(agents), - ), - ) - - // breadcrumbs - if len(agentListCmdState.Filters) == 0 { - cli.OutputHuman("\nTry adding '--filter status:ACTIVE' to show only active agents.\n") - } else if hasWindowsAgents(agents) && !agentListFiltersContains("os") { - cli.OutputHuman("\nTry adding '--filter os:Windows' to show only windows agents.\n") - } else if !hasWindowsAgents(agents) && !agentListFiltersContains("agentVersion") { - cli.OutputHuman("\nTry adding '--filter \"agentVersion:5.8.0.*\"' to show agents with version '5.8.0'.\n") - } - return nil -} - -func agentInfoToListAgentTable(agents []api.AgentInfo) [][]string { - out := [][]string{} - for _, a := range agents { - out = append(out, []string{ - fmt.Sprintf("%d", a.Mid), - a.Status, - a.AgentVersion, - a.Hostname, - a.Tags.Name, - a.Tags.InternalIP, - a.Tags.ExternalIP, - fmt.Sprintf("%s/%s", a.Tags.Os, a.Tags.Arch), - a.LastUpdate.Format(time.RFC3339), - a.CreatedTime.Format(time.RFC3339), - }) - } - - return out -} - -// agentListFiltersContains returns true if one of filters passed to this function -// matches the filters provided to the 'agent list' command -func agentListFiltersContains(filters ...string) bool { - for _, cmdFilter := range agentListCmdState.Filters { - for _, expectedFilter := range filters { - if strings.Contains(cmdFilter, expectedFilter) { - return true - } - } - } - return false -} - -// hasWindowsAgents returns true if there the user has windows agents -func hasWindowsAgents(agents []api.AgentInfo) bool { - for _, a := range agents { - if a.Os == "Windows" { - return true - } - } - return false -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert.go deleted file mode 100644 index 9ec915001..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert.go +++ /dev/null @@ -1,163 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "net/url" - "os/exec" - "runtime" - "strconv" - - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -type alertCmdStateType struct { - Comment string - End string - Fixable bool - Range string - Reason int - Scope string - Severity string - Status string - Start string - Type string -} - -// hasFilters returns true if certain filters are present -// in the command state. excludes time filters (start, end, range). -func (s alertCmdStateType) hasFilters() bool { - // severity / status / type filters - if s.Severity != "" || s.Status != "" || s.Type != "" { - return true - } - return s.Fixable -} - -var ( - alertCmdState = alertCmdStateType{} - - // alertCmd represents the alert parent command - alertCmd = &cobra.Command{ - Use: "alert", - Aliases: []string{"alerts"}, - Short: "Inspect and manage alerts", - Long: `Inspect and manage alerts. - -Lacework provides real-time alerts that are interactive and manageable. - -Each alert contains various metadata information, such as severity level, type, -status, alert category, and associated tags. - -You can also post a comment to an alert's timeline; or change an alert status -from Open to Closed. - -For more information about alerts, visit: - -https://docs.lacework.com/console/alerts-overview - -To view all alerts in your Lacework account. - - lacework alert ls - -To show an alert. - - lacework alert show - -To close an alert. - - lacework alert close -`, - } - - alertOpenCmd = &cobra.Command{ - Use: "open ", - Short: "Open a specified alert in a web browser", - Long: `Open a specified alert in a web browser.`, - Args: cobra.ExactArgs(1), - RunE: openAlert, - } -) - -func init() { - // add the alert command - rootCmd.AddCommand(alertCmd) - - // add the alert open command - alertCmd.AddCommand(alertOpenCmd) -} - -// Generates a URL similar to: -// -// => https://account.lacework.net/ui/investigation/monitor/AlertInbox/123/details?accountName=subaccount -func alertLinkBuilder(id int) string { // nolint - return alertLinkBuilderWithCLI(cli, id) -} - -func alertLinkBuilderWithCLI(c *cliState, id int) string { - u, err := url.Parse( - fmt.Sprintf( - "https://%s.lacework.net/ui/investigation/monitor/AlertInbox/%d/details", - c.Account, - id, - ), - ) - if err != nil { - return "" - } - - q := u.Query() - q.Set("accountName", c.Account) - if c.Subaccount != "" { - q.Set("accountName", c.Subaccount) - } - if r := q.Encode(); r != "" { - u.RawQuery = r - } - return u.String() -} - -func openAlert(_ *cobra.Command, args []string) error { - cli.Log.Debugw("opening alert", "alert", args[0]) - - id, err := strconv.Atoi(args[0]) - if err != nil { - return errors.New("alert ID must be a number") - } - - url := alertLinkBuilder(id) - - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", url).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() - case "darwin": - err = exec.Command("open", url).Start() - default: - err = fmt.Errorf("unsupported platform\n\nNavigate to %s", url) - } - if err != nil { - return errors.Wrap(err, "unable to open web browser") - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_channel.go deleted file mode 100644 index 48f6f91a9..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_channel.go +++ /dev/null @@ -1,227 +0,0 @@ -package cmd - -import ( - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - alertChannelCommand = &cobra.Command{ - Use: "alert-channel", - Aliases: []string{"alert-channels", "ac"}, - Short: "Manage alert channels", - Long: "Manage alert channels integrations with Lacework", - } - - // alertChannelsListCmd represents the list sub-command inside the alert channels command - alertChannelListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all available alert channel integrations", - Args: cobra.NoArgs, - RunE: alertChannelList, - } - - // alertChannelShowCmd represents the show sub-command inside the alert channel command - alertChannelShowCmd = &cobra.Command{ - Use: "show", - Aliases: []string{"get"}, - Short: "Show a single alert channel integration", - Args: cobra.ExactArgs(1), - RunE: alertChannelShow, - } - - // alertChannelCreateCmd represents the show sub-command inside the alert channels command - alertChannelCreateCmd = &cobra.Command{ - Use: "create", - Short: "Create a new alert channel integration", - Args: cobra.NoArgs, - RunE: alertChannelCreate, - } - - // alertChannelDeleteCmd represents the delete sub-command inside the alert channels command - alertChannelDeleteCmd = &cobra.Command{ - Use: "delete", - Aliases: []string{"rm"}, - Short: "Delete a alert channel integration", - Args: cobra.ExactArgs(1), - RunE: alertChannelDelete, - } -) - -func init() { - // add the alert-channel command - rootCmd.AddCommand(alertChannelCommand) - alertChannelCommand.AddCommand(alertChannelListCmd) - alertChannelCommand.AddCommand(alertChannelShowCmd) - alertChannelCommand.AddCommand(alertChannelCreateCmd) - alertChannelCommand.AddCommand(alertChannelDeleteCmd) -} - -func alertChannelsToTable(alertChannels []api.AlertChannelRaw) [][]string { - var out [][]string - for _, cadata := range alertChannels { - out = append(out, []string{ - cadata.IntgGuid, - cadata.Name, - cadata.Type, - cadata.Status(), - cadata.StateString(), - }) - } - return out -} - -func alertChannelList(_ *cobra.Command, _ []string) error { - alertChannels, err := cli.LwApi.V2.AlertChannels.List() - - if err != nil { - return errors.Wrap(err, "unable to get alert channels") - } - - if cli.JSONOutput() { - return cli.OutputJSON(alertChannels.Data) - } - - if len(alertChannels.Data) == 0 { - cli.OutputHuman("No alert channels found.\n") - return nil - } - - cli.OutputHuman( - renderSimpleTable( - []string{"alert channel GUID", "Name", "Type", "Status", "State"}, - alertChannelsToTable(alertChannels.Data), - ), - ) - return nil -} - -func alertChannelShow(_ *cobra.Command, args []string) error { - var ( - alertChannel api.AlertChannelResponse - out [][]string - ) - cli.StartProgress("Fetching alert channel...") - time.Sleep(time.Second * 3) - err := cli.LwApi.V2.AlertChannels.Get(args[0], &alertChannel) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to retrieve alert channel") - } - - if cli.JSONOutput() { - return cli.OutputJSON(alertChannel.Data) - } - - out = append(out, []string{alertChannel.Data.IntgGuid, - alertChannel.Data.Name, - alertChannel.Data.Type, - alertChannel.Data.Status(), - alertChannel.Data.StateString()}) - - cli.OutputHuman(renderSimpleTable([]string{"Alert Channel GUID", "Name", "Type", "Status", "State"}, out)) - cli.OutputHuman("\n") - cli.OutputHuman(buildDetailsTable(alertChannel.Data)) - return nil -} - -func alertChannelCreate(_ *cobra.Command, _ []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - - err := promptCreateAlertChannel() - if err != nil { - return errors.Wrap(err, "unable to create alert channel") - } - - cli.OutputHuman("The alert channel was created.\n") - return nil -} - -func alertChannelDelete(_ *cobra.Command, args []string) error { - cli.StartProgress(" Deleting alert channel...") - err := cli.LwApi.V2.AlertChannels.Delete(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to delete alert channel") - } - cli.OutputHuman("The alert channel %s was deleted.\n", args[0]) - return nil -} - -func promptCreateAlertChannel() error { - var ( - alertChannel = "" - prompt = &survey.Select{ - Message: "Choose an alert channel type to create: ", - Options: []string{ - "Slack", - "Email", - "Amazon S3", - "Cisco Webex", - "Datadog", - "GCP PubSub", - "Microsoft Teams", - "New Relic Insights", - "Webhook", - "VictorOps", - "Splunk", - "QRadar", - "Service Now", - "PagerDuty", - "Amazon CloudWatch", - "Jira Cloud", - "Jira Server", - }, - } - err = survey.AskOne(prompt, &alertChannel) - ) - if err != nil { - return err - } - - switch alertChannel { - case "Slack": - return createSlackAlertChannelIntegration() - case "Email": - return createEmailAlertChannelIntegration() - case "GCP PubSub": - return createGcpPubSubChannelIntegration() - case "Microsoft Teams": - return createMicrosoftTeamsChannelIntegration() - case "New Relic Insights": - return createNewRelicAlertChannelIntegration() - case "Amazon S3": - return createAwsS3ChannelIntegration() - case "Cisco Webex": - return createCiscoWebexChannelIntegration() - case "Datadog": - return createDatadogIntegration() - case "Webhook": - return createWebhookIntegration() - case "VictorOps": - return createVictorOpsChannelIntegration() - case "Splunk": - return createSplunkIntegration() - case "PagerDuty": - return createPagerDutyAlertChannelIntegration() - case "QRadar": - return createQRadarAlertChannelIntegration() - case "Service Now": - return createServiceNowAlertChannelIntegration() - case "Amazon CloudWatch": - return createAwsCloudWatchAlertChannelIntegration() - case "Jira Cloud": - return createJiraAlertChannelIntegration(api.JiraCloudAlertType) - case "Jira Server": - return createJiraAlertChannelIntegration(api.JiraServerAlertType) - default: - return errors.New("unknown alert channel type") - } -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_close.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_close.go deleted file mode 100644 index 86affdc36..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_close.go +++ /dev/null @@ -1,172 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strconv" - - "github.com/AlecAivazis/survey/v2" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -const ( - ReasonUnset = -1 -) - -var ( - // alertCloseCmd represents the alert close command - alertCloseCmd = &cobra.Command{ - Use: "close ", - Short: "Close an alert", - Long: `Use this command to change the status of an alert to closed. - -The reason for closing the alert must be provided from the following options: - - * 0 - Other - * 1 - False positive - * 2 - Not enough information - * 3 - Malicious and have resolution in place - * 4 - Expected because of routine testing. - -Reasons may be provided inline or via prompt. - -If you choose Other, a comment is required and should contain a brief explanation of why the alert is closed. -Comments may be provided inline or via editor. - -**Note: A closed alert cannot be reopened. You will be prompted to confirm closure of the alert. -This prompt can be bypassed with the --noninteractive flag** -`, - Args: cobra.ExactArgs(1), - RunE: closeAlert, - } -) - -func init() { - alertCmd.AddCommand(alertCloseCmd) - - // reason flag - alertCloseCmd.Flags().IntVarP( - &alertCmdState.Reason, - "reason", "r", ReasonUnset, - "the reason for closing the alert", - ) - - // comment flag - alertCloseCmd.Flags().StringVarP( - &alertCmdState.Comment, - "comment", "c", "", - "a comment to associate with the alert closure", - ) -} - -func inputReason() (reason int, err error) { - if alertCmdState.Reason != ReasonUnset { - reason = alertCmdState.Reason - return - } - - prompt := &survey.Select{ - Message: "Reason:", - Options: api.AlertCloseReasons.GetOrderedReasonStrings(), - } - err = survey.AskOne(prompt, &reason) - - return -} - -func inputComment() (comment string, err error) { - if alertCmdState.Comment != "" { - comment = alertCmdState.Comment - return - } - - prompt := &survey.Editor{ - Message: "Type a comment", - FileName: "alert.comment", - } - err = survey.AskOne(prompt, &comment) - - return -} - -func closeAlert(_ *cobra.Command, args []string) error { - cli.Log.Debugw("closing alert", "alert", args[0]) - - id, err := strconv.Atoi(args[0]) - if err != nil { - return errors.New("alert ID must be a number") - } - - // if comment is not supplied inline - // validate that the alert exists - if alertCmdState.Comment == "" || alertCmdState.Reason == ReasonUnset { - exists, err := cli.LwApi.V2.Alerts.Exists(id) - // if we are very certain the alert doesn't exist - if !exists && err == nil { - return errors.New(fmt.Sprintf("alert %d does not exist", id)) - } - } - - reason, err := inputReason() - if err != nil { - return errors.Wrap(err, "unable to process alert close reason") - } - - comment, err := inputComment() - if err != nil { - return errors.Wrap(err, "unable to process alert close comment") - } - - // ask user to confirm - if !cli.nonInteractive { - var confirm bool - prompt := &survey.Confirm{ - Message: fmt.Sprintf( - "Are you sure you want to close alert %d. Alerts cannot be reopend.", id), - Default: false, - } - err = survey.AskOne(prompt, &confirm) - if err != nil { - return errors.Wrap(err, "unable to confirm alert close attempt") - } - if !confirm { - return nil - } - } - - request := api.AlertCloseRequest{ - AlertID: id, - Reason: reason, - Comment: comment, - } - - cli.StartProgress(" Closing alert...") - _, err = cli.LwApi.V2.Alerts.Close(request) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to close alert") - } - - cli.OutputHuman("Alert %d was successfully closed.\n", id) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_comment.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_comment.go deleted file mode 100644 index c4323d897..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_comment.go +++ /dev/null @@ -1,90 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strconv" - - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // alertCommentCmd represents the alert comment command - alertCommentCmd = &cobra.Command{ - Use: "comment ", - Short: "Add a comment", - Long: `Post a user comment on an alert's timeline . - -Comments may be provided inline or via editor. -`, - Args: cobra.ExactArgs(1), - RunE: commentAlert, - } -) - -func init() { - alertCmd.AddCommand(alertCommentCmd) - - // comment flag - alertCommentCmd.Flags().StringVarP( - &alertCmdState.Comment, - "comment", "c", "", - "a comment to add to the alert", - ) -} - -func commentAlert(_ *cobra.Command, args []string) error { - cli.Log.Debugw("commenting on alert", "alert", args[0]) - - id, err := strconv.Atoi(args[0]) - if err != nil { - return errors.New("alert ID must be a number") - } - - // if comment is not supplied inline - // validate that the alert exists - if alertCmdState.Comment == "" { - exists, err := cli.LwApi.V2.Alerts.Exists(id) - // if we are very certain the alert doesn't exist - if !exists && err == nil { - return errors.New(fmt.Sprintf("alert %d does not exist", id)) - } - } - - comment, err := inputComment() - if err != nil { - return errors.Wrap(err, "unable to process alert comment") - } - - cli.StartProgress(" Adding alert comment...") - commentResponse, err := cli.LwApi.V2.Alerts.Comment(id, comment) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to add alert comment") - } - - if cli.JSONOutput() { - return cli.OutputJSON(commentResponse.Data) - } - - cli.OutputHuman("Comment added successfully.") - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list.go deleted file mode 100644 index ac93fc93a..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list.go +++ /dev/null @@ -1,291 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strconv" - "strings" - "time" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/lwseverity" - "github.com/lacework/go-sdk/lwtime" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // alertListCmd represents the alert list command - alertListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all alerts", - Long: `List all alerts. - -By default, alerts are shown for the last 24 hours. -Use a custom time range by suppling a range flag... - - lacework alert ls --range "last 7 days" - -Or by specifying start and end flags. - - lacework alert ls --start "-7d@d" --end "now" - -Start and end times may be specified in one of the following formats: - A. A relative time specifier - B. RFC3339 date and time - C. Epoch time in milliseconds - -To list open alerts of type "NewViolations" with high or critical severity. - - lacework alert ls --status Open --severity high --type NewViolations -`, - Args: cobra.NoArgs, - PreRunE: func(_ *cobra.Command, args []string) error { - // validate severity - if alertCmdState.Severity != "" && !array.ContainsStr( - api.ValidAlertSeverities, alertCmdState.Severity) { - return errors.Wrap( - errors.New(fmt.Sprintf("the severity (%s) is not valid, use one of (%s)", - alertCmdState.Severity, strings.Join(api.ValidAlertSeverities, ", "))), - "unable to list alerts", - ) - } - // validate status - if alertCmdState.Status != "" && !array.ContainsStr( - api.ValidAlertStatuses, alertCmdState.Status) { - return errors.Wrap( - errors.New(fmt.Sprintf("the status (%s) is not valid, use one of (%s)", - alertCmdState.Status, strings.Join(api.ValidAlertStatuses, ", "))), - "unable to list alerts", - ) - } - return nil - }, - RunE: listAlert, - } -) - -func init() { - alertCmd.AddCommand(alertListCmd) - - // range time flag - alertListCmd.Flags().StringVar( - &alertCmdState.Range, - "range", "", - "natural time range for alerts", - ) - - // start time flag - alertListCmd.Flags().StringVar( - &alertCmdState.Start, - "start", "-24h", - "start time for alerts", - ) - // end time flag - alertListCmd.Flags().StringVar( - &alertCmdState.End, - "end", "now", - "end time for alerts", - ) - - // severity flag - alertListCmd.Flags().StringVar( - &alertCmdState.Severity, - "severity", "", - fmt.Sprintf( - "filter alerts by severity threshold (%s)", - strings.Join(api.ValidAlertSeverities, ", "), - ), - ) - - // status flag - alertListCmd.Flags().StringVar( - &alertCmdState.Status, - "status", "", - fmt.Sprintf( - "filter alerts by status (%s)", - strings.Join(api.ValidAlertStatuses, ", "), - ), - ) - - // type flag - alertListCmd.Flags().StringVar( - &alertCmdState.Type, - "type", "", - "filter alerts by type", - ) - - // fixable - if cli.isRemediateInstalled() { - alertListCmd.Flags().BoolVar( - &alertCmdState.Fixable, - "fixable", false, - "filter alerts by fixability", - ) - } -} - -func alertListTable(alerts api.Alerts) (out [][]string) { - alerts.SortByID() - alerts.SortBySeverity() - - for _, alert := range alerts { - out = append(out, []string{ - strconv.Itoa(alert.ID), - alert.Type, - alert.Name, - alert.Severity, - alert.StartTime, - alert.EndTime, - alert.Status, - }) - } - - return -} - -func renderAlertListTable(alerts api.Alerts) { - cli.OutputHuman( - renderCustomTable( - []string{"Alert ID", "Type", "Name", "Severity", "Start Time", "End Time", "Status"}, - alertListTable(alerts), - tableFunc(func(t *tablewriter.Table) { - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) -} - -func listAlert(_ *cobra.Command, _ []string) error { - cli.Log.Debugw("listing alerts") - - var ( - err error - start time.Time - end time.Time - msg string = "unable to list alerts" - ) - - // use of if/else intentional here based on logic paths for determining start and end time.Time values - // if cli user has specified a range we use ParseNatural which gives us start and end time.Time values - // otherwise we need to convert alertCmdState start and end strings to time.Time values using parseQueryTime - if alertCmdState.Range != "" { - cli.Log.Debugw("retrieving natural time range") - - start, end, err = lwtime.ParseNatural(alertCmdState.Range) - if err != nil { - return errors.Wrap(err, msg) - } - } else { - // parse start - start, err = parseQueryTime(alertCmdState.Start) - if err != nil { - return errors.Wrap(err, msg) - } - // parse end - end, err = parseQueryTime(alertCmdState.End) - if err != nil { - return errors.Wrap(err, msg) - } - } - - filters := []api.Filter{} - - if alertCmdState.Status != "" { - filters = append(filters, api.Filter{ - Expression: "eq", - Field: string(api.AlertsFilterFieldStatus), - Value: alertCmdState.Status, - }) - } - - if alertCmdState.Type != "" { - filters = append(filters, api.Filter{ - Expression: "eq", - Field: string(api.AlertsFilterFieldType), - Value: alertCmdState.Type, - }) - } - - cli.StartProgress( - fmt.Sprintf( - " Fetching alerts in the time range %s - %s...", - start.Format("2006-Jan-2 15:04:05 MST"), - end.Format("2006-Jan-2 15:04:05 MST"), - ), - ) - var searchRequest = api.SearchFilter{ - TimeFilter: &api.TimeFilter{StartTime: &start, EndTime: &end}, - Filters: filters, - } - - listResponse, err := cli.LwApi.V2.Alerts.SearchAll(searchRequest) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, msg) - } - - // filter severity - alerts := api.Alerts{} - for _, alert := range listResponse.Data { - // filter severity if desired - if lwseverity.ShouldFilter(alert.Severity, alertCmdState.Severity) { - continue - } - alerts = append(alerts, alert) - } - - // filter fixable - if alertCmdState.Fixable { - templateIDs, err := getRemediationTemplateIDs() - if err != nil { - return errors.Wrap(err, "unable to filter by alert fixability") - } - alerts = filterFixableAlerts(alerts, templateIDs) - } - - if cli.JSONOutput() { - return cli.OutputJSON(alerts) - } - - if len(alerts) == 0 { - if alertCmdState.hasFilters() { - cli.OutputHuman(fmt.Sprintf("%s %s\n", - "No alerts match the specified filters within the given time range.", - "Try removing filters or expanding the time range.", - )) - return nil - } - cli.OutputHuman("There are no alerts in the specified time range.\n") - return nil - } - renderAlertListTable(alerts) - - // breadcrumb - cli.OutputHuman("\nUse 'lacework alert show ' to see details for a specific alert.\n") - if alertCmdState.Fixable { - cli.OutputHuman("Use 'lacework remediate alert ' to fix a specific alert.\n") - } - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list_fixable.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list_fixable.go deleted file mode 100644 index 3b7787866..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_list_fixable.go +++ /dev/null @@ -1,108 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "regexp" - "strings" - - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" -) - -const remediateComponentName string = "remediate" - -// isRemediateInstalled returns true if the remediate component is installed -func (c *cliState) isRemediateInstalled() bool { - return c.IsComponentInstalled(remediateComponentName) -} - -// getTemplateIdentifiers runs the remediate component to retrieve a list -// of remediation template identifiers -func getRemediationTemplateIDs() ([]string, error) { - remediate, found := cli.LwComponents.GetComponent(remediateComponentName) - if !found { - return []string{}, errors.New("remediate component not found") - } - - // set up environment variables - envs := []string{ - fmt.Sprintf("LW_COMPONENT_NAME=%s", remediateComponentName), - "LW_JSON=true", - "LW_NONINTERACTIVE=true", - } - for _, e := range cli.envs() { - // don't let LW_JSON / LW_NONINTERACTIVE through here - if strings.HasPrefix(e, "LW_JSON=") || strings.HasPrefix(e, "LW_NONINTERACTIVE=") { - continue - } - envs = append(envs, e) - } - stdout, stderr, err := remediate.RunAndReturn([]string{"ls", "templates"}, nil, envs...) - if err != nil { - cli.Log.Debugw("remediate error details", "stderr", stderr) - return []string{}, err - } - - var templates []map[string]interface{} - err = json.Unmarshal([]byte(stdout), &templates) - if err != nil { - return []string{}, err - } - - templateIDs := []string{} - for _, template := range templates { - v, ok := template["id"] - if !ok { - continue - } - s, ok := v.(string) - if !ok { - continue - } - templateIDs = append(templateIDs, s) - } - return templateIDs, nil -} - -// filterFixableAlerts identifies which alerts have corresponding remediation template IDs -// and returns those which don't -func filterFixableAlerts(alerts api.Alerts, templateIDs []string) api.Alerts { - fixableAlerts := api.Alerts{} - for _, alert := range alerts { - if alert.PolicyID == "" { - continue - } - found := false - // Historically alerts did not consistently populate policyID and - // templates were named arbitrarily. - // If and when policies explicitly reference templates we will no longer need - // any inference logic. - for _, id := range templateIDs { - if id == alert.PolicyID { - fixableAlerts = append(fixableAlerts, alert) - found = true - break - } - } - if found { - continue - } - // Another interesting problem that we have is that policyIDs are dynamic - // For instance, on dev7 policy lwcustom-11 is dev7-lwcustom-11 - // On some other environment it might be someother-lwcustom-11 - dynamicIDRE := regexp.MustCompile(`^\w+-\d+$`) - // Iterate through the templates looking for those with dynamic policy IDs - for _, id := range templateIDs { - if dynamicIDRE.MatchString(id) { - // if the policyID of the alert ends with - - // i.e. if dev7-lwcustom-11 endswith -lwcustom-11 - if strings.HasSuffix(alert.PolicyID, fmt.Sprintf("-%s", id)) { - fixableAlerts = append(fixableAlerts, alert) - break - } - } - } - } - return fixableAlerts -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_profiles.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_profiles.go deleted file mode 100644 index 5e1b7472f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_profiles.go +++ /dev/null @@ -1,458 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sort" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "gopkg.in/yaml.v3" - - "github.com/lacework/go-sdk/api" -) - -var ( - // alert-profiles command is used to manage lacework alert profiles - alertProfilesCommand = &cobra.Command{ - Use: "alert-profile", - Aliases: []string{"alert-profiles", "ap"}, - Short: "Manage alert profiles", - Long: `Manage alert profiles to define how your LQL queries get consumed into alerts. - -An alert profile consists of the ID of the new profile, the ID of an existing profile that -the new profile extends, and a list of alert templates.`, - } - - // list command is used to list all lacework alert profiles - alertProfilesListCommand = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all alert profiles", - Long: "List all alert profiles configured in your Lacework account.", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - alertProfiles, err := cli.LwApi.V2.Alert.Profiles.List() - if err != nil { - return errors.Wrap(err, "unable to get alert profiles") - } - if len(alertProfiles.Data) == 0 { - msg := `There are no alert profiles configured in your account. - -To manage alerting, integrate alert profiles using the command: - - lacework alert-profile create - -To integrate alert profiles via the Lacework Console, log in to your account at: - - https://%s.lacework.net - -Then go to Settings > Alert Profiles. -` - cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) - return nil - } - if cli.JSONOutput() { - return cli.OutputJSON(alertProfiles) - } - - var rows [][]string - for _, profile := range alertProfiles.Data { - rows = append(rows, []string{profile.Guid, profile.Extends}) - } - sort.Slice(rows, func(i, j int) bool { - return rows[i][0] < rows[j][0] - }) - - cli.OutputHuman(renderSimpleTable([]string{"ID", "EXTENDS"}, rows)) - return nil - }, - } - // show command is used to retrieve a lacework alert profile by id - alertProfilesShowCommand = &cobra.Command{ - Use: "show ", - Short: "Show an alert profile by ID", - Aliases: []string{"get"}, - Long: "Show a single alert profile by its ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var response api.AlertProfileResponse - err := cli.LwApi.V2.Alert.Profiles.Get(args[0], &response) - if err != nil { - return errors.Wrap(err, "unable to get alert profile") - } - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - - alertProfile := response.Data - var headers [][]string - headers = append(headers, []string{alertProfile.Guid, alertProfile.Extends}) - cli.OutputHuman(renderSimpleTable([]string{"ALERT PROFILE ID", "EXTENDS"}, headers)) - cli.OutputHuman("\n") - cli.OutputHuman(buildAlertProfileDetailsTable(alertProfile)) - - return nil - }, - } - - // delete command is used to remove a lacework alert profile by id - alertProfilesDeleteCommand = &cobra.Command{ - Use: "delete ", - Short: "Delete an alert profile", - Long: "Delete a single alert profile by its ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - err := cli.LwApi.V2.Alert.Profiles.Delete(args[0]) - if err != nil { - return errors.Wrap(err, "unable to delete alert profile") - } - cli.OutputHuman("The alert profile with GUID %s was deleted \n", args[0]) - return nil - }, - } - - // create command is used to create a new lacework alert profile - alertProfilesCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a new alert profile", - RunE: func(_ *cobra.Command, args []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - - response, err := promptCreateAlertProfile() - if err != nil { - return errors.Wrap(err, "unable to create alert profile") - } - - cli.OutputHuman(fmt.Sprintf("The alert profile was created with ID %s \n", response.Data.Guid)) - return nil - }, - } - - // update command is used to update an existing lacework alert profile - alertProfilesUpdateCommand = &cobra.Command{ - Use: "update [alert_profile_id]", - Short: "Update alert templates from an existing alert profile", - Args: cobra.MaximumNArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - response, err := promptUpdateAlertProfile(args) - if err != nil { - return err - } - - cli.OutputHuman("The alert profile %s was updated \n", response.Data.Guid) - return nil - }, - } -) - -func init() { - // add the alert-profile command - rootCmd.AddCommand(alertProfilesCommand) - - // add sub-commands to the alert-profile command - alertProfilesCommand.AddCommand(alertProfilesListCommand) - alertProfilesCommand.AddCommand(alertProfilesShowCommand) - alertProfilesCommand.AddCommand(alertProfilesCreateCommand) - alertProfilesCommand.AddCommand(alertProfilesUpdateCommand) - alertProfilesCommand.AddCommand(alertProfilesDeleteCommand) -} - -func buildAlertProfileDetailsTable(profile api.AlertProfile) string { - var details [][]string - - detailsTable := &strings.Builder{} - - for _, alert := range profile.Alerts { - details = append(details, []string{alert.Name, alert.EventName, alert.Description, alert.Subject}) - } - - detailsTable.WriteString(renderOneLineCustomTable("ALERT TEMPLATES", - renderSimpleTable([]string{"NAME", "EVENT NAME", "DESCRIPTION", "SUBJECT"}, details), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ), - ) - - if len(profile.Fields) > 0 { - var fields []string - for _, f := range profile.Fields { - fields = append(fields, f.Name) - } - - detailsTable.WriteString(renderOneLineCustomTable("FIELDS", "", - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - // format field names into even rows - rowWidth := 10 - var j int - for i := 0; i < len(fields); i += rowWidth { - j += rowWidth - if j > len(fields) { - j = len(fields) - } - t.Append([]string{strings.Join(fields[i:j], ", ")}) - } - }), - )) - detailsTable.WriteString("\nUse a field inside an alert template subject or description by enclosing " + - "it in double brackets. For example: '{{FIELD_NAME}}'\n") - } - - return detailsTable.String() -} - -func promptUpdateAlertProfile(args []string) (api.AlertProfileResponse, error) { - var ( - msg = "unable to update alert profile" - profileID string - err error - ) - if len(args) == 0 { - profileID, err = promptSelectProfile() - if err != nil { - return api.AlertProfileResponse{}, errors.Wrap(err, msg) - } - } else { - profileID = args[0] - } - - var existingProfile api.AlertProfileResponse - cli.StartProgress("Retrieving alert profile...") - err = cli.LwApi.V2.Alert.Profiles.Get(profileID, &existingProfile) - cli.StopProgress() - if err != nil { - return api.AlertProfileResponse{}, errors.Wrap(err, msg) - } - - queryYaml, err := yaml.Marshal(existingProfile.Data.Alerts) - if err != nil { - return api.AlertProfileResponse{}, errors.Wrap(err, msg) - } - - prompt := &survey.Editor{ - Message: fmt.Sprintf("Update alert templates for profile %s", profileID), - Default: string(queryYaml), - HideDefault: true, - AppendDefault: true, - FileName: "templates*.yaml", - } - var templatesString string - err = survey.AskOne(prompt, &templatesString) - if err != nil { - return api.AlertProfileResponse{}, errors.Wrap(err, msg) - } - - var templates []api.AlertTemplate - err = yaml.Unmarshal([]byte(templatesString), &templates) - if err != nil { - return api.AlertProfileResponse{}, errors.Wrap(err, msg) - } - - cli.StartProgress(" Updating alert profile...") - response, err := cli.LwApi.V2.Alert.Profiles.Update(profileID, templates) - cli.StopProgress() - return response, err -} - -func promptSelectProfile() (string, error) { - profileResponse, err := cli.LwApi.V2.Alert.Profiles.List() - if err != nil { - return "", err - } - var profileList = filterAlertProfilesByDefault(profileResponse) - - questions := []*survey.Question{ - { - Name: "profile", - Prompt: &survey.Select{ - Message: "Select an alert profile to update:", - Options: profileList, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Profile string `json:"profile"` - }{} - - err = survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return "", err - } - - return answers.Profile, nil -} - -func promptCreateAlertProfile() (api.AlertProfileResponse, error) { - profileResponse, err := cli.LwApi.V2.Alert.Profiles.List() - if err != nil { - return api.AlertProfileResponse{}, err - } - profileList := filterAlertProfiles(profileResponse) - - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Profile Name: "}, - Validate: survey.Required, - }, - { - Name: "extends", - Prompt: &survey.Select{ - Message: "Select an alert profile to extend:", - Options: profileList, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string `json:"name"` - Extends string `json:"extends"` - }{} - - err = survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return api.AlertProfileResponse{}, err - } - - if strings.HasPrefix(answers.Name, "LW_") { - return api.AlertProfileResponse{}, errors.New("profile name prefix 'LW_' is reserved for Lacework-defined profiles") - } - - var templates []api.AlertTemplate - templates = append(templates, promptAddAlertTemplate()) - addTemplates := false - for { - if err := survey.AskOne(&survey.Confirm{ - Message: "Add another alert template?", - }, &addTemplates); err != nil { - return api.AlertProfileResponse{}, err - } - - if addTemplates { - templates = append(templates, promptAddAlertTemplate()) - } else { - break - } - } - alertProfile := api.NewAlertProfile(answers.Name, answers.Extends, templates) - - cli.StartProgress(" Creating alert profile...") - response, err := cli.LwApi.V2.Alert.Profiles.Create(alertProfile) - - cli.StopProgress() - return response, err -} - -func promptAddAlertTemplate() api.AlertTemplate { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Alert Template Name: "}, - Validate: survey.Required, - }, - { - Name: "eventName", - Prompt: &survey.Input{Message: "Alert Template Event Name: "}, - Validate: survey.Required, - }, - { - Name: "description", - Prompt: &survey.Input{Message: "Alert Template Description: "}, - Validate: survey.Required, - }, - { - Name: "subject", - Prompt: &survey.Input{Message: "Alert Template Subject: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string `json:"name"` - EventName string `json:"eventName"` - Description string `json:"description"` - Subject string `json:"subject"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return api.AlertTemplate{} - } - - return api.AlertTemplate{ - Name: answers.Name, - EventName: answers.EventName, - Description: answers.Description, - Subject: answers.Subject, - } -} - -func filterAlertProfilesByDefault(response api.AlertProfilesResponse) []string { - var profiles = make([]string, 0) - for _, p := range response.Data { - if !strings.HasPrefix(p.Guid, "LW_") && len(p.Alerts) >= 1 { - profiles = append(profiles, p.Guid) - } - } - - sort.Slice(profiles, func(i, j int) bool { - return profiles[i] < profiles[j] - }) - - return profiles -} - -func filterAlertProfiles(response api.AlertProfilesResponse) []string { - var profiles = make([]string, 0) - for _, p := range response.Data { - // profiles can only extend from 'LW_' profiles with >= 1 alert template - if strings.HasPrefix(p.Guid, "LW_") && len(p.Alerts) >= 1 { - profiles = append(profiles, p.Guid) - } - } - - sort.Slice(profiles, func(i, j int) bool { - return profiles[i] < profiles[j] - }) - - return profiles -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_rules.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_rules.go deleted file mode 100644 index 85bf30338..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_rules.go +++ /dev/null @@ -1,378 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strconv" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - // alert-rules command is used to manage lacework alert rules - alertRulesCommand = &cobra.Command{ - Use: "alert-rule", - Aliases: []string{"alert-rules", "ar"}, - Short: "Manage alert rules", - Long: `Manage alert rules to route events to the appropriate people or tools. - -An alert rule has three parts: - - 1. Alert channel(s) that should receive the event notification - 2. Event severity and categories to include - 3. Resource group(s) containing the subset of your environment to consider -`, - } - - // list command is used to list all lacework alert rules - alertRulesListCommand = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all alert rules", - Long: "List all alert rules configured in your Lacework account.", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - alertRules, err := cli.LwApi.V2.AlertRules.List() - if err != nil { - return errors.Wrap(err, "unable to get alert rules") - } - if len(alertRules.Data) == 0 { - msg := `There are no alert rules configured in your account. - -Get started by integrating your alert rules to manage alerting using the command: - - lacework alert-rule create - -If you prefer to configure alert rules via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Settings > Alert Rules. -` - cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) - return nil - } - if cli.JSONOutput() { - return cli.OutputJSON(alertRules) - } - - var rows [][]string - for _, rule := range alertRules.Data { - rows = append(rows, []string{rule.Guid, rule.Filter.Name, rule.Filter.Status()}) - } - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "ENABLED"}, rows)) - return nil - }, - } - // show command is used to retrieve a lacework alert rule by resource id - alertRulesShowCommand = &cobra.Command{ - Use: "show ", - Short: "Show an alert rule by ID", - Long: "Show a single alert rule by it's ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var response api.AlertRuleResponse - err := cli.LwApi.V2.AlertRules.Get(args[0], &response) - if err != nil { - return errors.Wrap(err, "unable to get alert rule") - } - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - - alertRule := response.Data - var headers [][]string - headers = append(headers, []string{alertRule.Guid, alertRule.Filter.Name, alertRule.Filter.Status()}) - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "ENABLED"}, headers)) - cli.OutputHuman("\n") - cli.OutputHuman(buildAlertRuleDetailsTable(alertRule)) - - return nil - }, - } - - // delete command is used to remove a lacework alert rule by resource id - alertRulesDeleteCommand = &cobra.Command{ - Use: "delete ", - Short: "Delete a alert rule", - Long: "Delete a single alert rule by it's ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - err := cli.LwApi.V2.AlertRules.Delete(args[0]) - if err != nil { - return errors.Wrap(err, "unable to delete alert rule") - } - cli.OutputHuman(fmt.Sprintf("The alert rule with GUID %s was deleted \n", args[0])) - return nil - }, - } - - // create command is used to create a new lacework alert rule - alertRulesCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a new alert rule", - RunE: func(_ *cobra.Command, args []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - - response, err := promptCreateAlertRule() - if err != nil { - return errors.Wrap(err, "unable to create alert rule") - } - - cli.OutputHuman(fmt.Sprintf("The alert rule was created with GUID %s \n", response.Data.Guid)) - return nil - }, - } -) - -func init() { - // add the alert-rule command - rootCmd.AddCommand(alertRulesCommand) - - // add sub-commands to the alert-rule command - alertRulesCommand.AddCommand(alertRulesListCommand) - alertRulesCommand.AddCommand(alertRulesShowCommand) - alertRulesCommand.AddCommand(alertRulesCreateCommand) - alertRulesCommand.AddCommand(alertRulesDeleteCommand) -} - -func buildAlertRuleDetailsTable(rule api.AlertRule) string { - var ( - details [][]string - updatedTime string - ) - severities := api.NewAlertRuleSeveritiesFromIntSlice(rule.Filter.Severity).ToStringSlice() - - if nano, err := strconv.ParseInt(rule.Filter.CreatedOrUpdatedTime, 10, 64); err == nil { - updatedTime = time.Unix(nano/1000, 0).Format(time.RFC3339) - } - details = append(details, []string{"SEVERITIES", strings.Join(severities, ", ")}) - details = append(details, []string{"EVENT CATEGORIES", strings.Join(rule.Filter.AlertSubCategories, ", ")}) - details = append(details, []string{"DESCRIPTION", rule.Filter.Description}) - details = append(details, []string{"UPDATED BY", rule.Filter.CreatedOrUpdatedBy}) - details = append(details, []string{"LAST UPDATED", updatedTime}) - - detailsTable := &strings.Builder{} - detailsTable.WriteString(renderOneLineCustomTable("ALERT RULE DETAILS", - renderCustomTable([]string{}, details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ), - ) - - if len(rule.Channels) > 0 { - channels := [][]string{{strings.Join(rule.Channels, "\n")}} - detailsTable.WriteString(renderCustomTable([]string{"ALERT CHANNELS"}, channels, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - ) - detailsTable.WriteString("\n") - } - - if len(rule.Filter.ResourceGroups) > 0 { - resourceGroups := [][]string{{strings.Join(rule.Filter.ResourceGroups, "\n")}} - detailsTable.WriteString(renderCustomTable([]string{"RESOURCE GROUPS"}, resourceGroups, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - ) - } - - return detailsTable.String() -} - -func promptCreateAlertRule() (api.AlertRuleResponse, error) { - channelList, channelMap := getAlertChannels() - - if len(channelList) < 1 { - return api.AlertRuleResponse{}, errors.New("no Alert Channels found.") - } - - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "description", - Prompt: &survey.Input{Message: "Description: "}, - Validate: survey.Required, - }, - { - Name: "channels", - Prompt: &survey.MultiSelect{ - Message: "Select alert channels:", - Options: channelList, - }, - Validate: survey.Required, - }, - { - Name: "severities", - Prompt: &survey.MultiSelect{ - Message: "Select severities:", - Options: []string{"Critical", "High", "Medium", "Low", "Info"}, - }, - }, - { - Name: "eventCategories", - Prompt: &survey.MultiSelect{ - Message: "Select event categories:", - Options: []string{"Compliance", "App", "Cloud", "File", "Machine", "User", "Platform"}, - }, - }, - } - - answers := struct { - Name string - Description string `survey:"description"` - Channels []string `survey:"channels"` - Severities []string `survey:"severities"` - EventCategories []string `survey:"eventCategories"` - ResourceGroups []string `survey:"resourceGroups"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return api.AlertRuleResponse{}, err - } - - var channels []string - for _, channel := range answers.Channels { - channels = append(channels, channelMap[channel]) - } - - resourceGroups, resourceGroupMap := promptAddResourceGroupsToAlertRule() - groups := make([]string, 0) - for _, group := range resourceGroups { - groups = append(groups, resourceGroupMap[group]) - } - - alertCategories := make([]string, 0) - - alertRule := api.NewAlertRule( - answers.Name, - api.AlertRuleConfig{ - Description: answers.Description, - Channels: channels, - Severities: api.NewAlertRuleSeverities(answers.Severities), - AlertSubCategories: answers.EventCategories, - AlertCategories: alertCategories, - ResourceGroups: groups, - }) - - cli.StartProgress(" Creating alert rule...") - response, err := cli.LwApi.V2.AlertRules.Create(alertRule) - - cli.StopProgress() - return response, err -} - -func getAlertChannels() ([]string, map[string]string) { - response, err := cli.LwApi.V2.AlertChannels.List() - - if err != nil { - return nil, nil - } - var items = make(map[string]string) - var channels = make([]string, 0) - for _, i := range response.Data { - displayName := fmt.Sprintf("%s - %s", i.ID(), i.Name) - channels = append(channels, displayName) - items[displayName] = i.ID() - } - - return channels, items -} - -func getResourceGroups() ([]string, map[string]string) { - cli.StartProgress("") - defer cli.StopProgress() - response, err := cli.LwApi.V2.ResourceGroups.List() - - if err != nil { - return nil, nil - } - var items = make(map[string]string) - var groups = make([]string, 0) - - for _, i := range response.Data { - displayName := fmt.Sprintf("%s - %s", i.ID(), i.Name) - groups = append(groups, displayName) - items[displayName] = i.ID() - } - - return groups, items -} - -func promptAddResourceGroupsToAlertRule() ([]string, map[string]string) { - addResourceGroups := false - err := survey.AskOne(&survey.Confirm{ - Message: "Add Resource Groups to Alert Rule?", - }, &addResourceGroups) - - if err != nil { - return nil, nil - } - - if addResourceGroups { - var groups []string - groupList, groupMap := getResourceGroups() - - err = survey.AskOne(&survey.MultiSelect{ - Message: "Select Resource Groups:", - Options: groupList, - }, &groups) - - if err != nil { - return nil, nil - } - return groups, groupMap - } - return nil, nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show.go deleted file mode 100644 index 8d780fb01..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show.go +++ /dev/null @@ -1,109 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strconv" - - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // alertShowCmd represents the alert list command - alertShowCmd = &cobra.Command{ - Use: "show ", - Short: "Show details about a specific alert", - Long: `Show details about a specific alert. - -There are different types of alert details that can be shown to assist -with alert investigation. These types are referred to as alert detail scopes. - -The following alert detail scopes are available: - - * Details (default) - * Investigation - * Events - * RelatedAlerts - * Integrations - * Timeline - -View an alert's timeline details: - - lacework alert show --scope Timeline -`, - Args: cobra.ExactArgs(1), - RunE: showAlert, - } -) - -func init() { - // show command - alertCmd.AddCommand(alertShowCmd) - - // scope flag - alertShowCmd.Flags().StringVar( - &alertCmdState.Scope, - "scope", api.AlertDetailsScope.String(), - "type of alert details to show", - ) -} - -func showAlert(_ *cobra.Command, args []string) error { - cli.Log.Debugw("showing alert", "alert", args[0]) - - id, err := strconv.Atoi(args[0]) - if err != nil { - return errors.New("alert ID must be a number") - } - - switch alertCmdState.Scope { - case api.AlertDetailsScope.String(): - err = showAlertDetails(id) - case api.AlertInvestigationScope.String(): - err = showAlertInvestigation(id) - case api.AlertEventsScope.String(): - err = showAlertEvents(id) - case api.AlertRelatedAlertsScope.String(): - err = showRelatedAlerts(id) - case api.AlertIntegrationsScope.String(): - err = showAlertIntegrations(id) - case api.AlertTimelineScope.String(): - err = showAlertTimeline(id) - default: - err = errors.New(fmt.Sprintf("scope (%s) is not recognized", alertCmdState.Scope)) - } - if err != nil { - return err - } - - // breadcrumb - if !cli.JSONOutput() { - url := alertLinkBuilder(id) - cli.OutputHuman( - fmt.Sprintf( - "\nFor further investigation of this alert navigate to %s\n", - url, - ), - ) - } - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_details.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_details.go deleted file mode 100644 index 2e8b8f00f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_details.go +++ /dev/null @@ -1,107 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/lacework/go-sdk/api" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" -) - -func alertDetailsTable(alert api.Alert) ( - category, description, subject, entity [][]string, -) { - category = append(category, []string{ - alert.DerivedFields.Source, - alert.DerivedFields.Category, - alert.DerivedFields.SubCategory, - alert.PolicyID, - }) - - description = append(description, []string{ - alert.Info.Description, - }) - - subject = append(subject, []string{ - alert.Info.Subject, - }) - - entity = append(entity, []string{ - fmt.Sprintf( - "Entity map coming soon. Use 'lacework alert show %d --json' for full details.", - alert.ID, - ), - }) - - return -} - -func renderAlertDetailsTable(alert api.Alert) { - cat, desc, subj, entity := alertDetailsTable(alert) - cli.OutputHuman( - renderCustomTable( - []string{"Source", "Category", "Sub Category", "Policy ID"}, - cat, - tableFunc(func(t *tablewriter.Table) { - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) - cli.OutputHuman("\n") - cli.OutputHuman( - renderCustomTable( - []string{"Description"}, - desc, - tableFunc(func(t *tablewriter.Table) { - t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) - cli.OutputHuman("\n") - cli.OutputHuman( - renderCustomTable( - []string{"Subject"}, - subj, - tableFunc(func(t *tablewriter.Table) { - t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) - cli.OutputHuman("\n") - cli.OutputHuman( - renderCustomTable( - []string{"Entities"}, - entity, - tableFunc(func(t *tablewriter.Table) { - t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) -} - -func showAlertDetails(id int) error { - cli.StartProgress(" Fetching alert details...") - details, err := cli.LwApi.V2.Alerts.GetDetails(id) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to show alert") - } - - if cli.JSONOutput() { - return cli.OutputJSON(details.Data) - } - - alerts := api.Alerts{details.Data.Alert} - renderAlertListTable(alerts) - cli.OutputHuman("\n") - renderAlertDetailsTable(details.Data.Alert) - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_events.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_events.go deleted file mode 100644 index cf2731db7..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_events.go +++ /dev/null @@ -1,16 +0,0 @@ -package cmd - -import ( - "github.com/pkg/errors" -) - -func showAlertEvents(id int) error { - cli.StartProgress(" Fetching alert events...") - eventsResponse, err := cli.LwApi.V2.Alerts.GetEvents(id) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to show alert") - } - - return cli.OutputJSON(eventsResponse.Data) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_integrations.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_integrations.go deleted file mode 100644 index a216f2547..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_integrations.go +++ /dev/null @@ -1,55 +0,0 @@ -package cmd - -import ( - "github.com/lacework/go-sdk/api" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" -) - -func alertIntegrationsTable(integrations []api.AlertIntegration) (out [][]string) { - for _, i := range integrations { - out = append(out, []string{ - i.ID, - i.IntgGUID, - i.Type, - i.Channel.Status(), - i.Channel.StateString(), - }) - } - return -} - -func renderAlertIntegrationsTable(integrations []api.AlertIntegration) { - cli.OutputHuman( - renderCustomTable( - []string{"Alert Integration ID", "Alert Channel GUID", "Type", "Status", "State"}, - alertIntegrationsTable(integrations), - tableFunc(func(t *tablewriter.Table) { - t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) -} - -func showAlertIntegrations(id int) error { - cli.StartProgress(" Fetching alert integrations...") - integrationsResponse, err := cli.LwApi.V2.Alerts.GetIntegrations(id) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to show alert") - } - - if cli.JSONOutput() { - return cli.OutputJSON(integrationsResponse.Data) - } - - if len(integrationsResponse.Data) == 0 { - cli.OutputHuman("There are no integration details available for the specified alert.\n") - return nil - } - - renderAlertIntegrationsTable(integrationsResponse.Data) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_investigation.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_investigation.go deleted file mode 100644 index 7fe5d9777..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_investigation.go +++ /dev/null @@ -1,52 +0,0 @@ -package cmd - -import ( - "github.com/lacework/go-sdk/api" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" -) - -func alertInvestigationTable(investigations []api.AlertInvestigation) (out [][]string) { - for _, i := range investigations { - out = append(out, []string{ - i.Question, - i.Answer, - }) - } - return -} - -func renderAlertInvestigationTable(investigations []api.AlertInvestigation) { - cli.OutputHuman( - renderCustomTable( - []string{"Question", "Answer"}, - alertInvestigationTable(investigations), - tableFunc(func(t *tablewriter.Table) { - t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) -} - -func showAlertInvestigation(id int) error { - cli.StartProgress(" Fetching alert investigation...") - investigationResponse, err := cli.LwApi.V2.Alerts.GetInvestigation(id) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to show alert") - } - - if cli.JSONOutput() { - return cli.OutputJSON(investigationResponse.Data) - } - - if len(investigationResponse.Data) == 0 { - cli.OutputHuman("There are no investigation details available for the specified alert.\n") - return nil - } - - renderAlertInvestigationTable(investigationResponse.Data) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_related.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_related.go deleted file mode 100644 index 5eff3145c..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_related.go +++ /dev/null @@ -1,59 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/lacework/go-sdk/api" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" -) - -func relatedAlertsTable(relatedAlerts api.RelatedAlerts) (out [][]string) { - for _, relatedAlert := range relatedAlerts.SortRankDescending() { - out = append(out, []string{ - relatedAlert.ID, - relatedAlert.Name, - relatedAlert.Severity, - relatedAlert.StartTime, - relatedAlert.EndTime, - strconv.Itoa(relatedAlert.Rank), - }) - } - - return -} - -func renderRelatedAlertsTable(relatedAlerts api.RelatedAlerts) { - cli.OutputHuman( - renderCustomTable( - []string{"Alert ID", "Name", "Severity", "Start Time", "End Time", "Rank"}, - relatedAlertsTable(relatedAlerts), - tableFunc(func(t *tablewriter.Table) { - t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) -} - -func showRelatedAlerts(id int) error { - cli.StartProgress(" Fetching related alerts...") - relatedResponse, err := cli.LwApi.V2.Alerts.GetRelatedAlerts(id) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to show alert") - } - - if cli.JSONOutput() { - return cli.OutputJSON(relatedResponse.Data) - } - - if len(relatedResponse.Data) == 0 { - cli.OutputHuman("There are no related alerts associated with the specified alert.\n") - return nil - } - - renderRelatedAlertsTable(relatedResponse.Data) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_timeline.go b/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_timeline.go deleted file mode 100644 index 71b16ce09..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/alert_show_timeline.go +++ /dev/null @@ -1,57 +0,0 @@ -package cmd - -import ( - "strconv" - - "github.com/lacework/go-sdk/api" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" -) - -func alertTimelineTable(timelines []api.AlertTimeline) (out [][]string) { - for _, t := range timelines { - out = append(out, []string{ - strconv.Itoa(t.ID), - t.EntryType, - t.Message.Value, - t.EntryAuthorType, - t.User.Name, - }) - } - return -} - -func renderAlertTimelineTable(timelines []api.AlertTimeline) { - cli.OutputHuman( - renderCustomTable( - []string{"Timeline ID", "Entry Type", "Message", "Author Type", "Author"}, - alertTimelineTable(timelines), - tableFunc(func(t *tablewriter.Table) { - t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) -} - -func showAlertTimeline(id int) error { - cli.StartProgress(" Fetching alert timeline...") - timelineResponse, err := cli.LwApi.V2.Alerts.GetTimeline(id) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to show alert") - } - - if cli.JSONOutput() { - return cli.OutputJSON(timelineResponse.Data) - } - - if len(timelineResponse.Data) == 0 { - cli.OutputHuman("There are no timeline entries for the specified alert.\n") - return nil - } - - renderAlertTimelineTable(timelineResponse.Data) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/api.go b/vendor/github.com/lacework/go-sdk/cli/cmd/api.go deleted file mode 100644 index 202f1bff4..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/api.go +++ /dev/null @@ -1,133 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/internal/array" -) - -var ( - // list of valid API methods - validApiMethods = []string{"get", "post", "delete", "patch"} - - // data to send for POST/PATCH request - apiData string - - // apiCmd represents the api command - apiCmd = &cobra.Command{ - Use: "api ", - Short: "Helper to call Lacework's API", - Long: `Use this command as a helper to call any available Lacework API v2 endpoint. - -### API v2 - -To list all available Lacework schema types: - - lacework api get /v2/schemas - -To receive a json response of all machines within the given time window: - - lacework api post /api/v2/Entities/Machines/search -d "{}" - -To receive a json response of all agents within the given time window: - - lacework api post /api/v2/AgentInfo/search -d "{}" - -For a complete list of available API v2 endpoints visit: - - https://.lacework.net/api/v2/docs -`, - Args: argsApiValidator, - RunE: runApiCommand, - } -) - -func init() { - // add the api command - rootCmd.AddCommand(apiCmd) - - apiCmd.Flags().StringVarP(&apiData, - "data", "d", "", - "data to send only for post and patch requests", - ) -} - -func runApiCommand(_ *cobra.Command, args []string) error { - switch args[0] { - case "patch": - if apiData == "" { - return fmt.Errorf("missing '--data' parameter patch requests") - } - case "get": - if apiData != "" { - return fmt.Errorf("use '--data' only for post, delete and patch requests") - } - } - - response := new(interface{}) - err := cli.LwApi.RequestDecoder( - strings.ToUpper(args[0]), - cleanupEndpoint(args[1]), - strings.NewReader(apiData), - response, - ) - if err != nil { - return errors.Wrap(err, "unable to send the request") - } - - if err := cli.OutputJSON(*response); err != nil { - return errors.Wrap(err, "unable to format json response") - } - return nil -} - -func argsApiValidator(_ *cobra.Command, args []string) error { - if len(args) != 2 { - return errors.New("requires 2 argument. (method and path)") - } - if !array.ContainsStr(validApiMethods, args[0]) { - return fmt.Errorf( - "invalid method specified: '%s' (valid methods are %s)", - args[0], validApiMethods, - ) - } - return nil -} - -// cleanupEndpoint will make sure that any provided endpoint is well formatted -// and doesn't contain known fields like /api/v1/foo -func cleanupEndpoint(endpoint string) string { - splitEndpoint := strings.Split(endpoint, "/") - - if len(splitEndpoint) > 0 && splitEndpoint[0] == "api" { - return strings.Join(splitEndpoint[1:], "/") - } - - if len(splitEndpoint) > 1 && splitEndpoint[1] == "api" { - return strings.Join(splitEndpoint[2:], "/") - } - - return strings.TrimPrefix(endpoint, "/") -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/aws.go deleted file mode 100644 index 1a528e66d..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/aws.go +++ /dev/null @@ -1,224 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "context" - "sync" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/ec2" - "github.com/aws/aws-sdk-go-v2/service/ec2/types" - "github.com/gammazero/workerpool" - "github.com/lacework/go-sdk/lwrunner" -) - -func awsDescribeInstances(filterSSH bool) ([]*lwrunner.AWSRunner, error) { - cli.StartProgress("Finding target EC2 instances...") - defer cli.StopProgress() - - regions, err := awsDescribeRegions() - if err != nil { - return nil, err - } - - allRunners := []*lwrunner.AWSRunner{} - for _, region := range regions { - regionRunners, err := awsRegionDescribeInstances(*region.RegionName, filterSSH) - if err != nil { - return nil, err - } - allRunners = append(allRunners, regionRunners...) - } - - return allRunners, nil -} - -// awsDescribeRegions queries the AWS API to list all the regions that -// are enabled for the user's AWS account. Use the "include_regions" -// command-line flag to only get regions in this list. -func awsDescribeRegions() ([]types.Region, error) { - // Describe all regions that are enabled for the account - var filters []types.Filter - if len(agentCmdState.InstallIncludeRegions) > 0 { - filters = []types.Filter{ - { - Name: aws.String("region-name"), - Values: agentCmdState.InstallIncludeRegions, - }, - } - } - input := &ec2.DescribeRegionsInput{ - Filters: filters, - } - - cfg, err := config.LoadDefaultConfig(context.Background(), - config.WithSharedConfigProfile(agentCmdState.InstallAWSProfile), - ) - if err != nil { - return nil, err - } - svc := ec2.New(ec2.Options{ - Credentials: cfg.Credentials, - Region: cfg.Region, - }) - - output, err := svc.DescribeRegions(context.Background(), input) - if err != nil { - return nil, err - } - return output.Regions, nil -} - -func awsRegionDescribeInstances(region string, filterSSH bool) ([]*lwrunner.AWSRunner, error) { - var ( - tagKey = agentCmdState.InstallTagKey - tag = agentCmdState.InstallTag - ) - cfg, err := config.LoadDefaultConfig(context.Background(), - config.WithSharedConfigProfile(agentCmdState.InstallAWSProfile), - ) - if err != nil { - return nil, err - } - svc := ec2.New(ec2.Options{ - Credentials: cfg.Credentials, - Region: region, - }) - - var filters []types.Filter - - // Filter for instances that are running - filters = append(filters, types.Filter{ - Name: aws.String("instance-state-name"), - Values: []string{ - "running", - }, - }) - - // Filter for instances where a tag key exists - if tagKey != "" { - cli.Log.Debugw("looking for tagKey", "tagKey", tagKey, "region", region) - filters = append(filters, types.Filter{ - Name: aws.String("tag-key"), - Values: []string{ - tagKey, - }, - }) - } - - // Filter for instances where certain tags exist - if len(tag) > 0 { - cli.Log.Debugw("looking for tags", "tag length", len(tag), "tags", tag, "region", region) - filters = append(filters, types.Filter{ - Name: aws.String("tag:" + tag[0]), - Values: tag[1:], - }) - } - - input := &ec2.DescribeInstancesInput{ - Filters: filters, - } - result, err := svc.DescribeInstances(context.Background(), input) - if err != nil { - cli.Log.Debugw("error with describing instances", "input", input) - return nil, err - } - - runners := []*lwrunner.AWSRunner{} - producerWg := new(sync.WaitGroup) - wp := workerpool.New(agentCmdState.InstallMaxParallelism) - runnerCh := make(chan *lwrunner.AWSRunner) - - // We have multiple producers of runners and a single consumer. - // This goroutine acts as the consumer and reads from a channel into - // a slice. Pass a pointer to this slice and wait for this goroutine - // to finish before returning the memory pointed to. - consumerWg := new(sync.WaitGroup) - consumerWg.Add(1) - go func(runners *[]*lwrunner.AWSRunner) { - for runner := range runnerCh { - *runners = append(*runners, runner) - } - consumerWg.Done() - }(&runners) - - cli.Log.Debugw("iterating over runners", "region", region) - for _, reservation := range result.Reservations { - for _, instance := range reservation.Instances { - if instance.PublicIpAddress != nil && instance.State.Name == "running" { - cli.Log.Debugw("found runner", - "public ip address", *instance.PublicIpAddress, - "instance state name", instance.State.Name, - ) - - if err != nil { - cli.Log.Debugw("error identifying runner", "error", err, "instance_id", *instance.InstanceId) - continue - } - - producerWg.Add(1) - - // In order to use `wp.Submit()`, the input func() must not take any arguments. - // Copy the runner info to dedicated variable in the goroutine - instanceCopyWg := new(sync.WaitGroup) - instanceCopyWg.Add(1) - - wp.Submit(func() { - defer producerWg.Done() - - threadInstance := instance - instanceCopyWg.Done() - cli.Log.Debugw("found runner", - "public ip address", *threadInstance.PublicIpAddress, - "instance state name", threadInstance.State.Name, - ) - - runner, err := lwrunner.NewAWSRunner( - *threadInstance.ImageId, - agentCmdState.InstallSshUser, - *threadInstance.PublicIpAddress, - region, - *threadInstance.Placement.AvailabilityZone, - *threadInstance.InstanceId, - filterSSH, - verifyHostCallback, - cfg, - ) - if err != nil { - cli.Log.Debugw("error identifying runner", "error", err, "instance_id", *threadInstance.InstanceId) - } else { - runnerCh <- runner - } - }) - instanceCopyWg.Wait() - } - } - } - - // Wait for the producers to finish, then close the producer thread pool, - // then close the channel they're writing to, then wait for the consumer to finish - producerWg.Wait() - wp.StopWait() - close(runnerCh) - consumerWg.Wait() - - return runners, nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/awsiam.go b/vendor/github.com/lacework/go-sdk/cli/cmd/awsiam.go deleted file mode 100644 index de7b263fa..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/awsiam.go +++ /dev/null @@ -1,320 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "context" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/iam" - "github.com/aws/aws-sdk-go-v2/service/iam/types" - "github.com/google/uuid" - "github.com/lacework/go-sdk/lwrunner" -) - -// setupSSMAccess sets up an IAM role for SSM and attaches it to -// the machine's instance profile. Takes role name as argument; -// pass the empty string to create a new role. -// Then creates SSM document. -func setupSSMAccess(cfg aws.Config, roleName string, token string) (types.Role, types.InstanceProfile, error) { - c := iam.New(iam.Options{ - Credentials: cfg.Credentials, - Region: cfg.Region, - }) - - cli.Log.Debugw("setting up role", "passed roleName", roleName) - role, err := setupSSMRole(c, roleName) - if err != nil { - return role, types.InstanceProfile{}, err - } - - err = attachSSMPoliciesToRole(c, role) - if err != nil { - return role, types.InstanceProfile{}, err - } - - // Create instance profile and add the role to it - instanceProfile, err := setupInstanceProfile(c, role) - if err != nil { - return role, instanceProfile, err - } - - return role, instanceProfile, nil -} - -// teardownSSMAccess destroys all the infra created during the execution of this program. -// Specifically, this function: -// - Removes the role from the instance profile -// - Deletes the instance profile -// - Detaches all managed policies from the role (assumes no inline policies attached) -// - Deletes the role -func teardownSSMAccess( - cfg aws.Config, role types.Role, instanceProfile types.InstanceProfile, byoRoleName string, -) error { - c := iam.New(iam.Options{ - Credentials: cfg.Credentials, - Region: cfg.Region, - }) - - if taggedLaceworkResource(instanceProfile.Tags) { - cli.Log.Debugw("removing role from instance profile", "role", role, "instance profile", instanceProfile) - _, err := c.RemoveRoleFromInstanceProfile( - context.Background(), - &iam.RemoveRoleFromInstanceProfileInput{ - InstanceProfileName: instanceProfile.InstanceProfileName, - RoleName: role.RoleName, - }, - ) - if err != nil { - return err - } - - cli.Log.Debugw("deleting instance profile", "instance profile", instanceProfile) - _, err = c.DeleteInstanceProfile( - context.Background(), - &iam.DeleteInstanceProfileInput{ - InstanceProfileName: instanceProfile.InstanceProfileName, - }, - ) - if err != nil { - return err - } - } - - if byoRoleName != "" || !taggedLaceworkResource(role.Tags) { - cli.Log.Debugw("Lacework didn't create this role, will not delete it", - "byoRoleName", byoRoleName, - "role", role, - ) - return nil - } - - cli.Log.Debug("listing managed policies attached to this role (assuming no inline policies") - listOutput, err := c.ListAttachedRolePolicies( - context.Background(), - &iam.ListAttachedRolePoliciesInput{ - RoleName: role.RoleName, - }, - ) - if err != nil { - return err - } - - // Detach managed policies - for _, attachedPolicy := range listOutput.AttachedPolicies { - cli.Log.Debugw("detaching policy", "policy", attachedPolicy, "role", role) - _, err := c.DetachRolePolicy( - context.Background(), - &iam.DetachRolePolicyInput{ - PolicyArn: attachedPolicy.PolicyArn, - RoleName: role.RoleName, - }, - ) - if err != nil { - return err - } - } - - cli.Log.Debugw("deleting role", "role", role) - _, err = c.DeleteRole( - context.Background(), - &iam.DeleteRoleInput{ - RoleName: role.RoleName, - }, - ) - if err != nil { - return err - } - - return nil -} - -// taggedLaceworkResource is a helper function that takes the tag set of -// a suspected Lacework resource, iterates through the tags, and determines -// if the resource belongs to Lacework. Returns `true, nil` if the resource -// belongs to Lacework and `false, nil` if the resource does not belong to -// Lacework. -func taggedLaceworkResource(tags []types.Tag) bool { - for _, tag := range tags { - if *tag.Key == laceworkAutomationTagKey { - return true - } - } - - return false -} - -const laceworkAutomationTagKey = "LaceworkAutomation" - -// setupSSMRole sets up an IAM role for SSM to assume. -// If `roleName` is not the empty string, then use that role -// instead of creating a new one. -func setupSSMRole(c *iam.Client, roleName string) (types.Role, error) { - if roleName != "" { - return getRoleFromName(c, roleName) - } else { - cli.Log.Debug("user did not provide a role, creating one now") - return createSSMRole(c) - } -} - -type iamGetRoleFromNameAPI interface { - GetRole(ctx context.Context, params *iam.GetRoleInput, optFns ...func(*iam.Options)) (*iam.GetRoleOutput, error) -} - -func getRoleFromName(c iamGetRoleFromNameAPI, roleName string) (types.Role, error) { - cli.Log.Debug("fetching info about role", roleName) - output, err := c.GetRole( - context.Background(), - &iam.GetRoleInput{ - RoleName: aws.String(roleName), - }, - ) - if err != nil { - return types.Role{}, err - } - - return *output.Role, nil -} - -type iamCreateSSMRoleAPI interface { - CreateRole( - ctx context.Context, params *iam.CreateRoleInput, optFns ...func(*iam.Options), - ) (*iam.CreateRoleOutput, error) -} - -// createSSMRole makes a call to the AWS API to create an IAM role. -// Returns information about the newly created role and any errors. -func createSSMRole(c iamCreateSSMRoleAPI) (types.Role, error) { - const roleNameBase string = "Lacework-Agent-SSM-Install-Role-" - roleName := roleNameBase + uuid.New().String()[:5] - - const trustPolicyDocument = `{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { "Service": "ec2.amazonaws.com" }, - "Action": "sts:AssumeRole" - } - ] -}` - - output, err := c.CreateRole( - context.Background(), - &iam.CreateRoleInput{ - AssumeRolePolicyDocument: aws.String(trustPolicyDocument), - RoleName: aws.String(roleName), - Description: aws.String( - `Ephemeral role to install Lacework agents using SSM; created by the Lacework CLI. -Safe to delete if found`, - ), - Tags: []types.Tag{ - { - Key: aws.String("Name"), - Value: aws.String(roleName), - }, - { - Key: aws.String(laceworkAutomationTagKey), - Value: aws.String("agent-ssm-install"), - }, - }, - }, - ) - if err != nil { - return types.Role{}, err - } - cli.Log.Debugw("freshly created role", "role", *output.Role) - - return *output.Role, nil -} - -// attachSSMPoliciesToRole takes a role, calls the IAM API to attach -// policies required for SSM to the role, and returns the role along -// with any errors. -func attachSSMPoliciesToRole(c *iam.Client, role types.Role) error { - cli.Log.Debug("attaching policy to role") - _, err := c.AttachRolePolicy( - context.Background(), - &iam.AttachRolePolicyInput{ - PolicyArn: aws.String(lwrunner.SSMInstancePolicy), - RoleName: role.RoleName, - }, - ) - - return err -} - -func setupInstanceProfile(c *iam.Client, role types.Role) (types.InstanceProfile, error) { - instanceProfile, err := createInstanceProfile(c) - if err != nil { - return instanceProfile, err - } - - err = addRoleToInstanceProfile(c, role, instanceProfile) - if err != nil { - return types.InstanceProfile{}, err - } - - return instanceProfile, nil -} - -func createInstanceProfile(c *iam.Client) (types.InstanceProfile, error) { - const instanceProfileNameBase string = "Lacework-Agent-SSM-Install-Instance-Profile-" - instanceProfileName := instanceProfileNameBase + uuid.New().String()[:5] - - createOutput, err := c.CreateInstanceProfile( - context.Background(), - &iam.CreateInstanceProfileInput{ - InstanceProfileName: aws.String(instanceProfileName), - Tags: []types.Tag{ - { - Key: aws.String("Name"), - Value: aws.String(instanceProfileName), - }, - { - Key: aws.String(laceworkAutomationTagKey), - Value: aws.String("agent-ssm-install"), - }, - }, - }, - ) - if err != nil { - return types.InstanceProfile{}, err - } - - cli.Log.Debug("sleeping for 15sec to wait for instance profile eventual consistency") - time.Sleep(15 * time.Second) - - return *createOutput.InstanceProfile, err -} - -func addRoleToInstanceProfile(c *iam.Client, role types.Role, instanceProfile types.InstanceProfile) error { - cli.Log.Debugw("adding role to instance profile", "role", role, "instance profile", instanceProfile) - _, err := c.AddRoleToInstanceProfile( - context.Background(), - &iam.AddRoleToInstanceProfileInput{ - InstanceProfileName: instanceProfile.InstanceProfileName, - RoleName: role.RoleName, - }, - ) - - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cache.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cache.go deleted file mode 100644 index 4b81f8a7a..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/cache.go +++ /dev/null @@ -1,317 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "path" - "strings" - "time" - - "github.com/lacework/go-sdk/internal/cache" - "github.com/lacework/go-sdk/internal/format" - "github.com/mitchellh/hashstructure/v2" - "github.com/peterbourgon/diskv/v3" - "github.com/pkg/errors" -) - -const MaxCacheSize = 1024 * 1024 * 1024 - -// InitCache initializes the Lacework CLI cache to store data on disk, -// this functions accepts an specific path to store the cache or, by -// default, it will use the default location. -// -// Simple CRUD example: -// -// ```go -// cli.Cache.WriteString("data", "something useful") // Create -// myData := cli.Cache.Read("data") // Read -// cli.Cache.Write("data", []byte("cool update")) // Update -// cli.Cache.Erase("data") // Delete -// ``` -func (c *cliState) InitCache(d ...string) { - if len(d) == 0 { - dir, err := cache.CacheDir() - if err == nil { - d = []string{dir} - } - } - - cache := strings.Join(d, "/") - c.Cache = diskv.New(diskv.Options{ - BasePath: path.Join(cache, "cache"), - AdvancedTransform: CacheTransform, - InverseTransform: InverseCacheTransform, - CacheSizeMax: MaxCacheSize, - }) -} - -func CacheTransform(key string) *diskv.PathKey { - // Global cache - // - // The Lacework CLI will have times where we need to cache global things - // such as the daily version checks. For those cases, we will use the - // global cache that can be accessed like: - // - // cli.Cache.Read("global/version") - // - if strings.HasPrefix(key, "global/") { - keys := strings.Split(key, "/") - pathToKey := []string{} - if len(keys) > 2 { - pathToKey = keys[1 : len(keys)-1] - } - return &diskv.PathKey{ - Path: pathToKey, - FileName: keys[len(keys)-1], - } - } - - // Scoped cache - // - // If the Lacework CLI is not using the global cache, then we will land - // in the scoped cache, this cache is individual per profile, that is, - // a convination of /account/subaccount/key_id/{file}. This is the default - // cache location when doing CRUD actions like: - // - // cli.Cache.WriteString("static_data", "{ ... some static data ... }") - // - subaccount := cli.Subaccount - if subaccount == "" { - subaccount = "standalone" - } - pathToKey := []string{cli.Account, subaccount, cli.KeyID} - - // if the key contains "/" we need to split the path - if strings.Contains(key, "/") { - keys := strings.Split(key, "/") - pathToKey = append(pathToKey, keys[0:len(keys)-1]...) - key = keys[len(keys)-1] - } - - return &diskv.PathKey{ - Path: pathToKey, - FileName: key, - } -} - -func InverseCacheTransform(pathKey *diskv.PathKey) string { - if strings.HasPrefix(pathKey.FileName, "global/") { - keys := strings.Split(pathKey.FileName, "/") - return strings.Join(pathKey.Path, "/") + keys[1] - } - return strings.Join(pathKey.Path, "/") + pathKey.FileName -} - -func (c *cliState) EraseCachedToken() error { - if c.noCache { - return nil - } - - c.Log.Debugw("token expired, removing from cache", - "feature", "cache", - ) - - return c.Cache.Erase("token") -} - -func (c *cliState) ReadCachedToken() { - if c.noCache { - return - } - - if tokenJSON, err := c.Cache.Read("token"); err == nil { - if err := json.Unmarshal(tokenJSON, &c.tokenCache); err == nil { - c.Log.Debugw("token loaded from cache", - "feature", "cache", - "token", format.Secret(4, c.tokenCache.Token), - ) - c.Token = c.tokenCache.Token - } - } -} - -// return true if the cached token is expired or will expire within -// the eminent duration (i.e. 10 seconds) -func (c *cliState) cachedTokenExpiryEminent() bool { - eminentDuration := -10 * time.Second - // only consider the tokenCache expiry time if we actually have a cached token - return c.tokenCache.Token != "" && c.tokenCache.ExpiresAt.Before(time.Now().Add(eminentDuration)) -} - -func (c *cliState) WriteCachedToken() error { - if c.noCache { - return nil - } - // if we don't have a token or the cached token expiry is eminent - // then attempt to refresh it... - if c.Token == "" || c.cachedTokenExpiryEminent() { - response, err := c.LwApi.GenerateToken() - if err != nil { - return errors.New("Failed to generate token. Validate your credentials are properly configured and not expired.") - } - - c.Log.Debugw("saving token", - "feature", "cache", - "token", format.Secret(4, response.Token), - "expires_at", response.ExpiresAt, - ) - err = c.Cache.Write("token", structToString(response)) - if err != nil { - c.Log.Warnw("unable to write token in cache", - "feature", "cache", - "error", err.Error(), - ) - } - c.Token = response.Token - c.tokenCache.Token = response.Token - c.tokenCache.ExpiresAt = response.ExpiresAt - } - return nil -} - -// structToString takes any arbitrary type and converts it into a string -func structToString(v interface{}) []byte { - out, err := json.Marshal(v) - if err != nil { - return []byte{} - } - return out -} - -// cliAsset is a simple private struct that acks as an envelope for storing -// assets that has a expiration time into the local cache. The Data field -// is an interface on purpose to allow developers store any kind of asset, -// from primitives such as strings, ints, bools, to JSON objects -type cliAsset struct { - Data interface{} `json:"data"` - ExpiresAt time.Time `json:"expires_at"` -} - -// writeAssetToCache stores an asset with an expiration time and returns errors -// -// Simple Example: Having a struct named vulnReport -// -// ```go -// cli.WriteAssetToCache("my-report", time.Now().Add(time.Hour * 1), vulnReport{Foo: "bar"}) -// ``` -// writeAssetToCache stores an asset with an expiration time -// -// Simple Example: Having a struct named vulnReport -// -// ```go -// cli.WriteAssetToCache("my-report", time.Now().Add(time.Hour * 1), vulnReport{Foo: "bar"}) -// ``` -func (c *cliState) writeAssetToCache(key string, expiresAt time.Time, data interface{}) error { - if c.noCache { - return nil - } - - if expiresAt.Before(time.Now()) { - return nil // avoid writing assets that are already expired - } - - c.Log.Debugw("saving asset", - "feature", "cache", - "path", key, - "data", data, - "expires_at", expiresAt, - ) - return c.Cache.Write(key, structToString(cliAsset{data, expiresAt})) -} - -// WriteAssetToCache wraps WriteAssetToCacheErroring and squashes errors -func (c *cliState) WriteAssetToCache(key string, expiresAt time.Time, data interface{}) { - err := c.writeAssetToCache(key, expiresAt, data) - if err != nil { - c.Log.Warnw("unable to write asset in cache", - "feature", "cache", - "error", err.Error(), - ) - } -} - -// ReadCachedAsset tries to reads an asset with an expiration time, if the -// asset has expired, it returns "true", otherwise it returns "false" -// -// Simple Example: Having a struct named vulnReport -// -// ```go -// var report vulnReport -// -// if expired := cli.ReadCachedAsset("my-report", &report); !expired { -// fmt.Printf("My report: %v\n", report) -// } -// -// ``` -func (c *cliState) ReadCachedAsset(key string, data interface{}) bool { - if c.noCache { - return true // if the cache is disabled, all assets are treated like expired - } - - if dataJSON, err := c.Cache.Read(key); err == nil { - var asset cliAsset - if err := json.Unmarshal(dataJSON, &asset); err == nil { - c.Log.Debugw("asset loaded from cache", - "feature", "cache", - "path", key, - "expires_at", asset.ExpiresAt, - ) - - // check if the cache expired - if time.Now().After(asset.ExpiresAt) { - c.Log.Debugw("asset expired, removing from cache", - "feature", "cache", - "path", key, - "time_now", time.Now(), - "expires_at", asset.ExpiresAt, - ) - if err := c.Cache.Erase(key); err != nil { - c.Log.Warnw("unable to erase asset from cache", - "feature", "cache", "path", key, - ) - } - return true - } - - if err := json.Unmarshal(structToString(asset.Data), &data); err == nil { - // we successfully retrieved the asset, which has not expired, - // and we cast it to the proper type - return false - } - c.Log.Warnw("unable to cast asset data", - "feature", "cache", - "error", err.Error(), - ) - } - } - - return true -} - -// hash creates a unique hash value for arbitrary values in Go. -// -// Useful to generate unique cache keys of filters applied to commands. -func hash(v interface{}) uint64 { - h, err := hashstructure.Hash(v, hashstructure.FormatV2, nil) - if err != nil { - cli.Log.Warnw("unable to generate Hash()", "error", err.Error()) - } - return h -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cdk.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cdk.go deleted file mode 100644 index 684d1299d..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/cdk.go +++ /dev/null @@ -1,177 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "context" - "fmt" - "net" - "os" - - cdk "github.com/lacework/go-sdk/cli/cdk/go/proto/v1" - "github.com/pkg/errors" - "google.golang.org/grpc" -) - -// default gRPC target if not specified via LW_CDK_TARGET -const defaultGrpcPort int = 1123 - -// envs are all the environment variables passed to CDK components -func (c *cliState) envs() []string { - return []string{ - fmt.Sprintf("LW_PROFILE=%s", c.Profile), - fmt.Sprintf("LW_ACCOUNT=%s", c.Account), - fmt.Sprintf("LW_SUBACCOUNT=%s", c.Subaccount), - fmt.Sprintf("LW_API_KEY=%s", c.KeyID), - fmt.Sprintf("LW_API_SECRET=%s", c.Secret), - fmt.Sprintf("LW_API_TOKEN=%s", c.Token), - fmt.Sprintf("LW_ORGANIZATION=%v", c.OrgLevel), - fmt.Sprintf("LW_NONINTERACTIVE=%v", c.nonInteractive), - fmt.Sprintf("LW_NOCACHE=%v", c.noCache), - fmt.Sprintf("LW_NOCOLOR=%s", os.Getenv("NO_COLOR")), - fmt.Sprintf("LW_LOG=%s", c.Log.Level().CapitalString()), - fmt.Sprintf("LW_JSON=%v", c.jsonOutput), - fmt.Sprintf("LW_CDK_TARGET=%s", c.GrpcTarget()), - fmt.Sprintf("LW_API_SERVER_URL=%s", c.LwApi.URL()), - fmt.Sprintf("LW_CLI_VERSION=%s", Version), - } -} - -// GrpcTarget returns the gRPC target that the CDK architecture will use -// to allow components to communicate back to the Lacework CLI -func (c *cliState) GrpcTarget() string { - if target := os.Getenv("LW_CDK_TARGET"); target != "" { - return target - } - return fmt.Sprintf("localhost:%v", c.cdkServerPort) -} - -func (c *cliState) ReadCache(ctx context.Context, in *cdk.ReadCacheRequest) (*cdk.ReadCacheResponse, error) { - if in.Key == "" { - return nil, errors.New("cache key must be supplied") - } - - var data []byte - if !c.ReadCachedAsset(in.Key, &data) { // not expired - return &cdk.ReadCacheResponse{Hit: true, Data: data}, nil - } - return &cdk.ReadCacheResponse{ - Hit: false, - Data: nil, - }, nil -} - -func (c *cliState) WriteCache(ctx context.Context, in *cdk.WriteCacheRequest) (*cdk.WriteCacheResult, error) { - if in.Key == "" { - return nil, errors.New("cache key must be supplied") - } - - err := c.writeAssetToCache(in.Key, in.Expires.AsTime(), in.Data) - if err != nil { - msg := err.Error() - return &cdk.WriteCacheResult{Error: true, Message: msg}, nil - } - return &cdk.WriteCacheResult{Error: false, Message: ""}, nil -} - -// Ping implements CDK.Ping -func (c *cliState) Ping(ctx context.Context, in *cdk.PingRequest) (*cdk.PongReply, error) { - c.Log.Debugw("message", "from", "CDK/Ping", "component_name", in.GetComponentName()) - return &cdk.PongReply{Message: fmt.Sprintf("Pong %s", in.GetComponentName())}, nil -} - -// Honeyvent implements CDK.Honeyvent -func (c *cliState) Honeyvent(ctx context.Context, in *cdk.HoneyventRequest) (*cdk.Reply, error) { - c.Log.Debugw("message", "from", "CDK/Honeyvent", "feature", in.GetFeature()) - - // Set event feature, if provided - if f := in.GetFeature(); f != "" { - c.Event.Feature = f - } - - // Add feature fields - for key, value := range in.GetFeatureData() { - c.Event.AddFeatureField(key, value) - } - - // Set any error, if any - if err := in.GetError(); err != "" { - c.Event.Error = err - } - - // Set duration in millisecond, if provided - if durationMs := in.GetDurationMs(); durationMs != 0 { - c.Event.DurationMs = durationMs - } - - // Send the Honeyvent - c.SendHoneyvent() - - return &cdk.Reply{}, nil -} - -// Serve will start the CDK gRPC server -func (c *cliState) Serve() error { - // Start the gRPC server for components to communicate back - const maxAttempts = 20 - - if target := os.Getenv("LW_CDK_TARGET"); target != "" { - return c.serve(target) - } - - // Try a range of port numbers in case the default one is not available - var err error - for i := 0; i < maxAttempts; i++ { - err = c.serve(fmt.Sprintf("localhost:%v", defaultGrpcPort+i)) - if err == nil { - c.cdkServerPort = defaultGrpcPort + i - return err - } - } - return errors.Wrap(err, fmt.Sprintf("unable to start gRPC server (attempts: %d)", maxAttempts)) -} - -func (c *cliState) serve(target string) error { - c.Stop() // make sure server is not running - - lis, err := net.Listen("tcp", target) - if err != nil { - return errors.Wrap(err, "failed to listen") - } - - c.cdkServer = grpc.NewServer() // guardrails-disable-line - cdk.RegisterCoreServer(c.cdkServer, c) - - c.Log.Infow("gRPC server started", "address", lis.Addr()) - if err := c.cdkServer.Serve(lis); err != nil { - return errors.Wrap(err, "failed to serve") - } - - return nil -} - -// Stop will stop the CDK gRPC server gracefully. It stops the server from -// accepting new connections and RPCs and blocks until all the pending RPCs -// are finished. -func (c *cliState) Stop() { - if c.cdkServer != nil { - c.Log.Info("stopping gRPC server") - c.cdkServer.GracefulStop() - } -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cli_state.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cli_state.go deleted file mode 100644 index ec7499932..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/cli_state.go +++ /dev/null @@ -1,517 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "math/rand" - "net/http" - "os" - "reflect" - "strconv" - "strings" - "sync" - "time" - - "github.com/briandowns/spinner" - "github.com/fatih/color" - prettyjson "github.com/hokaccha/go-prettyjson" - "github.com/mattn/go-isatty" - "github.com/peterbourgon/diskv/v3" - "github.com/pkg/errors" - "github.com/spf13/viper" - "go.uber.org/zap" - "google.golang.org/grpc" - - "github.com/lacework/go-sdk/api" - cdk "github.com/lacework/go-sdk/cli/cdk/go/proto/v1" - "github.com/lacework/go-sdk/internal/format" - "github.com/lacework/go-sdk/lwcomponent" - "github.com/lacework/go-sdk/lwconfig" - "github.com/lacework/go-sdk/lwlogger" -) - -// cliState holds the state of the entire Lacework CLI -type cliState struct { - Profile string - Account string - Subaccount string - KeyID string - Secret string - Token string - OrgLevel bool - CfgVersion int - - LwApi *api.Client - JsonF *prettyjson.Formatter - Log *zap.SugaredLogger - Event *api.Honeyvent - Cache *diskv.Diskv - LwComponents *lwcomponent.State - - id string - workers sync.WaitGroup - spinner *spinner.Spinner - jsonOutput bool - yamlOutput bool - csvOutput bool - nonInteractive bool - noCache bool - lqlOperator string - profileDetails map[string]interface{} - tokenCache api.TokenData - installedCmd bool - componentParser componentArgParser - - // Implements core proto service - cdk.UnimplementedCoreServer - - // Allows only one gRPC Server - cdkServer *grpc.Server - cdkServerPort int -} - -// NewDefaultState creates a new cliState with some defaults -func NewDefaultState() *cliState { - c := &cliState{ - id: newID(), - Profile: "default", - lqlOperator: "rlike", // @afiune we use rlike to allow user to pass regex - CfgVersion: 2, - Log: lwlogger.New("").Sugar(), - JsonF: &prettyjson.Formatter{ - KeyColor: color.New(color.FgCyan, color.Bold), - StringColor: color.New(color.FgGreen, color.Bold), - BoolColor: color.New(color.FgYellow, color.Bold), - NumberColor: color.New(color.FgRed, color.Bold), - NullColor: color.New(color.FgWhite, color.Bold), - Indent: 2, - Newline: "\n", - }, - nonInteractive: !isatty.IsTerminal(os.Stdout.Fd()), - cdkServerPort: defaultGrpcPort, - } - - // initialize honeycomb library and honeyvent - c.InitHoneyvent() - - return c -} - -// SetProfile sets the provided profile into the cliState and loads the entire -// state of the Lacework CLI by calling 'LoadState()' -func (c *cliState) SetProfile(profile string) error { - if profile == "" { - return errors.New("Specify a profile.") - } - - c.Profile = profile - c.Log.Debugw("custom profile", "profile", profile) - return c.LoadState() -} - -// LoadState loads the state of the cli in the following order, loads the -// configured profile out from the viper loaded config, if the profile is -// set to the default and it is not found, we assume that the user is running -// the CLI with parameters or environment variables, so we proceed to load -// those. Though, if the profile is NOT the default, we error out with some -// breadcrumbs to help the user configure the CLI. After loading the profile, -// this function verifies parameters and env variables coming from viper -func (c *cliState) LoadState() error { - defer func() { - // update global honeyvent with loaded state - c.Event.Account = c.Account - c.Event.Subaccount = c.Subaccount - c.Event.Profile = c.Profile - c.Event.ApiKey = c.KeyID - c.Event.CfgVersion = c.CfgVersion - }() - - c.profileDetails = viper.GetStringMap(c.Profile) - if len(c.profileDetails) == 0 { - if c.Profile != "default" { - return fmt.Errorf( - "The profile '%s' could not be found.\n\nTry running 'lacework configure --profile %s'.", - c.Profile, c.Profile, - ) - } else { - c.Log.Debugw("unable to load state from config") - c.loadStateFromViper() - return nil - } - } - - c.Token = c.extractValueString("api_token") - c.KeyID = c.extractValueString("api_key") - c.Secret = c.extractValueString("api_secret") - c.Account = c.extractValueString("account") - c.Subaccount = c.extractValueString("subaccount") - version := c.extractValueInt("version") - if version > 2 { - c.CfgVersion = version - } - - c.Log.Debugw("state loaded", - "profile", c.Profile, - "account", c.Account, - "subaccount", c.Subaccount, - "api_token", format.Secret(4, c.Token), - "api_key", c.KeyID, - "api_secret", format.Secret(4, c.Secret), - "config_version", c.CfgVersion, - ) - - c.loadStateFromViper() - return nil -} - -// LoadProfiles loads all the profiles from the configuration file -func (c *cliState) LoadProfiles() (lwconfig.Profiles, error) { - confPath := viper.ConfigFileUsed() - - if confPath == "" { - return nil, errors.New("unable to load profiles. No configuration file found.") - } - - return lwconfig.LoadProfilesFrom(confPath) -} - -// VerifySettings checks if the CLI state has the necessary settings to run, -// if not, it throws an error with breadcrumbs to help the user configure the CLI -func (c *cliState) VerifySettings() error { - c.Log.Debugw("verifying config", "version", c.CfgVersion) - - // Token from cache - if c.Token != "" && c.Account != "" { - return nil - } - - if c.Profile == "" || - c.Account == "" || - c.Secret == "" || - c.KeyID == "" { - return fmt.Errorf( - "there is one or more settings missing.\n\nTry running 'lacework configure'.", - ) - } - - return nil -} - -// NewClient creates and stores a new Lacework API client to be used by the CLI -func (c *cliState) NewClient() error { - // @afiune load token from cache only if the token has not been already - // provided by env variables or flags. Example: lacework --api_token foo - if c.Token == "" { - c.ReadCachedToken() - } - - err := c.VerifySettings() - if err != nil { - return err - } - - apiOpts := []api.Option{ - api.WithLogLevel(c.Log.Level().CapitalString()), - api.WithSubaccount(c.Subaccount), - api.WithApiKeys(c.KeyID, c.Secret), - api.WithTimeout(time.Second * 125), - api.WithHeader("User-Agent", fmt.Sprintf("Command-Line/%s", Version)), - } - - if c.OrgLevel { - c.Log.Debug("accessing organization level data sets") - apiOpts = append(apiOpts, api.WithOrgAccess()) - } - - if c.tokenCache.Token != "" { - apiOpts = append(apiOpts, - api.WithTokenAndExpiration(c.Token, c.tokenCache.ExpiresAt)) - } else if c.Token != "" { - apiOpts = append(apiOpts, api.WithToken(c.Token)) - } else if os.Getenv("LW_API_TOKEN") != "" { - apiOpts = append(apiOpts, api.WithToken(os.Getenv("LW_API_TOKEN"))) - } - - apiOpts = append(apiOpts, - api.WithLifecycleCallbacks(api.LifecycleCallbacks{ - TokenExpiredCallback: cli.EraseCachedToken, - RequestCallback: func(httpCode int, _ http.Header) error { - if httpCode == 403 { - return c.Cache.Erase("token") - } - return nil - }, - })) - - if os.Getenv("LW_API_SERVER_URL") != "" { - apiOpts = append(apiOpts, api.WithURL(os.Getenv("LW_API_SERVER_URL"))) - } - - c.LoadLQLOperator() - - client, err := api.NewClient(c.Account, apiOpts...) - if err != nil { - return errors.Wrap(err, "unable to generate api client") - } - - c.LwApi = client - - // cache token - return c.WriteCachedToken() -} - -// LoadLQLOperator reads the reserverd environment variable to change the -// default LQL operator in the CLI for filter flags. Available operators; -// -// The `eq` operator allows you to specify a value that the field values -// of the result must be equal to. The `ne` operator means not equal to. -// -// The `in` operator allows you to specify multiple values in the values -// field of the filters. The field values of the result must match one of -// the values. The `not_in` operator is the opposite of `in`. -// -// The `like` operator allows you to specify a pattern that the field -// values of the result must match. The `not_like` operator is the -// opposite of `like`. -// -// The `ilike` operator works similar to like but it makes the match case -// insensitive. The `not_ilike` operator is the opposite of `ilike`. -// -// (default) The `rlike` operator matches the specified pattern represented -// by regular expressions. The `not_rlike` operator is the opposite of `rlike`. -// (more info on `RLIKE` see https://docs.snowflake.com/en/sql-reference/functions/rlike.html). -// -// The `gt` operator allows you to specify a value that the field values of -// the result must be greater than. The `lt` (less-than) operator is the -// opposite of `gt`. -// -// The `ge` operator allows you to specify a value that the field values of -// the result must be greater than or equal to. The `le` (less-than-or-equal-to) -// operator is the opposite of `ge`. - -func (c *cliState) LoadLQLOperator() { - if os.Getenv("LW_LQL_OPERATOR") != "" { - c.lqlOperator = os.Getenv("LW_LQL_OPERATOR") - } -} - -// InteractiveMode returns true if the cli is running in interactive mode -func (c *cliState) InteractiveMode() bool { - return !c.nonInteractive && !c.csvOutput -} - -// NonInteractive turns off interactive mode, that is, no progress bars and spinners -func (c *cliState) NonInteractive() { - c.Log.Info("turning off interactive mode") - c.nonInteractive = true -} - -// Interactive turns on interactive mode, that is, progress bars and spinners -func (c *cliState) Interactive() { - c.Log.Info("turning on interactive mode") - c.nonInteractive = false -} - -// NoCache turns off the Lacework CLI caching mechanism, so nothing will be cached -func (c *cliState) NoCache() { - c.Log.Info("turning off caching mechanism") - c.noCache = true -} - -// StartProgress starts a new progress spinner with the provider suffix and stores it -// into the cli state, make sure to run StopSpinner when you are done processing -func (c *cliState) StartProgress(suffix string) { - if c.nonInteractive { - c.Log.Debugw("skipping spinner", - "noninteractive", c.nonInteractive, - "action", "start_progress", - ) - return - } - - // humans like spinners (^.^) - if c.HumanOutput() { - // make sure there is not a spinner already running - c.StopProgress() - - // verify that the suffix starts with a space - if !strings.HasPrefix(suffix, " ") { - suffix = fmt.Sprintf(" %s", suffix) - } - - c.Log.Debugw("starting spinner", "suffix", suffix) - c.spinner = spinner.New(spinner.CharSets[9], 100*time.Millisecond) - c.spinner.Suffix = suffix - c.spinner.Start() - } -} - -// StopProgress stops the running progress spinner, if any -func (c *cliState) StopProgress() { - if c.nonInteractive { - c.Log.Debugw("skipping spinner", - "noninteractive", c.nonInteractive, - "action", "stop_progress", - ) - return - } - - // humans like spinners (^.^) - if c.HumanOutput() { - if c.spinner != nil { - c.Log.Debug("stopping spinner") - c.spinner.Stop() - c.spinner = nil - } - } -} - -// EnableJSONOutput enables the cli to display JSON output -func (c *cliState) EnableJSONOutput() { - c.Log.Info("switch output to json format") - c.jsonOutput = true -} - -// EnableYAMLOutput enables the cli to display YAML output -func (c *cliState) EnableYAMLOutput() { - c.Log.Info("switch output to yaml format") - c.yamlOutput = true -} - -// EnableJSONOutput enables the cli to display human readable output -func (c *cliState) EnableHumanOutput() { - c.Log.Info("switch output to human format") - c.jsonOutput = false -} - -// EnableCSVOutput enables the cli to display CSV output -func (c *cliState) EnableCSVOutput() { - c.Log.Info("switch output to csv format") - c.csvOutput = true -} - -// JSONOutput returns true if the cli is configured to display JSON output -func (c *cliState) JSONOutput() bool { - return c.jsonOutput -} - -// YAMLOutput returns true if the cli is configured to display YAML output -func (c *cliState) YAMLOutput() bool { - return c.yamlOutput -} - -// HumanOutput returns true if the cli is configured to display human readable output -func (c *cliState) HumanOutput() bool { - return !c.jsonOutput && !c.csvOutput && !c.yamlOutput -} - -// CSVOutput returns true if the cli is configured to display csv output -func (c *cliState) CSVOutput() bool { - return c.csvOutput -} - -// loadStateFromViper loads parameters and environment variables -// coming from viper into the CLI state -func (c *cliState) loadStateFromViper() { - if v := viper.GetString("api_token"); v != "" { - c.Token = v - c.Log.Debugw("state updated", "api_token", format.Secret(4, c.Token)) - } - - if v := viper.GetString("api_key"); v != "" { - c.KeyID = v - c.Log.Debugw("state updated", "api_key", c.KeyID) - } - - if v := viper.GetString("api_secret"); v != "" { - c.Secret = v - c.Log.Debugw("state updated", "api_secret", format.Secret(4, c.Secret)) - } - - if v := viper.GetString("account"); v != "" { - c.Account = v - c.Log.Debugw("state updated", "account", c.Account) - } - - if v := viper.GetString("subaccount"); v != "" { - c.Subaccount = v - c.Log.Debugw("state updated", "subaccount", c.Subaccount) - } - - if viper.GetBool("organization") { - c.OrgLevel = true - c.Log.Debugw("state updated", "organization", "true") - } -} - -func (c *cliState) extractValueString(key string) string { - if val, ok := c.profileDetails[key]; ok { - if str, ok := val.(string); ok { - return str - } - c.Log.Warnw("config value type mismatch", - "expected_type", "string", - "actual_type", reflect.TypeOf(val), - "file", viper.ConfigFileUsed(), - "profile", c.Profile, - "key", key, - "value", val, - ) - return "" - } - c.Log.Warnw("unable to find key from config", - "file", viper.ConfigFileUsed(), - "profile", c.Profile, - "key", key, - ) - return "" -} - -func (c *cliState) extractValueInt(key string) int { - if val, ok := c.profileDetails[key]; ok { - if i, ok := val.(int64); ok { - return int(i) - } - c.Log.Warnw("config value type mismatch", - "expected_type", "int", - "actual_type", reflect.TypeOf(val), - "file", viper.ConfigFileUsed(), - "profile", c.Profile, - "key", key, - "value", val, - ) - return 0 - } - c.Log.Warnw("unable to find key from config", - "file", viper.ConfigFileUsed(), - "profile", c.Profile, - "key", key, - ) - return 0 -} - -// newID generates a new client id, this id is useful for logging purposes -// when there are more than one client running on the same machine -// TODO @afiune move this into its own go package (look at api/client.go) -func newID() string { - now := time.Now().UTC().UnixNano() - seed := rand.New(rand.NewSource(now)) - return strconv.FormatInt(seed.Int63(), 16) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cli_unix.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cli_unix.go deleted file mode 100644 index 252c35a7a..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/cli_unix.go +++ /dev/null @@ -1,101 +0,0 @@ -//go:build !windows - -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "os" - - "github.com/AlecAivazis/survey/v2" - "github.com/fatih/color" -) - -// used by configure.go -var configureListCmdSetProfileEnv = `export LW_PROFILE="my-profile"` - -// promptIconsFuncs configures the prompt icons for Unix systems -var promptIconsFunc = func(icons *survey.IconSet) { - icons.Question.Text = "â–¸" -} - -// customPromptIconsFunc configures the prompt icons with custom string for Unix systems -var customPromptIconsFunc = func(s string) func(icons *survey.IconSet) { - return func(icons *survey.IconSet) { - icons.Question.Text = fmt.Sprintf("â–¸ %s", s) - } -} - -// A variety of colorized icons used throughout the code -var ( - successIcon = color.HiGreenString("✓") - failureIcon = color.HiRedString("✖") //nolint -) - -// Env variables found in GCP, AWS and Azure cloudshell. -// Used to determine if cli is running on cloudshell. -const ( - gcpCloudEnv = "CLOUD_SHELL" - awsCloudEnv = "AWS_EXECUTION_ENV" - AzureCloudEnv = "POWERSHELL_DISTRIBUTION_CHANNEL" -) - -// UpdateCommand returns the command that a user should run to update the cli -// to the latest available version (unix specific command) -func (c *cliState) UpdateCommand() string { - if os.Getenv(HomebrewInstall) != "" { - return ` - brew upgrade lacework-cli -` - } - - if isCloudShell() { - return ` - curl https://raw.githubusercontent.com/lacework/go-sdk/main/cli/install.sh | bash -s -- -d $HOME/bin -` - } - return ` - curl https://raw.githubusercontent.com/lacework/go-sdk/main/cli/install.sh | bash -` -} - -// isCloudShell uses env variables specific to GCP, AWS and Azure -// to determine if the Lacework CLI is running on cloudshell -func isCloudShell() bool { - return isAwsCloudShell() || isGcpCloudShell() || isAzureCloudShell() -} - -// isAzureCloudShell uses the native env variable POWERSHELL_DISTRIBUTION_CHANNEL="CloudShell" -// to determine if the Lacework CLI is running on Azure cloudshell -func isAzureCloudShell() bool { - return os.Getenv(AzureCloudEnv) == "CloudShell" -} - -// isGcpCloudShell uses the native env variable CLOUD_SHELL=true -// to determine if the Lacework CLI is running on GCP cloudshell -func isGcpCloudShell() bool { - return os.Getenv(gcpCloudEnv) == "true" -} - -// isAwsCloudShell uses the native env variable AWS_EXECUTION_ENV="Cloudshell" -// to determine if the Lacework CLI is running on AWS cloudshell -func isAwsCloudShell() bool { - return os.Getenv(awsCloudEnv) == "Cloudshell" -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cli_windows.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cli_windows.go deleted file mode 100644 index 7ace9160f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/cli_windows.go +++ /dev/null @@ -1,63 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "os" - - "github.com/AlecAivazis/survey/v2" - "github.com/fatih/color" -) - -// used by configure.go -var configureListCmdSetProfileEnv = `$env:LW_PROFILE = 'my-profile'` - -// promptIconsFuncs configures the prompt icons for Windows systems -var promptIconsFunc = func(icons *survey.IconSet) { - icons.Question.Text = ">" -} - -// customPromptIconsFunc configures the prompt icons with custom string for Windows systems -var customPromptIconsFunc = func(s string) func(icons *survey.IconSet) { - return func(icons *survey.IconSet) { - icons.Question.Text = fmt.Sprintf("> %s", s) - } -} - -// A variety of colorized icons used throughout the code -var ( - successIcon = color.HiGreenString("√") - failureIcon = color.HiRedString("×") //nolint -) - -// UpdateCommand returns the command that a user should run to update the cli -// to the latest available version (windows specific command) -func (c *cliState) UpdateCommand() string { - if os.Getenv(ChocolateyInstall) != "" { - return ` - choco upgrade lacework-cli -` - } - - return ` - Set-ExecutionPolicy Bypass -Scope Process -Force; - iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/lacework/go-sdk/main/cli/install.ps1')) -` -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/cloud_account.go b/vendor/github.com/lacework/go-sdk/cli/cmd/cloud_account.go deleted file mode 100644 index 7246a1dfe..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/cloud_account.go +++ /dev/null @@ -1,339 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/internal/format" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // top-level cloud-account command - cloudAccountCommand = &cobra.Command{ - Use: "cloud-account", - Aliases: []string{"cloud-accounts", "cloud", "ca"}, - Short: "Manage cloud accounts", - Long: "Manage cloud account integrations with Lacework", - } - - // used by cloud account list to list only a single type of cloud account - cloudAccountType string - - // cloudAccountsListCmd represents the list sub-command inside the cloud accounts command - cloudAccountListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all available cloud account integrations", - Args: cobra.NoArgs, - RunE: cloudAccountList, - } - - // cloudAccountShowCmd represents the show sub-command inside the cloud accounts command - cloudAccountShowCmd = &cobra.Command{ - Use: "show", - Aliases: []string{"get"}, - Short: "Show a single cloud account integration", - Args: cobra.ExactArgs(1), - RunE: cloudAccountShow, - } - - // cloudAccountCreateCmd represents the show sub-command inside the cloud accounts command - cloudAccountCreateCmd = &cobra.Command{ - Use: "create", - Short: "Create a new cloud account integration", - Args: cobra.NoArgs, - RunE: cloudAccountCreate, - } - - // cloudAccountDeleteCmd represents the delete sub-command inside the cloud accounts command - cloudAccountDeleteCmd = &cobra.Command{ - Use: "delete", - Aliases: []string{"rm"}, - Short: "Delete a cloud account integration", - Args: cobra.ExactArgs(1), - RunE: cloudAccountDelete, - } - - // cloudAccountMigrateCmd represents the migrate sub-command inside the cloud accounts command - cloudAccountMigrateCmd = &cobra.Command{ - Use: "migrate", - Short: "Mark a GCPv1 (storage-based) cloud account integration for migration", - Args: cobra.ExactArgs(1), - RunE: cloudAccountMigrate, - } -) - -func init() { - // add the cloud-account command - rootCmd.AddCommand(cloudAccountCommand) - cloudAccountCommand.AddCommand(cloudAccountListCmd) - cloudAccountCommand.AddCommand(cloudAccountShowCmd) - cloudAccountCommand.AddCommand(cloudAccountDeleteCmd) - cloudAccountCommand.AddCommand(cloudAccountCreateCmd) - cloudAccountCommand.AddCommand(cloudAccountMigrateCmd) - - // add type flag to cloud accounts list command - cloudAccountListCmd.Flags().StringVarP(&cloudAccountType, - "type", "t", "", "list all cloud accounts of a specific type", - ) -} - -func cloudAccountsToTable(cloudAccounts []api.CloudAccountRaw) [][]string { - var out [][]string - for _, cadata := range cloudAccounts { - out = append(out, []string{ - cadata.IntgGuid, - cadata.Name, - cadata.Type, - cadata.Status(), - cadata.StateString(), - }) - } - return out -} - -func cloudAccountList(_ *cobra.Command, _ []string) error { - var ( - cloudAccounts api.CloudAccountsResponse - err error - ) - - if cloudAccountType != "" { - caType, found := api.FindCloudAccountType(cloudAccountType) - if !found { - return errors.Errorf("unknown cloud account type '%s'", cloudAccountType) - } - cloudAccounts, err = cli.LwApi.V2.CloudAccounts.ListByType(caType) - } else { - cloudAccounts, err = cli.LwApi.V2.CloudAccounts.List() - } - if err != nil { - return errors.Wrap(err, "unable to get cloud accounts") - } - - if cli.JSONOutput() { - return cli.OutputJSON(cloudAccounts.Data) - } - - if len(cloudAccounts.Data) == 0 { - cli.OutputHuman("No cloud accounts found.\n") - return nil - } - - cli.OutputHuman( - renderSimpleTable( - []string{"Cloud Account GUID", "Name", "Type", "Status", "State"}, - cloudAccountsToTable(cloudAccounts.Data), - ), - ) - return nil -} - -func cloudAccountDelete(_ *cobra.Command, args []string) error { - cli.StartProgress(" Deleting cloud account...") - err := cli.LwApi.V2.CloudAccounts.Delete(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to delete cloud account") - } - cli.OutputHuman("The cloud account %s was deleted.\n", args[0]) - return nil -} - -func cloudAccountMigrate(_ *cobra.Command, args []string) error { - cli.StartProgress(" Initiating migration for cloud account...") - err := cli.LwApi.V2.CloudAccounts.Migrate(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to initiate migration for cloud-account.") - } - cli.OutputHuman("The cloud account %s was marked for migration.\n", args[0]) - return nil -} - -func cloudAccountShow(_ *cobra.Command, args []string) error { - var ( - cloudAccount api.CloudAccountResponse - out [][]string - ) - cli.StartProgress(" Fetching cloud account...") - err := cli.LwApi.V2.CloudAccounts.Get(args[0], &cloudAccount) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to retrieve cloud account") - } - - out = append(out, []string{cloudAccount.Data.IntgGuid, - cloudAccount.Data.Name, - cloudAccount.Data.Type, - cloudAccount.Data.Status(), - cloudAccount.Data.StateString()}) - - if cli.JSONOutput() { - return cli.OutputJSON(cloudAccount.Data) - } - - cli.OutputHuman(renderSimpleTable([]string{"Cloud Account GUID", "Name", "Type", "Status", "State"}, out)) - cli.OutputHuman("\n") - cli.OutputHuman(buildDetailsTable(cloudAccount.Data)) - return nil -} - -func buildDetailsTable(integration api.V2RawType) string { - var details [][]string - - if caMap, ok := integration.GetData().(map[string]interface{}); ok { - for k, v := range caMap { - switch val := v.(type) { - case int: - details = append(details, []string{strings.ToUpper(format.SpaceUpperCase(k)), strconv.Itoa(val)}) - case float64: - details = append(details, - []string{strings.ToUpper(format.SpaceUpperCase(k)), strconv.FormatFloat(val, 'f', -1, 64)}) - case string: - details = append(details, []string{strings.ToUpper(format.SpaceUpperCase(k)), val}) - case map[string]any: - for i, j := range val { - if v, ok := j.(string); ok { - details = append(details, []string{strings.ToUpper(format.SpaceUpperCase(i)), v}) - } else { - cli.Log.Warn("unable to build table details, unknown type", "type", i, "key", k) - } - } - case []any: - var values []string - for _, i := range val { - if _, ok := i.(string); ok { - values = append(values, i.(string)) - } else if _, ok := i.(map[string]interface{}); ok { - for m, n := range i.(map[string]interface{}) { - values = append(values, fmt.Sprintf("%s:%s", m, n)) - } - } else { - cli.Log.Warn("unable to build table details, unknown type", "type", i, "key", k) - } - } - details = append(details, []string{strings.ToUpper(format.SpaceUpperCase(k)), strings.Join(values, ",")}) - } - } - } - - // get server token for container registry type only - if c, ok := integration.(api.ContainerRegistryRaw); ok { - if c.ServerToken != nil { - details = append(details, []string{"SERVER_TOKEN", c.ServerToken.ServerToken}) - details = append(details, []string{"SERVER_TOKEN_URI", c.ServerToken.Uri}) - } - } - - //common - details = append(details, []string{"UPDATED AT", integration.GetCommon().CreatedOrUpdatedTime}) - details = append(details, []string{"UPDATED BY", integration.GetCommon().CreatedOrUpdatedBy}) - if integration.GetCommon().State != nil { - details = append(details, []string{ - "STATE UPDATED AT", integration.GetCommon().State.LastUpdatedTime.Format(time.RFC3339)}) - details = append(details, []string{ - "LAST SUCCESSFUL STATE", integration.GetCommon().State.LastSuccessfulTime.Format(time.RFC3339)}) - - //output state details as a json string - jsonState, err := json.Marshal(integration.GetCommon().State.Details) - if err == nil { - detailsJSON, err := cli.FormatJSONString(string(jsonState)) - if err == nil { - details = append(details, []string{"STATE DETAILS", detailsJSON}) - } - } - } - - array.Sort2D(details) - return renderOneLineCustomTable("DETAILS", - renderCustomTable([]string{}, details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ) - -} - -func cloudAccountCreate(_ *cobra.Command, _ []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - - err := promptCreateCloudAccount() - if err != nil { - return errors.Wrap(err, "unable to create cloud account") - } - - cli.OutputHuman("The cloud account was created.\n") - return nil -} - -func promptCreateCloudAccount() error { - var ( - cloudAccount = "" - prompt = &survey.Select{ - Message: "Choose a cloud account type to create: ", - Options: []string{ - "AWS Config", - "AWS CloudTrail", - "AWS Config (US GovCloud)", - "AWS CloudTrail (US GovCloud)", - "GCP Config", - "GCP Audit Log", - "GCP Audit Log PubSub", - "Azure Config", - "Azure Activity Log", - "OCI Config", - }, - } - err = survey.AskOne(prompt, &cloudAccount) - ) - if err != nil { - return err - } - - switch cloudAccount { - case "AWS Config": - return createAwsConfigIntegration() - case "AWS CloudTrail": - return createAwsCloudTrailIntegration() - case "AWS GovCloud Config": - return createAwsGovCloudConfigIntegration() - case "AWS GovCloud CloudTrail": - return createAwsGovCloudCTIntegration() - case "GCP Config": - return createGcpConfigIntegration() - case "GCP Audit Log": - return createGcpAuditLogIntegration() - case "GCP Audit Log PubSub": - return createGcpPubSubAuditLogIntegration() - case "Azure Config": - return createAzureConfigIntegration() - case "Azure Activity Log": - return createAzureActivityLogIntegration() - case "Azure Active Directory Activity Log": - return createAzureAdAlIntegration() - case "OCI Config": - return createOciConfigIntegration() - default: - return errors.New("unknown cloud account type") - } -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance.go b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance.go deleted file mode 100644 index 1ccd84052..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance.go +++ /dev/null @@ -1,572 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sort" - "strconv" - "strings" - "time" - - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/lwseverity" -) - -var ( - compCmdState = struct { - // download report in PDF format - Pdf bool - - // output report in CSV format - Csv bool - - // display extended details about a compliance report - Details bool - - // Filter the recommendations table by category - Category []string - - // Filter the recommendations table by service - Service []string - - // Filter the recommendations table by severity - Severity string - - // Filter the recommendations table by status - Status string - - // output resources affected by recommendationID - RecommendationID string - }{} - - // complianceCmd represents the compliance command - complianceCmd = &cobra.Command{ - Use: "compliance", - Aliases: []string{"comp"}, - Short: "Manage compliance reports", - Long: `Manage compliance reports for Google, Azure, or AWS cloud providers. - -Lacework cloud security platform provides continuous Compliance monitoring against -cloud security best practices and compliance standards as CIS, PCI DSS, SoC II and -HIPAA benchmark standards. - -Get started by integrating one or more cloud accounts using the command: - - lacework cloud-account create - -If you prefer to configure the integration via the WebUI, log in to your account at: - - https://.lacework.net - -Then navigate to Settings > Integrations > Cloud Accounts. - -Use the following command to list all available integrations in your account: - - lacework cloud-account list -`, - } - - // complianceAzureCmd represents the azure sub-command inside the compliance command - complianceAzureCmd = &cobra.Command{ - Use: "azure", - Aliases: []string{"az"}, - Short: "Compliance for Azure Cloud", - Long: `Manage compliance reports for Azure Cloud. - -To list all Azure tenants configured in your account: - - lacework compliance azure list-tenants - -To list all Azure subscriptions from a tenant, use the command: - - lacework compliance azure list-subscriptions - -To get the latest Azure compliance assessment report, use the command: - - lacework compliance azure get-report - -These reports run on a regular schedule, typically once a day. -`, - } - - // complianceGcpCmd represents the gcp sub-command inside the compliance command - complianceGcpCmd = &cobra.Command{ - Use: "google", - Aliases: []string{"gcp"}, - Short: "Compliance for Google Cloud", - Long: `Manage compliance reports for Google Cloud. - -To list all GCP organizations and projects configured in your account: - - lacework compliance gcp list - -To list all GCP projects from an organization, use the command: - - lacework compliance gcp list-projects - -To get the latest GCP compliance assessment report, use the command: - - lacework compliance gcp get-report - -These reports run on a regular schedule, typically once a day. -`, - } - - // complianceAwsCmd represents the aws sub-command inside the compliance command - complianceAwsCmd = &cobra.Command{ - Use: "aws", - Short: "Compliance for AWS", - Long: `Manage compliance reports for Amazon Web Services (AWS). - -To list all AWS accounts configured in your account: - - lacework compliance aws list-accounts - -To get the latest AWS compliance assessment report: - - lacework compliance aws get-report - -These reports run on a regular schedule, typically once a day. -`, - } -) - -func init() { - // add the compliance command - rootCmd.AddCommand(complianceCmd) - - // add sub-commands to the compliance command - complianceCmd.AddCommand(complianceAzureCmd) - complianceCmd.AddCommand(complianceAwsCmd) - complianceCmd.AddCommand(complianceGcpCmd) -} - -func complianceReportSummaryTable(summaries []api.ReportSummary) [][]string { - if len(summaries) == 0 { - return [][]string{} - } - summary := summaries[0] - return [][]string{ - {"Critical", fmt.Sprint(summary.NumSeverity1NonCompliance)}, - {"High", fmt.Sprint(summary.NumSeverity2NonCompliance)}, - {"Medium", fmt.Sprint(summary.NumSeverity3NonCompliance)}, - {"Low", fmt.Sprint(summary.NumSeverity4NonCompliance)}, - {"Info", fmt.Sprint(summary.NumSeverity5NonCompliance)}, - } -} - -func complianceReportRecommendationsTable(recommendations []api.RecommendationV2) [][]string { - out := [][]string{} - for _, recommend := range recommendations { - out = append(out, []string{ - recommend.RecID, - recommend.Title, - recommend.Status, - recommend.SeverityString(), - recommend.Service, - fmt.Sprint(len(recommend.Violations)), - fmt.Sprint(recommend.AssessedResourceCount), - }) - } - - sort.Slice(out, func(i, j int) bool { - return api.SeverityOrder(out[i][3]) < api.SeverityOrder(out[j][3]) - }) - - return out -} - -type complianceCSVReportDetails struct { - // For clouds with tenant models, supply tenant ID - TenantID string - - // For clouds with tenant models, supply tenant name/alias - TenantName string - - // Supply the account id for the cloud enviornment - AccountID string - - // Supply the account name/alias for the cloud enviornment, if available - AccountName string - - // The type of report being rendered - ReportType string - - // The time of the report execution - ReportTime time.Time - - // Recommendations - Recommendations []api.RecommendationV2 -} - -func (c complianceCSVReportDetails) GetAccountDetails() []string { - accountAlias := c.AccountID - if c.AccountName != "" { - accountAlias = fmt.Sprintf("%s(%s)", c.AccountName, c.AccountID) - } - - tenantAlias := c.TenantID - if c.TenantName != "" { - tenantAlias = fmt.Sprintf("%s(%s)", c.TenantName, c.TenantID) - } - out := []string{} - if tenantAlias != "" { - out = append(out, tenantAlias) - } - - if accountAlias != "" { - out = append(out, accountAlias) - } - return out -} - -func (c complianceCSVReportDetails) GetReportMetaData() []string { - return append([]string{c.ReportType, c.ReportTime.Format(time.RFC3339)}, c.GetAccountDetails()...) -} - -func (c complianceCSVReportDetails) SortRecommendations() { - sort.Slice(c.Recommendations, func(i, j int) bool { - return c.Recommendations[i].Category < c.Recommendations[j].Category - }) - -} - -func complianceCSVReportRecommendationsTable(details *complianceCSVReportDetails) [][]string { - details.SortRecommendations() - out := [][]string{} - - for _, recommendation := range details.Recommendations { - // GROW-1266: Do not add if status flag filters suppressed - if compCmdState.Status == "" || compCmdState.Status == "suppressed" { - for _, suppression := range recommendation.Suppressions { - out = append(out, - append(details.GetReportMetaData(), - recommendation.Category, - recommendation.RecID, - recommendation.Title, - "Suppressed", - recommendation.SeverityString(), - suppression, - "", - "")) - } - } - - // @afiune The platform today only returns a list of resources that are in violation, that - // means, "non-compliant" resources. We currently do not return the list of resources that - // are "compliant", "requires-manual-assessment" or "could-not-assess" - For those cases - // our CSV output will only show the number of resources. (GROW-2316) - if len(recommendation.Violations) == 0 { - out = append(out, - append(details.GetReportMetaData(), - recommendation.Category, - recommendation.RecID, - recommendation.Title, - recommendation.Status, - recommendation.SeverityString(), - fmt.Sprintf("%d", recommendation.ResourceCount), - "", - "")) - continue - } - - // @afiune - for _, violation := range recommendation.Violations { - out = append(out, - append(details.GetReportMetaData(), - recommendation.Category, - recommendation.RecID, - recommendation.Title, - recommendation.Status, - recommendation.SeverityString(), - violation.Resource, - violation.Region, - strings.Join(violation.Reasons, ","))) - } - } - - sort.Slice(out, func(i, j int) bool { - return api.SeverityOrder(out[i][3]) < api.SeverityOrder(out[j][3]) - }) - - return out -} - -func buildComplianceReportTable( - detailsTable, summaryTable, recommendationsTable [][]string, filteredOutput string, -) string { - mainReport := &strings.Builder{} - mainReport.WriteString( - renderCustomTable( - []string{ - "Compliance Report Details", - "Non-Compliant Recommendations", - }, - [][]string{[]string{ - renderCustomTable([]string{}, detailsTable, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator("") - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - renderCustomTable([]string{"Severity", "Count"}, summaryTable, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - }}, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - t.SetColumnSeparator(" ") - }), - ), - ) - - if compCmdState.Details || complianceFiltersEnabled() { - mainReport.WriteString( - renderCustomTable( - []string{"ID", "Recommendation", "Status", "Severity", - "Service", "Affected", "Assessed"}, - recommendationsTable, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetRowLine(true) - t.SetColumnSeparator(" ") - }), - ), - ) - if filteredOutput != "" { - mainReport.WriteString(filteredOutput) - } - mainReport.WriteString("\n") - - if compCmdState.Status == "" { - mainReport.WriteString( - "Try adding '--status non-compliant' to show only non-compliant recommendations.", - ) - } else if compCmdState.Severity == "" { - mainReport.WriteString( - "Try adding '--severity high' to show only high and critical recommendations.", - ) - } else { - mainReport.WriteString( - "Try adding [recommendation_id] to show affected resources.", - ) - } - mainReport.WriteString("\n") - } else { - mainReport.WriteString( - "Try adding '--details' to increase details shown about the compliance report.\n", - ) - } - return mainReport.String() -} - -func filterRecommendations(recommendations []api.RecommendationV2) ([]api.RecommendationV2, string) { - var filtered []api.RecommendationV2 - for _, r := range recommendations { - if matchRecommendationsFilters(r) { - filtered = append(filtered, r) - } - } - if len(filtered) == 0 { - return filtered, "There are no recommendations with the specified filter(s).\n" - } - - cli.Log.Debugw("filtered recommendations", "recommendations", filtered) - return filtered, fmt.Sprintf("%v of %v recommendations showing \n", len(filtered), len(recommendations)) -} - -func matchRecommendationsFilters(r api.RecommendationV2) bool { - var results []bool - - // severity returns specified threshold and above - if compCmdState.Severity != "" { - sevThreshold, _ := lwseverity.Normalize(compCmdState.Severity) - results = append(results, r.Severity <= sevThreshold) - } - - if len(compCmdState.Category) > 0 { - var categories []string - for _, c := range compCmdState.Category { - categories = append(categories, strings.ReplaceAll(c, "-", " ")) - } - results = append(results, array.ContainsStrCaseInsensitive(categories, r.Category)) - } - - if len(compCmdState.Service) > 0 { - results = append(results, array.ContainsStrCaseInsensitive(compCmdState.Service, r.Service)) - } - - if compCmdState.Status != "" { - results = append(results, r.Status == statusToProperTypes(compCmdState.Status)) - } - - return !array.ContainsBool(results, false) -} - -func complianceFiltersEnabled() bool { - return len(compCmdState.Category) > 0 || - compCmdState.Status != "" || - compCmdState.Severity != "" || - len(compCmdState.Service) > 0 -} - -func statusToProperTypes(status string) string { - switch strings.ToLower(status) { - case "non-compliant", "noncompliant": - return "NonCompliant" - case "compliant": - return "Compliant" - case "could-not-assess", "couldnotassess": - return "CouldNotAssess" - case "suppressed": - return "Suppressed" - case "requires-manual-assessment", "requiresmanualassessment": - return "RequiresManualAssessment" - default: - return "Unknown" - } -} - -func outputResourcesByRecommendationID(report api.CloudComplianceReportV2) error { - recommendation, found := report.GetComplianceRecommendation(compCmdState.RecommendationID) - if !found || recommendation == nil { - return errors.Errorf("recommendation id '%s' not found.", compCmdState.RecommendationID) - } - violations := recommendation.Violations - affectedResources := len(recommendation.Violations) - - if cli.JSONOutput() { - return cli.OutputJSON(recommendation) - } - - cli.OutputHuman( - renderOneLineCustomTable("RECOMMENDATION DETAILS", - renderCustomTable([]string{}, - [][]string{ - {"ID", compCmdState.RecommendationID}, - {"SEVERITY", recommendation.SeverityString()}, - {"SERVICE", recommendation.Service}, - {"CATEGORY", recommendation.Category}, - {"STATUS", recommendation.Status}, - {"ASSESSED RESOURCES", strconv.Itoa(recommendation.AssessedResourceCount)}, - {"AFFECTED RESOURCES", strconv.Itoa(affectedResources)}, - }, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - )) - - if affectedResources == 0 { - cli.OutputHuman("\nNo resources found affected by '%s'\n", compCmdState.RecommendationID) - return nil - } - - cli.OutputHuman( - renderSimpleTable( - []string{"AFFECTED RESOURCE", "REGION", "REASON"}, - violationsToTable(violations), - ), - ) - return nil -} - -func violationsToTable(violations []api.ComplianceViolationV2) (resourceTable [][]string) { - for _, v := range violations { - resourceTable = append(resourceTable, []string{v.Resource, v.Region, strings.Join(v.Reasons, ",")}) - } - return -} - -// nolint -func getReportTypes(reportSubType string) (validTypes []string, err error) { - cacheKey := fmt.Sprintf("reports/definitions/%s", reportSubType) - expired := cli.ReadCachedAsset(cacheKey, &validTypes) - - if expired { - cli.StartProgress("fetching valid report types...") - reportDefinitions, err := cli.LwApi.V2.ReportDefinitions.List() - cli.StopProgress() - - if err != nil { - return nil, err - } - - for _, report := range reportDefinitions.Data { - if report.SubReportType == reportSubType { - validTypes = append(validTypes, report.ReportNotificationType) - } - } - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), validTypes) - } - - return validTypes, err -} - -func prettyPrintReportTypes(reportTypes []string) string { - var sb strings.Builder - for i, r := range reportTypes { - if i%5 == 0 { - sb.WriteString("\n") - } - sb.WriteString(fmt.Sprintf("'%s',", r)) - } - return sb.String() -} - -func validReportName(cloud string, name string) error { - var validReportNames []string - definitions, err := cli.LwApi.V2.ReportDefinitions.List() - if err != nil { - return errors.Wrap(err, "unable to list report definitions") - } - - for _, d := range definitions.Data { - if d.SubReportType == cloud { - validReportNames = append(validReportNames, d.ReportName) - } - } - - if array.ContainsStr(validReportNames, name) { - return nil - } - - return errors.Errorf( - "'%s' is not a valid report name.\n"+ - "Run 'lacework report-definition list --subtype %s' for a list of valid report names", - name, cloud, - ) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_aws.go deleted file mode 100644 index d9181a96c..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_aws.go +++ /dev/null @@ -1,761 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/lwseverity" -) - -var ( - compAwsCmdState = struct { - Type string - ReportName string - }{ReportName: api.ComplianceReportDefaultAws} - - // complianceAwsListAccountsCmd represents the list-accounts inside the aws command - complianceAwsListAccountsCmd = &cobra.Command{ - Use: "list-accounts", - Aliases: []string{"list", "ls"}, - Short: "List all AWS accounts configured", - Long: `List all AWS accounts configured in your account.`, - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - cli.StartProgress("Fetching list of configured AWS accounts...") - awsAccounts, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AwsCfgCloudAccount) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get aws compliance integrations") - } - - return cliListAwsAccounts(awsAccounts) - }, - } - - // complianceAwsGetReportCmd represents the get-report sub-command inside the aws command - complianceAwsGetReportCmd = &cobra.Command{ - Use: "get-report [recommendation_id]", - Aliases: []string{"get", "show"}, - PreRunE: func(cmd *cobra.Command, args []string) error { - if compCmdState.Csv { - cli.EnableCSVOutput() - } - if len(args) > 1 { - compCmdState.RecommendationID = args[1] - } - - // ensure we cannot have both --type and --report_name flags - if cmd.Flags().Changed("type") && cmd.Flags().Changed("report_name") { - return errors.New("'--type' and '--report_name' flags cannot be used together") - } - - // validate report_name - if cmd.Flags().Changed("report_name") { - return validReportName(api.ReportDefinitionSubTypeAws.String(), compAwsCmdState.ReportName) - } - - if cmd.Flags().Changed("type") && !array.ContainsStr(api.AwsReportTypes(), compAwsCmdState.Type) { - return errors.Errorf("supported report types are: %s", strings.Join(api.AwsReportTypes(), ", ")) - } - - return nil - }, - Short: "Get the latest AWS compliance report", - Long: `Get the latest compliance assessment report from the provided AWS account, these -reports run on a regular schedule, typically once a day. The available report formats -are human-readable (default), json and pdf. - -To list all AWS accounts configured in your account: - - lacework compliance aws list-accounts - -To show recommendation details and affected resources for a recommendation id: - - lacework compliance aws get-report [recommendation_id] - -To retrieve a specific report by its report name: - - lacework compliance aws get-report --report_name 'AWS CSA CCM 4.0.5' -`, - Args: cobra.RangeArgs(1, 2), - RunE: func(cmd *cobra.Command, args []string) error { - var ( - // clean the AWS account ID if it was provided - // with an Alias in between parentheses - awsAccountID, _ = splitIDAndAlias(args[0]) - config = api.AwsReportConfig{ - AccountID: awsAccountID, - // Default config is report_name - Parameter: api.ReportFilterName, - Value: compAwsCmdState.ReportName, - } - ) - - // if --type flag is used, set the report config to type - if cmd.Flags().Changed("type") { - reportType, err := api.NewAwsReportType(compAwsCmdState.Type) - if err != nil { - return errors.Errorf("invalid report type %q", compAwsCmdState.Type) - } - - config.Parameter = api.ReportFilterType - config.Value = reportType.String() - } - - if compCmdState.Pdf { - pdfName := fmt.Sprintf( - "%s_Report_%s_%s_%s.pdf", - config.Value, - config.AccountID, - cli.Account, time.Now().Format("20060102150405"), - ) - - cli.StartProgress("Downloading compliance report...") - err := cli.LwApi.V2.Reports.Aws.DownloadPDF(pdfName, config) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get aws pdf compliance report") - } - - cli.OutputHuman("The AWS compliance report was downloaded at '%s'\n", pdfName) - return nil - } - - if compCmdState.Severity != "" { - if !lwseverity.IsValid(compCmdState.Severity) { - return errors.Errorf("the severity %s is not valid, use one of %s", - compCmdState.Severity, lwseverity.ValidSeverities.String(), - ) - } - } - if compCmdState.Status != "" { - if !array.ContainsStr(api.ValidComplianceStatus, compCmdState.Status) { - return errors.Errorf("the status %s is not valid, use one of %s", - compCmdState.Status, strings.Join(api.ValidComplianceStatus, ", "), - ) - } - } - - var ( - report api.AwsReport - cacheKey = fmt.Sprintf("compliance/aws/v2/%s/%s", config.AccountID, config.Value) - ) - expired := cli.ReadCachedAsset(cacheKey, &report) - if expired { - cli.StartProgress("Getting compliance report...") - response, err := cli.LwApi.V2.Reports.Aws.Get(config) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get aws compliance report") - } - - if len(response.Data) == 0 { - return errors.New("no data found in the report") - } - - report = response.Data[0] - - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), report) - } - - filteredOutput := "" - - if complianceFiltersEnabled() { - report.Recommendations, filteredOutput = filterRecommendations(report.Recommendations) - cli.Log.Infow("recommendations", - "count", len(report.Recommendations), - "filtered", len(filteredOutput), - ) - } - - if cli.JSONOutput() && compCmdState.RecommendationID == "" { - return cli.OutputJSON(report) - } - - if cli.CSVOutput() { - recommendations := complianceCSVReportRecommendationsTable( - &complianceCSVReportDetails{ - AccountName: report.AccountID, - AccountID: report.AccountID, - ReportType: report.ReportType, - ReportTime: report.ReportTime, - Recommendations: report.Recommendations, - }, - ) - cli.Log.Infow("csv recommendations", "count", len(recommendations)) - return cli.OutputCSV( - []string{"Report_Type", "Report_Time", "Account", - "Section", "ID", "Recommendation", "Status", - "Severity", "Resource", "Region", "Reason"}, - recommendations, - ) - } - - // If RecommendationID is provided, output resources matching that id - if compCmdState.RecommendationID != "" { - return outputResourcesByRecommendationID(report) - } - - recommendations := complianceReportRecommendationsTable(report.Recommendations) - cli.OutputHuman("\n") - cli.OutputHuman( - buildComplianceReportTable( - complianceAwsReportDetailsTable(&report), - complianceReportSummaryTable(report.Summary), - recommendations, - filteredOutput, - ), - ) - return nil - }, - } - - // complianceAwsDisableReportCmd represents the disable-report sub-command inside the aws command - // experimental feature - complianceAwsDisableReportCmd = &cobra.Command{ - Use: "disable-report ", - Hidden: true, - Aliases: []string{"disable"}, - Short: "Disable all recommendations for a given report type", - Long: `Disable all recommendations for a given report type. -Supported report types are CIS_1_1 - -To show the current status of recommendations in a report run: - lacework compliance aws status CIS_1_1 - -To disable all recommendations for CIS_1_1 report run: - lacework compliance aws disable CIS_1_1 -`, - PreRunE: func(_ *cobra.Command, args []string) error { - switch args[0] { - case "CIS", "CIS_1_1", "AWS_CIS_S3": - args[0] = "CIS_1_1" - return nil - default: - return errors.New("CIS_1_1 is the only supported report type") - } - }, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - // prompt for changes - proceed, err := complianceAwsDisableReportDisplayChanges() - if err != nil { - return errors.Wrap(err, "unable to confirm disable") - } - if !proceed { - return nil - } - - schema, err := fetchCachedAwsComplianceReportSchema(args[0]) - if err != nil { - return errors.Wrap(err, "unable to get aws compliance report schema") - } - - // set state of all recommendations in this report to disabled - patchReq := api.NewRecommendationV2State(schema, false) - cli.StartProgress("disabling recommendations...") - response, err := cli.LwApi.V2.Recommendations.Aws.Patch(patchReq) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to patch aws recommendations") - } - - var cacheKey = fmt.Sprintf("compliance/aws/schema/%s", "CIS_1_1") - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) - cli.OutputHuman("All recommendations for report %s have been disabled\n", args[0]) - return nil - }, - } - - // complianceAwsEnableReportCmd represents the enable-report sub-command inside the aws command - // experimental feature - complianceAwsEnableReportCmd = &cobra.Command{ - Use: "enable-report ", - Hidden: true, - Aliases: []string{"enable"}, - Short: "Enable all recommendations for a given report type", - Long: `Enable all recommendations for a given report type. -Supported report types are CIS_1_1 - -To show the current status of recommendations in a report run: - lacework compliance aws status CIS_1_1 - -To enable all recommendations for CIS_1_1 report run: - lacework compliance aws enable CIS_1_1 -`, - PreRunE: func(_ *cobra.Command, args []string) error { - switch args[0] { - case "CIS", "CIS_1_1", "AWS_CIS_S3": - args[0] = "CIS_1_1" - return nil - default: - return errors.New("CIS_1_1 is the only supported report type") - } - }, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - - schema, err := fetchCachedAwsComplianceReportSchema(args[0]) - if err != nil { - return errors.Wrap(err, "unable to get aws compliance report schema") - } - - // set state of all recommendations in this report to enabled - patchReq := api.NewRecommendationV2State(schema, true) - cli.StartProgress("enabling recommendations...") - response, err := cli.LwApi.V2.Recommendations.Aws.Patch(patchReq) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to patch aws recommendations") - } - - var cacheKey = fmt.Sprintf("compliance/aws/schema/%s", args[0]) - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) - cli.OutputHuman("All recommendations for report %s have been enabled\n", args[0]) - return nil - }, - } - - // complianceAwsReportStatusCmd represents the report-status sub-command inside the aws command - // experimental feature - complianceAwsReportStatusCmd = &cobra.Command{ - Use: "report-status ", - Hidden: true, - Aliases: []string{"status"}, - Short: "Show the status of recommendations for a given report type", - Long: `Show the status of recommendations for a given report type. -Supported report types are CIS_1_1 - -To show the current status of recommendations in a report run: - lacework compliance aws status CIS_1_1 - -The output from status with the --json flag can be used in the body of PATCH api/v1/external/recommendations/aws - lacework compliance aws status CIS_1_1 --json -`, - PreRunE: func(_ *cobra.Command, args []string) error { - switch args[0] { - case "CIS", "CIS_1_1", "AWS_CIS_S3": - args[0] = "CIS_1_1" - return nil - default: - return errors.New("CIS_1_1 is the only supported report type") - } - }, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var rows [][]string - report, err := fetchCachedAwsComplianceReportSchema(args[0]) - if err != nil { - return errors.Wrap(err, "unable to get Aws compliance report schema") - } - - if cli.JSONOutput() { - return cli.OutputJSON(api.NewRecommendationV2(report)) - } - - for _, r := range report { - rows = append(rows, []string{r.ID, strconv.FormatBool(r.State)}) - } - - cli.OutputHuman(renderOneLineCustomTable(args[0], - renderCustomTable([]string{}, rows, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - )) - return nil - }, - } - - // complianceAwsSearchCmd represents the search inside the aws command - complianceAwsSearchCmd = &cobra.Command{ - Use: "search ", - Short: "Search for all known violations of a given resource arn", - Long: `Search for all known violations of a given resource arn.`, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress(fmt.Sprintf("Searching accounts for resource '%s'...", args[0])) - var ( - now = time.Now().UTC() - before = now.AddDate(0, 0, -7) // last 7 days - awsInventorySearchResponse api.InventoryAwsResponse - filter = api.InventorySearch{ - SearchFilter: api.SearchFilter{ - Filters: []api.Filter{{ - Expression: "eq", - Field: "urn", - Value: args[0], - }}, - TimeFilter: &api.TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - }, - Dataset: api.AwsInventoryDataset, - Csp: api.AwsInventoryType, - } - ) - err := api.WindowedSearchFirst( - cli.LwApi.V2.Inventory.Search, api.V2ApiMaxSearchWindowDays, - api.V2ApiMaxSearchHistoryDays, &awsInventorySearchResponse, &filter, - ) - cli.StopProgress() - - if len(awsInventorySearchResponse.Data) == 0 { - cli.OutputHuman("Resource '%s' not found.\n\nTo learn how to configure Lacework with AWS visit "+ - "https://docs.lacework.com/onboarding/category/integrate-lacework-with-aws \n", args[0]) - return nil - } - cli.StopProgress() - if err != nil { - return err - } - - cli.StartProgress(fmt.Sprintf("Searching for compliance violations for '%s'...", args[0])) - var ( - awsComplianceEvaluationSearchResponse api.ComplianceEvaluationAwsResponse - complianceFilter = api.ComplianceEvaluationSearch{ - SearchFilter: api.SearchFilter{ - Filters: []api.Filter{{ - Expression: "eq", - Field: "resource", - Value: args[0], - }}, - TimeFilter: &api.TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - }, - Dataset: api.AwsComplianceEvaluationDataset, - } - ) - - err = api.WindowedSearchFirst( - cli.LwApi.V2.ComplianceEvaluations.Search, api.V2ApiMaxSearchWindowDays, - api.V2ApiMaxSearchHistoryDays, &awsComplianceEvaluationSearchResponse, &complianceFilter, - ) - cli.StopProgress() - if err != nil { - return err - } - - var recommendationIDs []string - var uniqueRecommendations []api.ComplianceEvaluationAws - - for _, recommend := range awsComplianceEvaluationSearchResponse.Data { - if !array.ContainsStr(recommendationIDs, recommend.Id) { - recommendationIDs = append(recommendationIDs, recommend.Id) - uniqueRecommendations = append(uniqueRecommendations, recommend) - } - } - - if len(uniqueRecommendations) == 0 { - cli.OutputHuman("No violations found. Time for %s\n", randomEmoji()) - return nil - } - - // output table - var out [][]string - for _, recommend := range uniqueRecommendations { - out = append(out, []string{ - recommend.Id, - recommend.Account.AccountId, - recommend.Reason, - recommend.Severity, - recommend.Status, - }) - } - - cli.OutputHuman( - renderSimpleTable( - []string{"RECOMMENDATION ID", "ACCOUNT ID", "REASON", "SEVERITY", "STATUS"}, - out, - ), - ) - - return nil - }, - } - - // complianceAwsScanCmd represents the inventory scan inside the aws command - complianceAwsScanCmd = &cobra.Command{ - Use: "scan", - Short: "Scan triggers a new resource inventory scan", - Long: `Scan triggers a new resource inventory scan.`, - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress("Triggering Aws inventory scan") - response, err := cli.LwApi.V2.Inventory.Scan(api.AwsInventoryType) - cli.StopProgress() - - if err != nil { - return err - } - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - - cli.OutputHuman(renderSimpleTable([]string{}, [][]string{ - {"STATUS", response.Data.Status}, - {"DETAILS", response.Data.Details}, - })) - return nil - }, - } -) - -func init() { - // add sub-commands to the aws command - complianceAwsCmd.AddCommand(complianceAwsGetReportCmd) - complianceAwsCmd.AddCommand(complianceAwsListAccountsCmd) - complianceAwsCmd.AddCommand(complianceAwsSearchCmd) - complianceAwsCmd.AddCommand(complianceAwsScanCmd) - - // Experimental Commands - complianceAwsCmd.AddCommand(complianceAwsReportStatusCmd) - complianceAwsCmd.AddCommand(complianceAwsDisableReportCmd) - complianceAwsCmd.AddCommand(complianceAwsEnableReportCmd) - - complianceAwsGetReportCmd.Flags().BoolVar(&compCmdState.Details, "details", false, - "increase details about the compliance report", - ) - - complianceAwsGetReportCmd.Flags().BoolVar(&compCmdState.Pdf, "pdf", false, - "download report in PDF format", - ) - - complianceAwsGetReportCmd.Flags().BoolVar(&compCmdState.Csv, "csv", false, - "output report in CSV format", - ) - // AWS report types: AWS_NIST_CSF, AWS_NIST_800-53_rev5, AWS_HIPAA, NIST_800-53_Rev4, LW_AWS_SEC_ADD_1_0, - //AWS_SOC_Rev2, AWS_PCI_DSS_3.2.1, AWS_CIS_S3, ISO_2700, SOC, AWS_CSA_CCM_4_0_5, PCI, AWS_Cyber_Essentials_2_2, - //AWS_ISO_27001:2013, AWS_CIS_14, AWS_CMMC_1.02, HIPAA, AWS_SOC_2, AWS_CIS_1_4_ISO_IEC_27002_2022, NIST_800-171_Rev2, - //AWS_NIST_800-171_rev2 - complianceAwsGetReportCmd.Flags().StringVar(&compAwsCmdState.Type, "type", "AWS_CIS_14", - fmt.Sprintf(`report type to display, run 'lacework report-definitions list' for more information. -valid types:%s`, prettyPrintReportTypes(api.AwsReportTypes()))) - - // mark report type flag as deprecated - errcheckWARN(complianceAwsGetReportCmd.Flags().MarkDeprecated("type", "use --report_name flag instead")) - - // Run 'lacework report-definition --subtype AWS' for a full list of AWS report names - complianceAwsGetReportCmd.Flags().StringVar(&compAwsCmdState.ReportName, "report_name", - api.ComplianceReportDefaultAws, - "report name to display, run 'lacework report-definitions list' for more information.") - - complianceAwsGetReportCmd.Flags().StringSliceVar(&compCmdState.Category, "category", []string{}, - "filter report details by category (identity-and-access-management, s3, logging...)", - ) - - complianceAwsGetReportCmd.Flags().StringSliceVar(&compCmdState.Service, "service", []string{}, - "filter report details by service (aws:s3, aws:iam, aws:cloudtrail, ...)", - ) - - complianceAwsGetReportCmd.Flags().StringVar(&compCmdState.Severity, "severity", "", - fmt.Sprintf("filter report details by severity threshold (%s)", - lwseverity.ValidSeverities.String()), - ) - - complianceAwsGetReportCmd.Flags().StringVar(&compCmdState.Status, "status", "", - fmt.Sprintf("filter report details by status (%s)", - strings.Join(api.ValidComplianceStatus, ", ")), - ) -} - -// Simple helper to prompt for approval after disable request -func complianceAwsDisableReportCmdPrompt() (int, error) { - message := `WARNING! -Disabling all recommendations for CIS_1_1 will disable the following reports and its corresponding compliance alerts: -AWS CIS Benchmark and S3 Report -AWS HIPAA Report -AWS ISO 27001:2013 Report -AWS NIST 800-171 Report -AWS NIST 800-53 Report -AWS PCI DSS Report -AWS SOC 2 Report -AWS SOC 2 Report Rev2 - -Would you like to proceed? -` - options := []string{ - "Proceed with disable", - "Quit", - } - - var answer int - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: message, - Options: options, - }, - Response: &answer, - }) - - return answer, err -} - -func complianceAwsDisableReportDisplayChanges() (bool, error) { - answer, err := complianceAwsDisableReportCmdPrompt() - if err != nil { - return false, err - } - return answer == 0, nil -} - -func complianceAwsReportDetailsTable(report *api.AwsReport) [][]string { - return [][]string{ - {"Report Type", report.ReportType}, - {"Report Title", report.ReportTitle}, - {"Account ID", report.AccountID}, - {"Account Alias", report.AccountAlias}, - {"Report Time", report.ReportTime.UTC().Format(time.RFC3339)}, - } -} - -type awsAccount struct { - AccountID string `json:"account_id"` - Status string `json:"status"` -} - -func cliListAwsAccounts(awsIntegrations api.CloudAccountsResponse) error { - awsAccounts := make([]awsAccount, 0) - errCount := 0 - jsonOut := struct { - Accounts []awsAccount `json:"aws_accounts"` - }{Accounts: awsAccounts} - - if len(awsIntegrations.Data) == 0 { - if cli.JSONOutput() { - return cli.OutputJSON(jsonOut) - } - - msg := `There are no AWS accounts configured in your account. - -Get started by integrating your AWS accounts to analyze configuration compliance using the command: - - lacework cloud-account aws create - -If you prefer to configure the integration via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Settings > Integrations > Cloud Accounts. -` - cli.OutputHuman(msg, cli.Account) - return nil - } - - for _, i := range awsIntegrations.Data { - var ( - account string - accountData api.AwsCfgData - ) - - awsJson, err := json.Marshal(i.Data) - if err != nil { - continue - } - err = json.Unmarshal(awsJson, &accountData) - if err != nil { - continue - } - - if accountData.AwsAccountID == "" { - errCount++ - cli.Log.Debugf(fmt.Sprintf("unable to find account id for cloud account %s\n", i.IntgGuid)) - continue - } - account = accountData.AwsAccountID - - if containsDuplicateAccountID(awsAccounts, account) { - cli.Log.Warnw("duplicate aws account", "integration_guid", i.IntgGuid, "account", account) - continue - } - awsAccounts = append(awsAccounts, awsAccount{ - AccountID: account, - Status: i.Status(), - }) - } - - if cli.JSONOutput() { - jsonOut.Accounts = awsAccounts - return cli.OutputJSON(jsonOut) - } - - var rows [][]string - for _, acc := range awsAccounts { - rows = append(rows, []string{acc.AccountID, acc.Status}) - } - - cli.OutputHuman(renderSimpleTable([]string{"AWS Account", "Status"}, rows)) - if errCount > 0 { - cli.OutputHuman(fmt.Sprintf("\n unable to find Aws Account ID's for %d integration(s)\n", errCount)) - } - return nil -} - -func containsDuplicateAccountID(awsAccount []awsAccount, accountID string) bool { - for _, value := range awsAccount { - if accountID == value.AccountID { - return true - } - } - return false -} - -func fetchCachedAwsComplianceReportSchema(reportType string) (response []api.RecV2, err error) { - var cacheKey = fmt.Sprintf("compliance/aws/schema/%s", reportType) - expired := cli.ReadCachedAsset(cacheKey, &response) - if expired { - cli.StartProgress("Fetching compliance report schema...") - response, err = cli.LwApi.V2.Recommendations.Aws.GetReport(reportType) - cli.StopProgress() - if err != nil { - return nil, errors.Wrap(err, "unable to get aws compliance report schema") - } - if len(response) == 0 { - return nil, errors.New("no data found in the report") - } - - // write previous state to cache, allowing for revert to previous state - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response) - } - return -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_azure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_azure.go deleted file mode 100644 index c2fd18dc6..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_azure.go +++ /dev/null @@ -1,765 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "sort" - "strconv" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/lwseverity" -) - -var ( - compAzCmdState = struct { - Type string - ReportName string - }{ReportName: api.ComplianceReportDefaultAzure} - - // complianceAzureListSubsCmd represents the list-subscriptions sub-command inside the azure command - complianceAzureListSubsCmd = &cobra.Command{ - Use: "list-subscriptions", - Aliases: []string{"list-subs"}, - Short: "List subscriptions ``", - Long: `List all Azure subscriptions for Tenant. - -Use the following command to list all Azure Tenants configured in your account: - - lacework compliance az list`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - var ( - response, err = cli.LwApi.V2.Configs.Azure.ListSubscriptions(args[0]) - cliCompAzureSubscriptions []cliComplianceAzureInfo - ) - if err != nil { - return errors.Wrap(err, "unable to list azure subscriptions") - } - - if len(response.Data) == 0 { - cli.OutputHuman("There are no azure subscriptions found for tenant %s\n", args[0]) - return nil - } - - for _, az := range response.Data { - cliCompAzureSubscriptions = append( - cliCompAzureSubscriptions, - splitAzureSubscriptionsApiResponse(az), - ) - } - - if cli.JSONOutput() { - return cli.OutputJSON(cliCompAzureSubscriptions) - } - - rows := [][]string{} - for _, subscriptionList := range cliCompAzureSubscriptions { - for _, subscription := range subscriptionList.Subscriptions { - rows = append(rows, []string{subscription.ID, subscription.Alias}) - } - } - - cli.OutputHuman(renderSimpleTable( - []string{"Subscription ID", "Subscription Alias"}, rows), - ) - return nil - }, - } - - // complianceAzureListTenantsCmd represents the list-tenants sub-command inside the azure command - complianceAzureListTenantsCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"list-tenants", "ls"}, - Short: "List Azure tenants and subscriptions", - Long: `List all Azure tenants and subscriptions configured in your account.`, - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - cli.StartProgress("Fetching list of configured Azure tenants...") - response, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AzureCfgCloudAccount) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get azure integrations") - } - - if len(response.Data) == 0 { - cli.OutputHuman("There are no azure cloud accounts configured in your account\n") - return nil - } - - return cliListTenantsAndSubscriptions(response) - }, - } - - // complianceAzureGetReportCmd represents the get-report sub-command inside the azure command - complianceAzureGetReportCmd = &cobra.Command{ - Use: "get-report ", - Aliases: []string{"get", "show"}, - PreRunE: func(cmd *cobra.Command, args []string) error { - if compCmdState.Csv { - cli.EnableCSVOutput() - } - - if len(args) > 2 { - compCmdState.RecommendationID = args[2] - } - - // ensure we cannot have both --type and --report_name flags - if cmd.Flags().Changed("type") && cmd.Flags().Changed("report_name") { - return errors.New("'--type' and '--report_name' flags cannot be used together") - } - - // validate report_name - if cmd.Flags().Changed("report_name") { - return validReportName(api.ReportDefinitionSubTypeAzure.String(), compAzCmdState.ReportName) - } - - if cmd.Flags().Changed("type") && !array.ContainsStr(api.AzureReportTypes(), compAzCmdState.Type) { - return errors.Errorf("supported report types are: %s", strings.Join(api.AzureReportTypes(), ", ")) - } - return nil - }, - Short: "Get the latest Azure compliance report", - Long: `Get the latest Azure compliance assessment report, these reports run on a regular schedule, -typically once a day. The available report formats are human-readable (default), json and pdf. - -To list all Azure tenants and subscriptions configured in your account: - - lacework compliance azure list - -To show recommendation details and affected resources for a recommendation id: - - lacework compliance azure get-report [recommendation_id] - -To retrieve a specific report by its report name: - - lacework compliance azure get-report --report_name 'Azure CIS 1.3.1 Report' -`, - Args: cobra.RangeArgs(2, 3), - RunE: func(cmd *cobra.Command, args []string) error { - var ( - // clean tenantID and subscriptionID if they were provided - // with an Alias in between parentheses - tenantID, _ = splitIDAndAlias(args[0]) - subscriptionID, _ = splitIDAndAlias(args[1]) - config = api.AzureReportConfig{ - TenantID: tenantID, - SubscriptionID: subscriptionID, - // Default config is report_name - Value: compAzCmdState.ReportName, - Parameter: api.ReportFilterName, - } - ) - - // if --type flag is used, set the report config to type - if cmd.Flags().Changed("type") { - reportType, err := api.NewAzureReportType(compAzCmdState.Type) - if err != nil { - return errors.Errorf("invalid report type %q", compAzCmdState.Type) - } - config.Parameter = api.ReportFilterType - config.Value = reportType.String() - } - - if compCmdState.Pdf { - pdfName := fmt.Sprintf( - "%s_Report_%s_%s_%s_%s.pdf", - config.Value, - config.TenantID, - config.SubscriptionID, - cli.Account, time.Now().Format("20060102150405"), - ) - - cli.StartProgress("Downloading compliance report...") - err := cli.LwApi.V2.Reports.Azure.DownloadPDF(pdfName, config) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get azure pdf compliance report") - } - - cli.OutputHuman("The Azure compliance report was downloaded at '%s'\n", pdfName) - return nil - } - - if compCmdState.Severity != "" { - if !lwseverity.IsValid(compCmdState.Severity) { - return errors.Errorf("the severity %s is not valid, use one of %s", - compCmdState.Severity, lwseverity.ValidSeverities.String(), - ) - } - } - if compCmdState.Status != "" { - if !array.ContainsStr(api.ValidComplianceStatus, compCmdState.Status) { - return errors.Errorf("the status %s is not valid, use one of %s", - compCmdState.Status, strings.Join(api.ValidComplianceStatus, ", "), - ) - } - } - - var ( - report api.AzureReport - cacheKey = fmt.Sprintf("compliance/azure/v2/%s/%s/%s", - config.TenantID, config.SubscriptionID, config.Value) - ) - expired := cli.ReadCachedAsset(cacheKey, &report) - if expired { - cli.StartProgress("Getting compliance report...") - response, err := cli.LwApi.V2.Reports.Azure.Get(config) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get azure compliance report") - } - - if len(response.Data) == 0 { - return errors.New("no data found in the report") - } - - report = response.Data[0] - - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), report) - } - - filteredOutput := "" - - if complianceFiltersEnabled() { - report.Recommendations, filteredOutput = filterRecommendations(report.Recommendations) - } - - if cli.JSONOutput() && compCmdState.RecommendationID == "" { - return cli.OutputJSON(report) - } - - if cli.CSVOutput() { - recommendations := complianceCSVReportRecommendationsTable( - &complianceCSVReportDetails{ - AccountName: report.SubscriptionName, - AccountID: report.SubscriptionID, - TenantName: report.TenantName, - TenantID: report.TenantID, - ReportType: report.ReportType, - ReportTime: report.ReportTime, - Recommendations: report.Recommendations, - }, - ) - - return cli.OutputCSV( - []string{"Report_Type", "Report_Time", "Tenant", - "Subscription", "Section", "ID", "Recommendation", - "Status", "Severity", "Resource", "Region", "Reason"}, - recommendations, - ) - } - - // If RecommendationID is provided, output resources matching that id - if compCmdState.RecommendationID != "" { - return outputResourcesByRecommendationID(report) - } - - recommendations := complianceReportRecommendationsTable(report.Recommendations) - cli.OutputHuman("\n") - cli.OutputHuman( - buildComplianceReportTable( - complianceAzureReportDetailsTable(&report), - complianceReportSummaryTable(report.Summary), - recommendations, - filteredOutput, - ), - ) - return nil - }, - } - - // complianceAzureDisableReportCmd represents the disable-report sub-command inside the azure command - // experimental feature - complianceAzureDisableReportCmd = &cobra.Command{ - Use: "disable-report ", - Hidden: true, - Aliases: []string{"disable"}, - Short: "Disable all recommendations for a given report type", - Long: `Disable all recommendations for a given report type. -Supported report types are: CIS_1_0, CIS_1_3_1 - -To show the current status of recommendations in a report run: - lacework compliance azure status CIS_1_3_1 - -To disable all recommendations for CIS_1_3_1 report run: - lacework compliance azure disable CIS_1_3_1 -`, - PreRunE: func(_ *cobra.Command, args []string) error { - switch args[0] { - case "CIS", "CIS_1_0", "AZURE_CIS": - args[0] = "CIS_1_0" - return nil - case "CIS_1_3_1", "AZURE_CIS_131": - args[0] = "CIS_1_3_1" - return nil - default: - return errors.New("supported report types are: CIS_1_0, CIS_1_3_1") - } - }, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - // prompt for changes - proceed, err := complianceAzureDisableReportDisplayChanges(args[0]) - if err != nil { - return errors.Wrap(err, "unable to confirm disable") - } - if !proceed { - return nil - } - - schema, err := fetchCachedAzureComplianceReportSchema(args[0]) - if err != nil { - return errors.Wrap(err, "unable to fetch azure compliance report schema") - } - - // set state of all recommendations in this report to disabled - patchReq := api.NewRecommendationV2State(schema, false) - cli.StartProgress("disabling recommendations...") - response, err := cli.LwApi.V2.Recommendations.Azure.Patch(patchReq) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to patch azure recommendations") - } - - var cacheKey = fmt.Sprintf("compliance/azure/schema/%s", args[0]) - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) - cli.OutputHuman("All recommendations for report %s have been disabled\n", args[0]) - return nil - }, - } - - // complianceAzureEnableReportCmd represents the enable-report sub-command inside the azure command - // experimental feature - complianceAzureEnableReportCmd = &cobra.Command{ - Use: "enable-report ", - Hidden: true, - Aliases: []string{"enable"}, - Short: "Enable all recommendations for a given report type", - Long: `Enable all recommendations for a given report type. -Supported report types are: CIS_1_0, CIS_1_3_1 - -To show the current status of recommendations in a report run: - lacework compliance azure status CIS_1_3_1 - -To enable all recommendations for CIS_1_3_1 report run: - lacework compliance azure enable CIS_1_3_1 -`, - PreRunE: func(_ *cobra.Command, args []string) error { - switch args[0] { - case "CIS", "CIS_1_0", "AZURE_CIS": - args[0] = "CIS_1_0" - return nil - case "CIS_1_3_1", "AZURE_CIS_131": - args[0] = "CIS_1_3_1" - return nil - default: - return errors.New("supported report types are: CIS_1_0, CIS_1_3_1") - } - }, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - - schema, err := fetchCachedAzureComplianceReportSchema(args[0]) - if err != nil { - return errors.Wrap(err, "unable to fetch azure compliance report schema") - } - - // set state of all recommendations in this report to enabled - patchReq := api.NewRecommendationV2State(schema, true) - cli.StartProgress("enabling recommendations...") - response, err := cli.LwApi.V2.Recommendations.Azure.Patch(patchReq) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to patch azure recommendations") - } - - var cacheKey = fmt.Sprintf("compliance/azure/schema/%s", args[0]) - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) - cli.OutputHuman("All recommendations for report %s have been enabled\n", args[0]) - return nil - }, - } - - // complianceAzureReportStatusCmd represents the report-status sub-command inside the azure command - // experimental feature - complianceAzureReportStatusCmd = &cobra.Command{ - Use: "report-status ", - Hidden: true, - Aliases: []string{"status"}, - Short: "Show the status of recommendations for a given report type", - Long: `Show the status of recommendations for a given report type. -Supported report types are: CIS_1_0, CIS_1_3_1 - -To show the current status of recommendations in a report run: - lacework compliance azure status CIS_1_3_1 - -The output from status with the --json flag can be used in the body of PATCH api/v1/external/recommendations/azure - lacework compliance azure status CIS_1_3_1 --json -`, - PreRunE: func(_ *cobra.Command, args []string) error { - switch args[0] { - case "CIS", "CIS_1_0", "AZURE_CIS": - args[0] = "CIS_1_0" - return nil - case "CIS_1_3_1", "AZURE_CIS_131": - args[0] = "CIS_1_3_1" - return nil - default: - return errors.New("supported report types are: CIS_1_0, CIS_1_3_1") - } - }, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var rows [][]string - report, err := fetchCachedAzureComplianceReportSchema(args[0]) - if err != nil { - return errors.Wrap(err, "unable to fetch azure compliance report schema") - } - - if cli.JSONOutput() { - return cli.OutputJSON(api.NewRecommendationV2(report)) - } - - for _, r := range report { - rows = append(rows, []string{r.ID, strconv.FormatBool(r.State)}) - } - - cli.OutputHuman(renderOneLineCustomTable(args[0], - renderCustomTable([]string{}, rows, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - )) - return nil - }, - } - // complianceAzureScanCmd represents the inventory scan inside the azure command - complianceAzureScanCmd = &cobra.Command{ - Use: "scan", - Short: "Scan triggers a new resource inventory scan", - Long: `Scan triggers a new resource inventory scan.`, - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress("Triggering Azure inventory scan") - response, err := cli.LwApi.V2.Inventory.Scan(api.AzureInventoryType) - cli.StopProgress() - - if err != nil { - return err - } - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - - cli.OutputHuman(renderSimpleTable([]string{}, [][]string{ - {"STATUS", response.Data.Status}, - {"DETAILS", response.Data.Details}, - })) - return nil - }, - } -) - -func init() { - // add sub-commands to the azure command - complianceAzureCmd.AddCommand(complianceAzureListSubsCmd) - complianceAzureCmd.AddCommand(complianceAzureListTenantsCmd) - complianceAzureCmd.AddCommand(complianceAzureGetReportCmd) - complianceAzureCmd.AddCommand(complianceAzureScanCmd) - - // Experimental Commands - complianceAzureCmd.AddCommand(complianceAzureReportStatusCmd) - complianceAzureCmd.AddCommand(complianceAzureDisableReportCmd) - complianceAzureCmd.AddCommand(complianceAzureEnableReportCmd) - - complianceAzureGetReportCmd.Flags().BoolVar(&compCmdState.Details, "details", false, - "increase details about the compliance report", - ) - complianceAzureGetReportCmd.Flags().BoolVar(&compCmdState.Pdf, "pdf", false, - "download report in PDF format", - ) - - // Output the report in CSV format - complianceAzureGetReportCmd.Flags().BoolVar(&compCmdState.Csv, "csv", false, - "output report in CSV format", - ) - - // Azure report types: AZURE_CIS_131, AZURE_NIST_800_171_REV2, AZURE_NIST_800_53_REV5, AZURE_NIST_CSF, - //AZURE_PCI, AZURE_SOC_Rev2, AZURE_ISO_27001, AZURE_SOC, AZURE_HIPAA, AZURE_CIS, AZURE_PCI_Rev2 - complianceAzureGetReportCmd.Flags().StringVar(&compAzCmdState.Type, "type", "AZURE_CIS_131", - fmt.Sprintf(`report type to display, run 'lacework report-definitions list' for more information. -valid types:%s`, prettyPrintReportTypes(api.AzureReportTypes())), - ) - - // mark report type flag as deprecated - errcheckWARN(complianceAzureGetReportCmd.Flags().MarkDeprecated("type", "use --report_name flag instead")) - - // Run 'lacework report-definition --subtype Azure' for a full list of Azure report names - complianceAzureGetReportCmd.Flags().StringVar(&compAzCmdState.ReportName, "report_name", - api.ComplianceReportDefaultAzure, - "report name to display, run 'lacework report-definitions list' for more information.") - - complianceAzureGetReportCmd.Flags().StringSliceVar(&compCmdState.Category, "category", []string{}, - "filter report details by category (networking, storage, ...)", - ) - - complianceAzureGetReportCmd.Flags().StringSliceVar(&compCmdState.Service, "service", []string{}, - "filter report details by service (azure:ms:storage, azure:ms:sql, azure:ms:network, ...)", - ) - - complianceAzureGetReportCmd.Flags().StringVar(&compCmdState.Severity, "severity", "", - fmt.Sprintf("filter report details by severity threshold (%s)", - lwseverity.ValidSeverities.String()), - ) - - complianceAzureGetReportCmd.Flags().StringVar(&compCmdState.Status, "status", "", - fmt.Sprintf("filter report details by status (%s)", - strings.Join(api.ValidComplianceStatus, ", ")), - ) -} - -// Simple helper to prompt for approval after disable request -func complianceAzureDisableReportCmdPrompt(arg string) (int, error) { - var message string - switch arg { - case "CIS", "CIS_1_0", "AZURE_CIS": - message = `WARNING! -Disabling all recommendations for CIS_1_0 will disable the following reports and its corresponding compliance alerts: - AZURE CIS Benchmark - PCI Benchmark - SOC 2 Report - - Would you like to proceed? - ` - case "CIS_1_3_1", "AZURE_CIS_131": - message = `WARNING! -Disabling all recommendations for CIS_1_3_1 will disable the following reports and its corresponding compliance alerts: - AZURE CIS Benchmark 1.3.1 - PCI Benchmark Rev2 - SOC 2 Report Rev2 - HIPAA Report - ISO27001 Report (+ couple CIS 1.0 controls) - NIST 800-171 rev2 Report - NIST 800-53 rev5 Report - NIST CSF rev2 Report - - Would you like to proceed? - ` - } - - options := []string{ - "Proceed with disable", - "Quit", - } - - var answer int - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: message, - Options: options, - }, - Response: &answer, - }) - - return answer, err -} - -func complianceAzureDisableReportDisplayChanges(arg string) (bool, error) { - answer, err := complianceAzureDisableReportCmdPrompt(arg) - if err != nil { - return false, err - } - return answer == 0, nil -} - -func complianceAzureReportDetailsTable(report *api.AzureReport) [][]string { - return [][]string{ - {"Report Type", report.ReportType}, - {"Report Title", report.ReportTitle}, - {"Tenant ID", report.TenantID}, - {"Tenant Name", report.TenantName}, - {"Subscription ID", report.SubscriptionID}, - {"Subscription Name", report.SubscriptionName}, - {"Report Time", report.ReportTime.UTC().Format(time.RFC3339)}, - } -} - -func splitAzureSubscriptionsApiResponse(azInfo api.AzureConfigData) cliComplianceAzureInfo { - var ( - tenantID, tenantAlias = splitIDAndAlias(azInfo.Tenant) - cliAzureInfo = cliComplianceAzureInfo{ - Tenant: cliComplianceIDAlias{tenantID, tenantAlias}, - Subscriptions: make([]cliComplianceIDAlias, 0), - } - ) - - for _, subscription := range azInfo.Subscriptions { - id, alias := splitIDAndAlias(subscription) - cliAzureInfo.Subscriptions = append(cliAzureInfo.Subscriptions, cliComplianceIDAlias{id, alias}) - } - - return cliAzureInfo -} - -type cliComplianceAzureInfo struct { - Tenant cliComplianceIDAlias `json:"tenant"` - Subscriptions []cliComplianceIDAlias `json:"subscriptions"` -} - -func cliListTenantsAndSubscriptions(azureIntegrations api.CloudAccountsResponse) error { - jsonOut := struct { - Subscriptions []azureSubscription `json:"azure_subscriptions"` - }{Subscriptions: make([]azureSubscription, 0)} - - if len(azureIntegrations.Data) == 0 { - if cli.JSONOutput() { - return cli.OutputJSON(jsonOut) - } - - msg := `There are no Azure Tenants configured in your account. - -Get started by integrating your Azure Tenants to analyze configuration compliance using the command: - - lacework cloud-account create - -If you prefer to configure the integration via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Settings > Integrations > Cloud Accounts. -` - cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) - return nil - } - - if cli.JSONOutput() { - jsonOut.Subscriptions = extractAzureSubscriptions(azureIntegrations) - return cli.OutputJSON(jsonOut) - } - - var rows [][]string - for _, az := range extractAzureSubscriptions(azureIntegrations) { - rows = append(rows, []string{az.TenantID, az.SubscriptionID, az.Status}) - } - - cli.OutputHuman(renderSimpleTable([]string{"Azure Tenant", "Azure Subscription", "Status"}, rows)) - return nil -} - -type azureSubscription struct { - TenantID string `json:"tenant_id"` - SubscriptionID string `json:"subscription_id"` - Status string `json:"status"` -} - -func extractAzureSubscriptions(response api.CloudAccountsResponse) []azureSubscription { - var azureSubscriptions []azureSubscription - var azureData api.AzureCfgData - if len(response.Data) == 0 { - return azureSubscriptions - } - - for _, az := range response.Data { - azJson, err := json.Marshal(az.Data) - if err != nil { - continue - } - - err = json.Unmarshal(azJson, &azureData) - if err != nil { - continue - } - // fetch the subscription ids from tenant id - azureSubscriptions = append(azureSubscriptions, getAzureSubscriptions(azureData.TenantID, az.Status())...) - } - - sort.Slice(azureSubscriptions, func(i, j int) bool { - switch strings.Compare(azureSubscriptions[i].TenantID, azureSubscriptions[j].TenantID) { - case -1: - return true - case 1: - return false - } - return azureSubscriptions[i].SubscriptionID < azureSubscriptions[j].SubscriptionID - }) - - return azureSubscriptions -} - -func getAzureSubscriptions(tenantID, status string) []azureSubscription { - var subs []azureSubscription - cli.StartProgress(fmt.Sprintf("Fetching subscriptions from tenant (%s)...", tenantID)) - subsResponse, err := cli.LwApi.V2.Configs.Azure.ListSubscriptions(tenantID) - cli.StopProgress() - if err != nil { - cli.Log.Warnw("unable to list azure subscriptions", "tenant_id", tenantID, "error", err.Error()) - return subs - } - for _, subsRes := range subsResponse.Data { - for _, subRes := range subsRes.Subscriptions { - subscriptionID, _ := splitIDAndAlias(subRes) - subs = append(subs, azureSubscription{ - TenantID: tenantID, - SubscriptionID: subscriptionID, - Status: status, - }) - } - } - return subs -} - -func fetchCachedAzureComplianceReportSchema(reportType string) (response []api.RecV2, err error) { - var cacheKey = fmt.Sprintf("compliance/azure/schema/%s", reportType) - - expired := cli.ReadCachedAsset(cacheKey, &response) - if expired { - cli.StartProgress("Fetching compliance report schema...") - response, err = cli.LwApi.V2.Recommendations.Azure.GetReport(reportType) - cli.StopProgress() - if err != nil { - return nil, errors.Wrap(err, "unable to get Azure compliance report schema") - } - - if len(response) == 0 { - return nil, errors.New("no data found in the report") - } - - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response) - } - return -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_gcp.go deleted file mode 100644 index f950045bd..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/compliance_gcp.go +++ /dev/null @@ -1,845 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "regexp" - "sort" - "strconv" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/olekukonko/tablewriter" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/lwseverity" -) - -var ( - compGcpCmdState = struct { - Type string - ReportName string - }{ReportName: api.ComplianceReportDefaultGcp} - - // complianceGcpListCmd represents the list sub-command inside the gcp command - complianceGcpListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List gcp projects and organizations", - Long: `List all GCP projects and organization IDs.`, - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress("Fetching list of configured GCP projects...") - response, err := cli.LwApi.V2.CloudAccounts.ListByType(api.GcpCfgCloudAccount) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to list gcp projects/organizations") - } - - cli.StartProgress("Fetching GCP config data...") - gcpData, err := cli.LwApi.V2.Configs.Gcp.List() - cli.StopProgress() - if err != nil { - return err - } - - return cliListGcpProjectsAndOrgs(response, gcpData) - }, - } - - // complianceGcpListProjCmd represents the list-projects sub-command inside the gcp command - complianceGcpListProjCmd = &cobra.Command{ - Use: "list-projects ", - Aliases: []string{"list-proj"}, - Short: "List projects from an organization", - Long: `List all GCP projects from the provided organization ID. - -Use the following command to list all GCP integrations in your account: - - lacework cloud-account list --type GcpCfg - -Then, select one GUID from an integration and visualize its details using the command: - - lacework cloud-account show -`, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var ( - orgID, _ = splitIDAndAlias(args[0]) - response, err = cli.LwApi.V2.Configs.Gcp.ListProjects(orgID) - ) - if err != nil { - return errors.Wrap(err, "unable to list gcp projects") - } - - if len(response.Data) == 0 { - return errors.New("no data found for the provided organization") - } - - // ALLY-431 Workaround to split the Project ID and Project Alias - // ultimately, we need to fix this in the API response - cliCompGcpProjects := splitGcpProjectsApiResponse(response.Data[0]) - - if cli.JSONOutput() { - return cli.OutputJSON(cliCompGcpProjects) - } - - rows := [][]string{} - for _, project := range cliCompGcpProjects.Projects { - rows = append(rows, []string{project.ID, project.Alias}) - } - cli.OutputHuman(renderSimpleTable([]string{"Project ID", "Project Alias"}, rows)) - return nil - }, - } - - // complianceGcpGetReportCmd represents the get-report sub-command inside the gcp command - complianceGcpGetReportCmd = &cobra.Command{ - Use: "get-report ", - Aliases: []string{"get", "show"}, - PreRunE: func(cmd *cobra.Command, args []string) error { - if compCmdState.Csv { - cli.EnableCSVOutput() - } - - if len(args) > 2 { - compCmdState.RecommendationID = args[2] - } - - // ensure we cannot have both --type and --report_name flags - if cmd.Flags().Changed("type") && cmd.Flags().Changed("report_name") { - return errors.New("'--type' and '--report_name' flags cannot be used together") - } - - // validate report_name - if cmd.Flags().Changed("report_name") { - return validReportName(api.ReportDefinitionSubTypeGcp.String(), compGcpCmdState.ReportName) - } - - if cmd.Flags().Changed("type") && !array.ContainsStr(api.GcpReportTypes(), compGcpCmdState.Type) { - return errors.Errorf("supported report types are: %s", strings.Join(api.GcpReportTypes(), ", ")) - } - - return nil - }, - Short: "Get the latest GCP compliance report", - Long: `Get the latest compliance assessment report, these reports run on a regular schedule, -typically once a day. The available report formats are human-readable (default), json and pdf. - -To list all GCP projects and organizations configured in your account: - - lacework compliance gcp list - -To show recommendation details and affected resources for a recommendation id: - - lacework compliance gcp get-report [recommendation_id] - -To retrieve a specific report by its report name: - - lacework compliance gcp get-report --report_name 'GCP Cybersecurity Maturity' -`, - Args: cobra.RangeArgs(2, 3), - RunE: func(cmd *cobra.Command, args []string) error { - var ( - // clean projectID and orgID if they were provided - // with an Alias in between parentheses - orgID, _ = splitIDAndAlias(args[0]) - projectID, _ = splitIDAndAlias(args[1]) - config = api.GcpReportConfig{ - OrganizationID: orgID, - ProjectID: projectID, - // Default config is report_name - Value: compGcpCmdState.ReportName, - Parameter: api.ReportFilterName, - } - ) - - // if --type flag is used, set the report config to type - if cmd.Flags().Changed("type") { - reportType, err := api.NewGcpReportType(compGcpCmdState.Type) - if err != nil { - return errors.Errorf("invalid report type %q", compGcpCmdState.Type) - } - config.Parameter = api.ReportFilterType - config.Value = reportType.String() - } - - if compCmdState.Pdf { - pdfName := fmt.Sprintf( - "%s_Report_%s_%s_%s_%s.pdf", - config.Value, - config.OrganizationID, - config.ProjectID, - cli.Account, time.Now().Format("20060102150405"), - ) - - cli.StartProgress(" Downloading compliance report...") - err := cli.LwApi.V2.Reports.Gcp.DownloadPDF(pdfName, config) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get gcp pdf compliance report") - } - - cli.OutputHuman("The GCP compliance report was downloaded at '%s'\n", pdfName) - return nil - } - - if compCmdState.Severity != "" { - if !lwseverity.IsValid(compCmdState.Severity) { - return errors.Errorf("the severity %s is not valid, use one of %s", - compCmdState.Severity, lwseverity.ValidSeverities.String(), - ) - } - } - if compCmdState.Status != "" { - if !array.ContainsStr(api.ValidComplianceStatus, compCmdState.Status) { - return errors.Errorf("the status %s is not valid, use one of %s", - compCmdState.Status, strings.Join(api.ValidComplianceStatus, ", "), - ) - } - } - - // diagonals are file separators and therefore we need to clean the organization - // ID if it is "n/a" or we will create two directories "n/a/..." - orgIDForCache := config.OrganizationID - if config.OrganizationID == "n/a" { - orgIDForCache = "not_applicable" - } - - var ( - report api.GcpReport - cacheKey = fmt.Sprintf("compliance/google/v2/%s/%s/%s", - orgIDForCache, config.ProjectID, config.Value) - ) - expired := cli.ReadCachedAsset(cacheKey, &report) - if expired { - cli.StartProgress(" Getting compliance report...") - response, err := cli.LwApi.V2.Reports.Gcp.Get(config) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get gcp compliance report") - } - - if len(response.Data) == 0 { - return errors.New("no data found in the report") - } - - report = response.Data[0] - - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), report) - } - - filteredOutput := "" - - if complianceFiltersEnabled() { - report.Recommendations, filteredOutput = filterRecommendations(report.Recommendations) - } - - if cli.JSONOutput() && compCmdState.RecommendationID == "" { - return cli.OutputJSON(report) - } - - if cli.CSVOutput() { - recommendations := complianceCSVReportRecommendationsTable( - &complianceCSVReportDetails{ - TenantName: report.OrganizationName, - TenantID: report.OrganizationID, - AccountName: report.ProjectName, - AccountID: report.ProjectID, - ReportType: report.ReportType, - ReportTime: report.ReportTime, - Recommendations: report.Recommendations, - }, - ) - - return cli.OutputCSV( - []string{"Report_Type", "Report_Time", "Organization", - "Project", "Section", "ID", "Recommendation", "Status", - "Severity", "Resource", "Region", "Reason"}, - recommendations, - ) - } - - // If RecommendationID is provided, output resources matching that id - if compCmdState.RecommendationID != "" { - return outputResourcesByRecommendationID(report) - } - - recommendations := complianceReportRecommendationsTable(report.Recommendations) - cli.OutputHuman("\n") - cli.OutputHuman( - buildComplianceReportTable( - complianceGcpReportDetailsTable(&report), - complianceReportSummaryTable(report.Summary), - recommendations, - filteredOutput, - ), - ) - return nil - }, - } - - // complianceGcpDisableReportCmd represents the disable-report sub-command inside the gcp command - // experimental feature - complianceGcpDisableReportCmd = &cobra.Command{ - Use: "disable-report ", - Hidden: true, - Aliases: []string{"disable"}, - Short: "Disable all recommendations for a given report type", - Long: `Disable all recommendations for a given report type. -Supported report types are: CIS_1_0, CIS_1_2 - -To show the current status of recommendations in a report run: - lacework compliance gcp status CIS_1_2 - -To disable all recommendations for CIS_1_2 report run: - lacework compliance gcp disable CIS_1_2 -`, - PreRunE: func(_ *cobra.Command, args []string) error { - switch args[0] { - case "CIS", "CIS_1_0", "GCP_CIS": - args[0] = "CIS_1_0" - return nil - case "CIS_1_2", "GCP_CIS12": - args[0] = "CIS_1_2" - return nil - default: - return errors.New("supported report types are: CIS_1_0, CIS_1_2") - } - }, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - // prompt for changes - proceed, err := complianceGcpDisableReportDisplayChanges(args[0]) - if err != nil { - return errors.Wrap(err, "unable to confirm disable") - } - if !proceed { - return nil - } - - schema, err := fetchCachedGcpComplianceReportSchema(args[0]) - if err != nil { - return errors.Wrap(err, "unable to fetch gcp compliance report schema") - } - - // set state of all recommendations in this report to disabled - patchReq := api.NewRecommendationV2State(schema, false) - cli.StartProgress("disabling recommendations...") - response, err := cli.LwApi.V2.Recommendations.Gcp.Patch(patchReq) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to patch gcp recommendations") - } - - var cacheKey = fmt.Sprintf("compliance/gcp/schema/%s", args[0]) - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) - cli.OutputHuman("All recommendations for report %s have been disabled\n", args[0]) - return nil - }, - } - - // complianceGcpEnableReportCmd represents the enable-report sub-command inside the gcp command - // experimental feature - complianceGcpEnableReportCmd = &cobra.Command{ - Use: "enable-report ", - Hidden: true, - Aliases: []string{"enable"}, - Short: "Enable all recommendations for a given report type", - Long: `Enable all recommendations for a given report type. -Supported report types are: CIS_1_0, CIS_1_2 - -To show the current status of recommendations in a report run: - lacework compliance gcp status CIS_1_2 - -To enable all recommendations for CIS_1_2 report run: - lacework compliance gcp enable CIS_1_2 -`, - PreRunE: func(_ *cobra.Command, args []string) error { - switch args[0] { - case "CIS", "CIS_1_0", "GCP_CIS": - args[0] = "CIS_1_0" - return nil - case "CIS_1_2", "GCP_CIS12": - args[0] = "CIS_1_2" - return nil - default: - return errors.New("supported report types are: CIS_1_0, CIS_1_2") - } - }, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - - schema, err := fetchCachedGcpComplianceReportSchema(args[0]) - if err != nil { - return errors.Wrap(err, "unable to fetch gcp compliance report schema") - } - - // set state of all recommendations in this report to enabled - patchReq := api.NewRecommendationV2State(schema, true) - cli.StartProgress("enabling recommendations...") - response, err := cli.LwApi.V2.Recommendations.Gcp.Patch(patchReq) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to patch gcp recommendations") - } - - var cacheKey = fmt.Sprintf("compliance/gcp/schema/%s", args[0]) - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response.RecommendationList()) - cli.OutputHuman("All recommendations for report %s have been enabled\n", args[0]) - return nil - }, - } - - // complianceGcpReportStatusCmd represents the report-status sub-command inside the gcp command - // experimental feature - complianceGcpReportStatusCmd = &cobra.Command{ - Use: "report-status ", - Hidden: true, - Aliases: []string{"status"}, - Short: "Show the status of recommendations for a given report type", - Long: `Show the status of recommendations for a given report type. -Supported report types are: CIS_1_0, CIS_1_2 - -To show the current status of recommendations in a report run: - lacework compliance gcp status CIS_1_2 - -The output from status with the --json flag can be used in the body of PATCH api/v1/external/recommendations/gcp - lacework compliance gcp status CIS_1_2 --json -`, - PreRunE: func(_ *cobra.Command, args []string) error { - switch args[0] { - case "CIS", "CIS_1_0", "GCP_CIS": - args[0] = "CIS_1_0" - return nil - case "CIS_1_2", "GCP_CIS12": - args[0] = "CIS_1_2" - return nil - default: - return errors.New("supported report types are: CIS_1_0, CIS_1_2") - } - }, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var rows [][]string - report, err := fetchCachedGcpComplianceReportSchema(args[0]) - if err != nil { - return errors.Wrap(err, "unable to fetch gcp compliance report schema") - } - - if cli.JSONOutput() { - return cli.OutputJSON(api.NewRecommendationV2(report)) - } - - for _, r := range report { - rows = append(rows, []string{r.ID, strconv.FormatBool(r.State)}) - } - - cli.OutputHuman(renderOneLineCustomTable(args[0], - renderCustomTable([]string{}, rows, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - )) - return nil - }, - } - - // complianceGcpScanCmd represents the inventory scan inside the gcp command - complianceGcpScanCmd = &cobra.Command{ - Use: "scan", - Short: "Scan triggers a new resource inventory scan", - Long: `Scan triggers a new resource inventory scan.`, - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress("Triggering Gcp inventory scan") - response, err := cli.LwApi.V2.Inventory.Scan(api.GcpInventoryType) - cli.StopProgress() - - if err != nil { - return err - } - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - - cli.OutputHuman(renderSimpleTable([]string{}, [][]string{ - {"STATUS", response.Data.Status}, - {"DETAILS", response.Data.Details}, - })) - return nil - }, - } -) - -func init() { - // add sub-commands to the gcp command - complianceGcpCmd.AddCommand(complianceGcpListCmd) - complianceGcpCmd.AddCommand(complianceGcpListProjCmd) - complianceGcpCmd.AddCommand(complianceGcpGetReportCmd) - complianceGcpCmd.AddCommand(complianceGcpScanCmd) - - // Experimental Commands - complianceGcpCmd.AddCommand(complianceGcpReportStatusCmd) - complianceGcpCmd.AddCommand(complianceGcpDisableReportCmd) - complianceGcpCmd.AddCommand(complianceGcpEnableReportCmd) - - complianceGcpGetReportCmd.Flags().BoolVar(&compCmdState.Details, "details", false, - "increase details about the compliance report", - ) - complianceGcpGetReportCmd.Flags().BoolVar(&compCmdState.Pdf, "pdf", false, - "download report in PDF format", - ) - - // Output the report in CSV format - complianceGcpGetReportCmd.Flags().BoolVar(&compCmdState.Csv, "csv", false, - "output report in CSV format", - ) - - // GCP report types: GCP_ISO_27001_2013, GCP_NIST_800_171_REV2, GCP_CMMC_1_02, GCP_PCI_DSS_3_2_1, GCP_PCI_Rev2, - //GCP_NIST_CSF, GCP_CIS13, GCP_HIPAA_2013, GCP_CIS12, GCP_CIS, GCP_SOC_2, GCP_ISO_27001, GCP_NIST_800_53_REV4, - //GCP_CIS_1_3_0_NIST_800_53_rev5, GCP_CIS_1_3_0_NIST_CSF, GCP_HIPAA, GCP_HIPAA_Rev2, GCP_CIS_1_3_0_NIST_800_171_rev2, - //GCP_SOC, GCP_K8S, GCP_SOC_Rev2, GCP_PCI - complianceGcpGetReportCmd.Flags().StringVar(&compGcpCmdState.Type, "type", "GCP_CIS13", - fmt.Sprintf(`report type to display, run 'lacework report-definitions list' for more information. -valid types:%s`, prettyPrintReportTypes(api.GcpReportTypes())), - ) - - // mark report type flag as deprecated - errcheckWARN(complianceGcpGetReportCmd.Flags().MarkDeprecated("type", "use --report_name flag instead")) - - // Run 'lacework report-definition --subtype GCP' for a full list of GCP report names - complianceGcpGetReportCmd.Flags().StringVar(&compGcpCmdState.ReportName, "report_name", - api.ComplianceReportDefaultGcp, - "report name to display, run 'lacework report-definitions list' for more information.") - - complianceGcpGetReportCmd.Flags().StringSliceVar(&compCmdState.Category, "category", []string{}, - "filter report details by category (storage, networking, identity-and-access-management, ...)", - ) - - complianceGcpGetReportCmd.Flags().StringSliceVar(&compCmdState.Service, "service", []string{}, - "filter report details by service (gcp:storage:bucket, gcp:kms:cryptoKey, gcp:project, ...)", - ) - - complianceGcpGetReportCmd.Flags().StringVar(&compCmdState.Severity, "severity", "", - fmt.Sprintf("filter report details by severity threshold (%s)", - lwseverity.ValidSeverities.String()), - ) - - complianceGcpGetReportCmd.Flags().StringVar(&compCmdState.Status, "status", "", - fmt.Sprintf("filter report details by status (%s)", - strings.Join(api.ValidComplianceStatus, ", ")), - ) -} - -// Simple helper to prompt for approval after disable request -func complianceGcpDisableReportCmdPrompt(arg string) (int, error) { - var message string - - switch arg { - case "CIS", "CIS_1_0", "GCP_CIS": - message = `WARNING! -Disabling all recommendations for CIS_1_0 will disable the following reports and its corresponding compliance alerts: - GCP CIS Benchmark - PCI Benchmark - SOC 2 Report - - Would you like to proceed? - ` - case "CIS_1_2", "GCP_CIS12": - message = `WARNING! -Disabling all recommendations for CIS_1_2 will disable the following reports and its corresponding compliance alerts: - GCP CIS Benchmark 1.2 - HIPAA Report Rev2 - PCI Benchmark Rev2 - SOC 2 Report Rev2 - ISO27001 Report - NIST 800-171 rev2 Report - NIST 800-53 rev4 Report - NIST CSF rev2 Report - - Would you like to proceed? - ` - } - - options := []string{ - "Proceed with disable", - "Quit", - } - - var answer int - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: message, - Options: options, - }, - Response: &answer, - }) - - return answer, err -} - -func complianceGcpDisableReportDisplayChanges(arg string) (bool, error) { - answer, err := complianceGcpDisableReportCmdPrompt(arg) - if err != nil { - return false, err - } - return answer == 0, nil -} - -func complianceGcpReportDetailsTable(report *api.GcpReport) [][]string { - return [][]string{ - {"Report Type", report.ReportType}, - {"Report Title", report.ReportTitle}, - {"Organization ID", report.OrganizationID}, - {"Organization Name", report.OrganizationName}, - {"Project ID", report.ProjectID}, - {"Project Name", report.ProjectName}, - {"Report Time", report.ReportTime.UTC().Format(time.RFC3339)}, - } -} - -// ALLY-431 Workaround to split the Project ID and Project Alias -// ultimately, we need to fix this in the API response -func splitGcpProjectsApiResponse(gcpInfo api.GcpConfigData) cliComplianceGcpInfo { - var ( - orgID, orgAlias = splitIDAndAlias(gcpInfo.Organization) - cliGcpInfo = cliComplianceGcpInfo{ - Organization: cliComplianceIDAlias{orgID, orgAlias}, - Projects: make([]cliComplianceIDAlias, 0), - } - ) - - for _, project := range gcpInfo.Projects { - id, alias := splitIDAndAlias(project) - cliGcpInfo.Projects = append(cliGcpInfo.Projects, cliComplianceIDAlias{id, alias}) - } - - return cliGcpInfo -} - -// @afiune we use named return in this function to be explicit about what is it -// that the function is returning, id and alias respectively -func splitIDAndAlias(text string) (id string, alias string) { - // Getting alias from text - aliasRegex := regexp.MustCompile(`\((.*?)\)`) - aliasBytes := aliasRegex.Find([]byte(text)) - if len(aliasBytes) == 0 { - // if we couldn't get the alias from the provided text - // it means that the entire text is the id - id = text - return - } - alias = string(aliasBytes) - alias = strings.Trim(alias, "(") - alias = strings.Trim(alias, ")") - - // Getting id from text - idRegex := regexp.MustCompile(`^(.*?)\(`) - idBytes := idRegex.Find([]byte(text)) - id = string(idBytes) - id = strings.Trim(id, "(") - id = strings.TrimSpace(id) - - cli.Log.Infow("splitted", "text", text, "id", id, "alias", alias) - return -} - -func getGcpAccounts(orgID, status string) []gcpProject { - var accounts []gcpProject - - cli.StartProgress(fmt.Sprintf("Fetching compliance information about %s organization...", orgID)) - projectsResponse, err := cli.LwApi.V2.Configs.Gcp.ListProjects(orgID) - cli.StopProgress() - if err != nil { - cli.Log.Warnw("unable to list gcp projects", "org_id", orgID, "error", err.Error()) - return accounts - } - for _, projects := range projectsResponse.Data { - for _, project := range projects.Projects { - projectID, _ := splitIDAndAlias(project) - accounts = append(accounts, gcpProject{ - OrganizationID: orgID, - ProjectID: projectID, - Status: status, - }) - } - } - return accounts -} - -type cliComplianceGcpInfo struct { - Organization cliComplianceIDAlias `json:"organization"` - Projects []cliComplianceIDAlias `json:"projects"` -} - -type cliComplianceIDAlias struct { - ID string `json:"id"` - Alias string `json:"alias"` -} - -type gcpProject struct { - ProjectID string `json:"project_id"` - OrganizationID string `json:"organization_id"` - Status string `json:"status"` -} - -func extractGcpProjects(response api.CloudAccountsResponse) []gcpProject { - var gcpAccounts []gcpProject - var gcpData api.GcpCfgData - - for _, gcp := range response.Data { - - gcpJson, err := json.Marshal(gcp.Data) - if err != nil { - continue - } - - err = json.Unmarshal(gcpJson, &gcpData) - if err != nil { - continue - } - - // if organization account, fetch the project ids - if gcpData.IDType == "ORGANIZATION" { - gcpAccounts = append(gcpAccounts, getGcpAccounts(gcpData.ID, gcp.Status())...) - } else if containsDuplicateProjectID(gcpAccounts, gcpData.ID) { - cli.Log.Warnw("duplicate gcp project", "integration_guid", gcp.IntgGuid, "project", gcpData.ID) - continue - } else { - gcpIntegration := gcpProject{ - OrganizationID: "n/a", - ProjectID: gcpData.ID, - Status: gcp.Status(), - } - gcpAccounts = append(gcpAccounts, gcpIntegration) - } - } - - sort.Slice(gcpAccounts, func(i, j int) bool { - switch strings.Compare(gcpAccounts[i].OrganizationID, gcpAccounts[j].OrganizationID) { - case -1: - return true - case 1: - return false - } - return gcpAccounts[i].ProjectID < gcpAccounts[j].ProjectID - }) - - return gcpAccounts -} - -func containsDuplicateProjectID(gcpAccounts []gcpProject, projectID string) bool { - for _, value := range gcpAccounts { - if projectID == value.ProjectID { - return true - } - } - return false -} - -func cliListGcpProjectsAndOrgs(response api.CloudAccountsResponse, gcpData api.GcpConfigsResponse) error { - jsonOut := struct { - Projects []gcpProject `json:"gcp_projects"` - }{Projects: make([]gcpProject, 0)} - - if len(response.Data) == 0 { - if cli.JSONOutput() { - return cli.OutputJSON(jsonOut) - } - - msg := `There are no GCP integrations configured in your account. - -Get started by integrating your GCP to analyze configuration compliance using the command: - - lacework cloud-account create - -If you prefer to configure the integration via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Settings > Integrations > Cloud Accounts. -` - cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) - return nil - } - - if cli.JSONOutput() { - jsonOut.Projects = extractGcpProjects(response) - return cli.OutputJSON(jsonOut) - } - - var rows [][]string - for _, gcp := range extractGcpProjects(response) { - var orgID = gcp.OrganizationID - // if orgID is missing, match org with configs response - if orgID == "" || orgID == "n/a" { - for _, g := range gcpData.Data { - for _, project := range g.Projects { - // split projectID from alias - projectID := strings.Split(project, " (")[0] - if projectID == gcp.ProjectID { - orgID = g.Organization - } - } - } - } - - rows = append(rows, []string{orgID, gcp.ProjectID, gcp.Status}) - } - - cli.OutputHuman(renderSimpleTable([]string{"Organization ID", "Project ID", "Status"}, rows)) - return nil -} - -func fetchCachedGcpComplianceReportSchema(reportType string) (response []api.RecV2, err error) { - var cacheKey = fmt.Sprintf("compliance/gcp/schema/%s", reportType) - - expired := cli.ReadCachedAsset(cacheKey, &response) - if expired { - cli.StartProgress("Fetching compliance report schema...") - response, err = cli.LwApi.V2.Recommendations.Gcp.GetReport(reportType) - cli.StopProgress() - if err != nil { - return nil, errors.Wrap(err, "unable to get GCP compliance report schema") - } - - if len(response) == 0 { - return nil, errors.New("no data found in the report") - } - - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), response) - } - return -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/component.go b/vendor/github.com/lacework/go-sdk/cli/cmd/component.go deleted file mode 100644 index c1c2e0b91..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/component.go +++ /dev/null @@ -1,1295 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "os" - "time" - - "github.com/Masterminds/semver" - "github.com/fatih/color" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/lwcomponent" -) - -const ( - componentTypeAnnotation string = "component" - componentsCacheKey string = "components" -) - -var ( - // componentsCmd represents the components command - componentsCmd = &cobra.Command{ - Use: "component", - Aliases: []string{"components"}, - Short: "Manage components", - Long: `Manage components to extend your experience with the Lacework platform`, - } - - // componentsListCmd represents the list sub-command inside the components command - componentsListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all components", - Long: `List all available components and their current state`, - RunE: runComponentsList, - } - - // componentsShowCmd represents the show sub-command inside the components command - componentsShowCmd = &cobra.Command{ - Use: "show ", - Short: "Show details about a component", - Long: "Show details about a component", - Args: cobra.ExactArgs(1), - RunE: runComponentsShow, - } - - // componentsInstallCmd represents the install sub-command inside the components command - componentsInstallCmd = &cobra.Command{ - Use: "install ", - Short: "Install a new component", - Long: `Install a new component`, - Args: cobra.ExactArgs(1), - RunE: runComponentsInstall, - } - - // componentsUpdateCmd represents the update sub-command inside the components command - componentsUpdateCmd = &cobra.Command{ - Use: "update ", - Aliases: []string{"upgrade"}, - Short: "Update an existing component", - Long: `Update an existing component`, - Args: cobra.ExactArgs(1), - RunE: runComponentsUpdate, - } - - // componentsUninstallCmd represents the uninstall sub-command inside the components command - componentsUninstallCmd = &cobra.Command{ - Use: "uninstall ", - Aliases: []string{"delete", "remove", "rm"}, - Short: "Uninstall an existing component", - Long: `Uninstall an existing component`, - Args: cobra.ExactArgs(1), - RunE: runComponentsDelete, - } - - // componentsDevModeCmd represents the dev sub-command inside the components command - componentsDevModeCmd = &cobra.Command{ - Use: "dev ", - Hidden: true, - Short: "Enter development mode of a new or existing component", - Args: cobra.ExactArgs(1), - RunE: runComponentsDevMode, - } - - versionArg string -) - -func init() { - // add the components command - rootCmd.AddCommand(componentsCmd) - - componentsInstallCmd.PersistentFlags().StringVar(&versionArg, "version", "", - "require a specific version to be installed (default is latest)") - componentsUpdateCmd.PersistentFlags().StringVar(&versionArg, "version", "", - "update to a specific version (default is latest)") - - // add sub-commands to the components command - componentsCmd.AddCommand(componentsListCmd) - componentsCmd.AddCommand(componentsShowCmd) - componentsCmd.AddCommand(componentsInstallCmd) - componentsCmd.AddCommand(componentsUpdateCmd) - componentsCmd.AddCommand(componentsUninstallCmd) - componentsCmd.AddCommand(componentsDevModeCmd) - - // load components dynamically - cli.PrototypeLoadComponents() - - // v1 components - cli.LoadComponents() -} - -// hasInstalledCommands is used inside the cobra template for generating the usage -// of commands, it returns true if there are installed commands via the CDK -func hasInstalledCommands() bool { - return cli.installedCmd -} - -// isComponent is used inside the cobra template for generating the usage of -// commands, it needs the annotations of the command and it will return true -// if the command was installed from the CDK -func isComponent(annotations map[string]string) bool { - t, found := annotations["type"] - if found && t == componentTypeAnnotation { - return true - } - return false -} - -// IsComponentInstalled returns true if component is -// valid and installed -func (c *cliState) IsComponentInstalled(name string) bool { - var err error - c.LwComponents, err = lwcomponent.LocalState() - if err != nil || c.LwComponents == nil { - return false - } - - component, found := c.LwComponents.GetComponent(name) - if found && component.IsInstalled() { - return true - } - return false -} - -// Load v1 components -func (c *cliState) LoadComponents() { - components, err := lwcomponent.LoadLocalComponents() - if err != nil { - c.Log.Debugw("unable to load components", "error", err) - return - } - - for _, component := range components { - exists := false - - for _, cmd := range rootCmd.Commands() { - if cmd.Use == component.Name { - exists = true - break - } - } - - // Skip components that were added by the prototype code - if exists { - continue - } - - version := component.InstalledVersion() - - if version != nil { - componentCmd := &cobra.Command{ - Use: component.Name, - Short: component.Description, - Annotations: map[string]string{"type": componentTypeAnnotation}, - Version: version.String(), - SilenceUsage: true, - DisableFlagParsing: true, - DisableFlagsInUseLine: true, - RunE: func(cmd *cobra.Command, args []string) error { - return v1ComponentCommand(c, cmd) - }, - } - - rootCmd.AddCommand(componentCmd) - } - } -} - -// Grpc server used for components to communicate back to the CLI -func startGrpcServer(c *cliState) { - if err := c.Serve(); err != nil { - c.Log.Errorw("couldn't serve gRPC server", "error", err) - } -} - -func v1ComponentCommand(c *cliState, cmd *cobra.Command) error { - // Parse component -v/--version flag - versionVal, _ := cmd.Flags().GetBool("version") - if versionVal { - cmd.Printf("%s version %s\n", cmd.Use, cmd.Version) - return nil - } - - go startGrpcServer(c) - - catalog, err := LoadCatalog(cmd.Use, false) - if err != nil { - return errors.Wrap(err, "unable to load component Catalog") - } - - component, err := catalog.GetComponent(cmd.Use) - if err != nil { - return err - } - - if !component.Exec.Executable() { - return errors.New("component is not executable") - } - - c.Log.Debugw("running component", "component", cmd.Use, - "args", c.componentParser.componentArgs, - "cli_flags", c.componentParser.cliArgs) - - envs := []string{ - fmt.Sprintf("LW_COMPONENT_NAME=%s", cmd.Use), - } - - envs = append(envs, c.envs()...) - - err = component.Exec.ExecuteInline(c.componentParser.componentArgs, envs...) - if err != nil { - return err - } - - shouldPrint, err := dailyComponentUpdateAvailable(component.Name) - if err != nil { - cli.Log.Debugw("unable to load components last check cache", "error", err) - } - if shouldPrint && component.ApiInfo != nil && component.InstalledVersion().LessThan(component.ApiInfo.Version) { - format := "\n%s v%s available: to update, run `lacework component update %s`\n" - cli.OutputHuman(format, cmd.Use, component.ApiInfo.Version, cmd.Use) - } - - return nil -} - -// LoadComponents reads the local components state and loads all installed components -// of type `CLI_COMMAND` dynamically into the root command of the CLI (`rootCmd`) -func (c *cliState) PrototypeLoadComponents() { - c.Log.Debugw("loading local components") - state, err := lwcomponent.LocalState() - if err != nil || state == nil { - c.Log.Debugw("unable to load components", "error", err) - return - } - - c.LwComponents = state - - // @dhazekamp how do we ensure component command names don't overlap with other commands? - - for _, component := range c.LwComponents.Components { - if component.IsInstalled() && component.IsCommandType() { - c.installedCmd = true - - ver, err := component.CurrentVersion() - if err != nil { - c.Log.Errorw("unable to load dynamic cli command", - "component", component.Name, "error", err, - ) - continue - } - - c.Log.Debugw("loading dynamic cli command", - "component", component.Name, "version", ver, - ) - componentCmd := - &cobra.Command{ - Use: component.Name, - Short: component.Description, - Annotations: map[string]string{"type": componentTypeAnnotation}, - Version: ver.String(), - SilenceUsage: true, - DisableFlagParsing: true, - DisableFlagsInUseLine: true, - RunE: func(cmd *cobra.Command, args []string) error { - // cobra will automatically add a -v/--version flag to - // the command, but because for components we're not - // parsing the args at the usual point in time, we have - // to repeat the check for -v here - versionVal, _ := cmd.Flags().GetBool("version") - if versionVal { - cmd.Printf("%s version %s\n", cmd.Use, cmd.Version) - return nil - } - go func() { - // Start the gRPC server for components to communicate back - if err := c.Serve(); err != nil { - c.Log.Errorw("couldn't serve gRPC server", "error", err) - } - }() - - c.Log.Debugw("running component", "component", cmd.Use, - "args", c.componentParser.componentArgs, - "cli_flags", c.componentParser.cliArgs) - f, ok := c.LwComponents.GetComponent(cmd.Use) - if ok { - shouldPrint, compVerErr := dailyComponentUpdateAvailable(f.Name) - if compVerErr != nil { - // Log an error but do not fail - cli.Log.Debugw("unable to run daily component version check", "error", err) - } - if shouldPrint && f.Status() == lwcomponent.UpdateAvailable { - format := "%s v%s available: to update, run `lacework component update %s`\n" - cli.OutputHuman(fmt.Sprintf(format, cmd.Use, f.LatestVersion.String(), cmd.Use)) - } - envs := []string{ - fmt.Sprintf("LW_COMPONENT_NAME=%s", cmd.Use), - } - envs = append(envs, c.envs()...) - return f.RunAndOutput(c.componentParser.componentArgs, envs...) - } - - // We will land here only if we couldn't run the component, which is not - // possible since we are adding the components dynamically, still if it - // happens, let the user know that we would love to hear their feedback - return errors.New("something went pretty wrong here, contact support@lacework.net") - }, - } - rootCmd.AddCommand(componentCmd) - } - } -} - -func runComponentsList(_ *cobra.Command, _ []string) (err error) { - if !lwcomponent.CatalogV1Enabled(cli.LwApi) { - return prototypeRunComponentsList() - } - - return listComponents() -} - -func listComponents() error { - catalog, err := LoadCatalog("", false) - if err != nil { - return errors.Wrap(err, "unable to load component Catalog") - } - - if cli.JSONOutput() { - return CDKComponentsJSON(catalog) - } - - if catalog.ComponentCount() == 0 { - msg := "There are no components available, " + - "come back later or contact support. (version: %s)\n" - cli.OutputHuman(fmt.Sprintf(msg, cli.LwComponents.Version)) - - return nil - } - - printComponents(catalog.PrintComponents()) - - return nil -} - -func printComponent(data []string) { - printComponents([][]string{data}) -} - -func printComponents(data [][]string) { - cli.OutputHuman( - renderCustomTable( - []string{"Status", "Name", "Version", "Description"}, - data, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - ) -} - -func runComponentsInstall(cmd *cobra.Command, args []string) (err error) { - if !lwcomponent.CatalogV1Enabled(cli.LwApi) { - return prototypeRunComponentsInstall(cmd, args) - } - - return installComponent(args) -} - -func installComponent(args []string) (err error) { - var ( - componentName string = args[0] - downloadComplete = make(chan int8, 1) - params map[string]interface{} = make(map[string]interface{}) - start time.Time - ) - - cli.Event.Component = componentName - cli.Event.Feature = "install_component" - defer cli.SendHoneyvent() - - catalog, err := LoadCatalog(componentName, false) - if err != nil { - err = errors.Wrap(err, "unable to load component Catalog") - return - } - - component, err := catalog.GetComponent(componentName) - if err != nil { - return - } - - installedVersion := component.InstalledVersion() - if installedVersion != nil { - cli.OutputHuman("Component %s is already installed. To upgrade run '%s'\n", - component.Name, - color.HiGreenString("lacework component update %s", component.Name)) - return nil - } - - cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", component.Name)) - - cli.StartProgress(fmt.Sprintf("Staging component %s...", componentName)) - - start = time.Now() - - progressClosure := func(path string, sizeB int64) { - downloadProgress(downloadComplete, path, sizeB) - } - - stageClose, err := catalog.Stage(component, versionArg, progressClosure) - defer stageClose() - downloadComplete <- 0 - - if err != nil { - cli.StopProgress() - return - } - - params["stage_duration_ms"] = time.Since(start).Milliseconds() - cli.Event.FeatureData = params - - cli.StopProgress() - if err != nil { - return - } - cli.OutputChecklist(successIcon, "Component %s staged\n", color.HiYellowString(componentName)) - - cli.StartProgress("Verifing component signature...") - - err = catalog.Verify(component) - - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "verification of component signature failed") - return - } - cli.OutputChecklist(successIcon, "Component signature verified\n") - - cli.StartProgress("Installing component...") - - err = catalog.Install(component) - - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "Install of component failed") - return - } - cli.OutputChecklist(successIcon, "Component version %s installed\n", component.InstalledVersion()) - - cli.StartProgress("Configuring component...") - - stdout, stderr, errCmd := component.Exec.Execute([]string{"cdk-init"}, cli.envs()...) - if errCmd != nil { - if errCmd != lwcomponent.ErrNonExecutable { - cli.Log.Warnw("component life cycle", - "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) - } - } else { - cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) - } - cli.StopProgress() - - cli.OutputChecklist(successIcon, "Component configured\n") - cli.OutputHuman("\nInstallation completed.\n") - - if component.InstallMessage != "" { - cli.OutputHuman(fmt.Sprintf("\n%s\n", component.InstallMessage)) - } - - return -} - -func runComponentsShow(_ *cobra.Command, args []string) (err error) { - if !lwcomponent.CatalogV1Enabled(cli.LwApi) { - return prototypeRunComponentsShow(args) - } - - return showComponent(args) -} - -func showComponent(args []string) error { - var ( - componentName string = args[0] - ) - - catalog, err := LoadCatalog(componentName, true) - if err != nil { - return errors.Wrap(err, "unable to load component Catalog") - } - - component, err := catalog.GetComponent(componentName) - if err != nil { - return err - } - - if cli.JSONOutput() { - return CDKComponentJSON(component) - } - - printComponent(component.PrintSummary()) - - allVersions, err := catalog.ListComponentVersions(component) - if err != nil { - return err - } - - printAvailableVersions(component.InstalledVersion(), allVersions) - - return nil -} - -func printAvailableVersions(installedVersion *semver.Version, availableVersions []*semver.Version) { - cli.OutputHuman("\n") - - result := "The following versions of this component are available to install:" - foundInstalled := false - - for _, version := range availableVersions { - result += "\n" - result += " - " + version.String() - if installedVersion != nil && version.Equal(installedVersion) { - result += " (installed)" - foundInstalled = true - } - } - - if installedVersion != nil && !foundInstalled { - result += fmt.Sprintf( - "\n\nThe currently installed version %s is no longer available to install.", - installedVersion.String(), - ) - } - - cli.OutputHuman(result) - cli.OutputHuman("\n") -} - -func runComponentsUpdate(_ *cobra.Command, args []string) (err error) { - if !lwcomponent.CatalogV1Enabled(cli.LwApi) { - return prototypeRunComponentsUpdate(args) - } - - return updateComponent(args) -} - -func updateComponent(args []string) (err error) { - var ( - componentName string = args[0] - downloadComplete = make(chan int8) - params map[string]interface{} = make(map[string]interface{}) - start time.Time - targetVersion *semver.Version - ) - - catalog, err := LoadCatalog(componentName, false) - if err != nil { - return errors.Wrap(err, "unable to load component Catalog") - } - - component, err := catalog.GetComponent(componentName) - if err != nil { - return err - } - - cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", component.Name)) - - installedVersion := component.InstalledVersion() - if installedVersion == nil { - return errors.Errorf("component %s not installed", color.HiYellowString(componentName)) - } - - latestVersion := component.LatestVersion() - if latestVersion == nil { - return errors.Errorf("component %s not available in API", color.HiYellowString(componentName)) - } - - if versionArg == "" { - targetVersion = latestVersion - } else { - targetVersion, err = semver.NewVersion(versionArg) - if err != nil { - return errors.Errorf("invalid semantic version %s", versionArg) - } - } - - if installedVersion.Equal(targetVersion) { - cli.OutputHuman("Component %s is version %s.\n", component.Name, color.HiYellowString(installedVersion.String())) - return nil - } - - cli.StartProgress(fmt.Sprintf("Staging component %s...", color.HiYellowString(componentName))) - - start = time.Now() - - progressClosure := func(path string, sizeB int64) { - downloadProgress(downloadComplete, path, sizeB) - } - - stageClose, err := catalog.Stage(component, versionArg, progressClosure) - defer stageClose() - downloadComplete <- 0 - if err != nil { - cli.StopProgress() - return - } - - params["stage_duration_ms"] = time.Since(start).Milliseconds() - cli.Event.FeatureData = params - - cli.StopProgress() - if err != nil { - return - } - cli.OutputChecklist(successIcon, "Component %s staged\n", color.HiYellowString(componentName)) - - cli.StartProgress("Verifing component signature...") - - err = catalog.Verify(component) - - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "verification of component signature failed") - return - } - cli.OutputChecklist(successIcon, "Component signature verified\n") - - cli.StartProgress(fmt.Sprintf("Updating component %s to version %s...", component.Name, targetVersion.String())) - - err = catalog.Install(component) - - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "Update of component failed") - return - } - - cli.OutputChecklist(successIcon, "Component %s updated to %s\n", - color.HiYellowString(component.Name), - color.HiCyanString(targetVersion.String())) - - cli.StartProgress("Configuring component...") - - stdout, stderr, errCmd := component.Exec.Execute([]string{"cdk-reconfigure"}, cli.envs()...) - if errCmd != nil { - if errCmd != lwcomponent.ErrNonExecutable { - cli.Log.Warnw("component life cycle", - "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) - } - } else { - cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) - } - cli.StopProgress() - - cli.OutputChecklist(successIcon, "Component reconfigured\n") - - if component.UpdateMessage != "" { - cli.OutputHuman(fmt.Sprintf("\n%s\n", component.UpdateMessage)) - } - - return -} - -func runComponentsDelete(_ *cobra.Command, args []string) (err error) { - if !lwcomponent.CatalogV1Enabled(cli.LwApi) { - return prototypeRunComponentsDelete(args) - } - - return deleteComponent(args) -} - -func deleteComponent(args []string) (err error) { - var ( - componentName string = args[0] - ) - - catalog, err := LoadCatalog(componentName, false) - if err != nil { - return errors.Wrap(err, "unable to load component Catalog") - } - - component, err := catalog.GetComponent(componentName) - if err != nil { - return err - } - - cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", component.Name)) - - cli.StartProgress("Cleaning component data...") - - stdout, stderr, errCmd := component.Exec.Execute([]string{"cdk-cleanup"}, cli.envs()...) - if errCmd != nil { - if errCmd != lwcomponent.ErrNonExecutable { - cli.Log.Warnw("component life cycle", - "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) - } - } else { - cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) - } - cli.StopProgress() - - cli.OutputChecklist(successIcon, "Component data removed\n") - - cli.StartProgress("Deleting component...") - defer cli.StopProgress() - - err = catalog.Delete(component) - if err != nil { - return - } - - cli.StopProgress() - - cli.OutputChecklist(successIcon, "Component %s deleted\n", color.HiYellowString(component.Name)) - cli.OutputHuman("\n- We will do better next time.\n") - cli.OutputHuman("\nDo you want to provide feedback?\n") - cli.OutputHuman("Reach out to us at %s\n", color.HiCyanString("support@lacework.net")) - return -} - -func prototypeRunComponentsList() (err error) { - cli.StartProgress("Loading components state...") - cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "unable to list components") - return - } - - if cli.JSONOutput() { - return cli.OutputJSON(cli.LwComponents) - } - - if len(cli.LwComponents.Components) == 0 { - msg := "There are no components available, " + - "come back later or contact support. (version: %s)\n" - cli.OutputHuman(fmt.Sprintf(msg, cli.LwComponents.Version)) - return - } - - cli.OutputHuman( - renderCustomTable( - []string{"Status", "Name", "Version", "Description"}, - componentsToTable(), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - ) - - cli.OutputHuman("\nComponents version: %s\n", cli.LwComponents.Version) - return -} - -func prototypeRunComponentsShow(args []string) (err error) { - cli.StartProgress("Loading components state...") - cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "unable to load state of components") - return - } - component, found := cli.LwComponents.GetComponent(args[0]) - if !found { - return errors.New("component not found") - } - if cli.JSONOutput() { - return cli.OutputJSON(component) - } - - var colorize *color.Color - switch component.Status() { - case lwcomponent.NotInstalled: - colorize = color.New(color.FgWhite, color.Bold) - case lwcomponent.Installed: - colorize = color.New(color.FgGreen, color.Bold) - case lwcomponent.UpdateAvailable: - colorize = color.New(color.FgYellow, color.Bold) - } - - cli.OutputHuman( - renderCustomTable( - []string{"Name", "Status", "Description"}, - [][]string{{ - component.Name, - colorize.Sprintf(component.Status().String()), - component.Description, - }}, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - ) - var currentVersion *semver.Version = nil - if component.Status() == lwcomponent.Installed || component.Status() == lwcomponent.UpdateAvailable { - installed, err := component.CurrentVersion() - if err != nil { - return err - } - currentVersion = installed - } - cli.OutputHuman("\n") - cli.OutputHuman(component.ListVersions(currentVersion)) - cli.OutputHuman("\n") - return -} - -func componentsToTable() [][]string { - out := [][]string{} - for _, cdata := range cli.LwComponents.Components { - var colorize *color.Color - switch cdata.Status() { - case lwcomponent.NotInstalled: - colorize = color.New(color.FgWhite, color.Bold) - case lwcomponent.Installed: - colorize = color.New(color.FgGreen, color.Bold) - case lwcomponent.UpdateAvailable: - colorize = color.New(color.FgYellow, color.Bold) - } - - // by default, we display the latest version - version := cdata.LatestVersion.String() - - // but if the component is installed, - // we display the current version instead - if currentVersion, err := cdata.CurrentVersion(); err == nil { - version = currentVersion.String() - } - - out = append(out, []string{ - colorize.Sprintf(cdata.Status().String()), - cdata.Name, - version, - cdata.Description, - }) - } - return out -} - -func prototypeRunComponentsInstall(_ *cobra.Command, args []string) (err error) { - var ( - componentName string = args[0] - downloadComplete = make(chan int8) - version string = versionArg - params map[string]interface{} = make(map[string]interface{}) - start time.Time - ) - - cli.Event.Component = componentName - cli.Event.Feature = "install_component" - defer cli.SendHoneyvent() - - cli.StartProgress("Loading components state...") - // @afiune maybe move the state to the cache and fetch if it if has expired - cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "unable to load components") - return - } - - component, found := cli.LwComponents.GetComponent(componentName) - if !found { - err = errors.New(fmt.Sprintf("component %s not found. Try running 'lacework component list'", componentName)) - return - } - - cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", componentName)) - - if version == "" { - version = component.LatestVersion.String() - } - - start = time.Now() - - progressClosure := func(path string, sizeB int64) { - downloadProgress(downloadComplete, path, sizeB) - } - - cli.StartProgress(fmt.Sprintf("Installing component %s...", component.Name)) - err = cli.LwComponents.Install(component, version, progressClosure) - downloadComplete <- 0 - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "unable to install component") - return - } - - cli.OutputChecklist(successIcon, "Component %s installed\n", color.HiYellowString(component.Name)) - - params["install_duration_ms"] = time.Since(start).Milliseconds() - - start = time.Now() - - cli.StartProgress("Verifing component signature...") - err = cli.LwComponents.Verify(component, version) - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "verification of component signature failed") - return - } - cli.OutputChecklist(successIcon, "Component signature verified\n") - - params["verify_duration_ms"] = time.Since(start).Milliseconds() - - start = time.Now() - - cli.StartProgress(fmt.Sprintf("Configuring component %s...", component.Name)) - // component life cycle: initialize - stdout, stderr, errCmd := component.RunAndReturn([]string{"cdk-init"}, nil, cli.envs()...) - if errCmd != nil { - cli.Log.Warnw("component life cycle", - "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) - } else { - cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) - } - cli.StopProgress() - - cli.OutputChecklist(successIcon, "Component configured\n") - cli.OutputHuman("\nInstallation completed.\n") - - params["configure_duration_ms"] = time.Since(start).Milliseconds() - - cli.Event.FeatureData = params - - if component.Breadcrumbs.InstallationMessage != "" { - cli.OutputHuman("\n") - cli.OutputHuman(component.Breadcrumbs.InstallationMessage) - cli.OutputHuman("\n") - } - return -} - -func prototypeRunComponentsUpdate(args []string) (err error) { - cli.StartProgress("Loading components state...") - // @afiune maybe move the state to the cache and fetch if it if has expired - cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "unable to load components") - return - } - - component_name := args[0] - - component, found := cli.LwComponents.GetComponent(args[0]) - if !found { - err = errors.New(fmt.Sprintf("component %s not found. Try running 'lacework component list'", component_name)) - return - } - // @afiune end boilerplate load components - - cli.OutputChecklist(successIcon, fmt.Sprintf("Component %s found\n", component_name)) - - updateTo := component.LatestVersion - if versionArg != "" { - parsedVersion, err := semver.NewVersion(versionArg) - if err != nil { - err = errors.Wrap(err, "invalid version specified") - return err - } - updateTo = *parsedVersion - } - - currentVersion, err := component.CurrentVersion() - if err != nil { - return err - } - - if currentVersion.Equal(&updateTo) { - cli.OutputHuman("You are already running version %s of this component", currentVersion.String()) - return nil - } - - downloadComplete := make(chan int8) - - progressClosure := func(path string, sizeB int64) { - downloadProgress(downloadComplete, path, sizeB) - } - - cli.StartProgress(fmt.Sprintf("Updating component %s to version %s...", component.Name, &updateTo)) - err = cli.LwComponents.Install(component, updateTo.String(), progressClosure) - downloadComplete <- 0 - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "unable to update component") - return - } - - cli.OutputChecklist(successIcon, "Component %s updated to %s\n", - color.HiYellowString(component.Name), - color.HiCyanString(fmt.Sprintf("v%s", updateTo.String()))) - - cli.StartProgress("Verifing component signature...") - err = cli.LwComponents.Verify(component, updateTo.String()) - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "verification of component signature failed") - return - } - cli.OutputChecklist(successIcon, "Component signature verified\n") - - cli.StartProgress(fmt.Sprintf("Reconfiguring %s component...", component.Name)) - // component life cycle: reconfigure - stdout, stderr, errCmd := component.RunAndReturn( - []string{"cdk-reconfigure", currentVersion.String(), updateTo.String()}, - nil, cli.envs()...) - if errCmd != nil { - cli.Log.Warnw("component life cycle", - "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) - } else { - cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) - } - cli.StopProgress() - - cli.OutputChecklist(successIcon, "Component reconfigured\n") - cli.OutputHuman("\n") - cli.OutputHuman(component.MakeUpdateMessage(*currentVersion, updateTo)) - cli.OutputHuman("\n") - return -} - -func prototypeRunComponentsDelete(args []string) (err error) { - cli.StartProgress("Loading components state...") - // @afiune maybe move the state to the cache and fetch if it if has expired - // @afiune DO WE NEED THIS? It should already be loaded - cli.LwComponents, err = lwcomponent.LocalState() - cli.StopProgress() - if err != nil { - err = errors.Wrap(err, "unable to load components") - return - } - - component, found := cli.LwComponents.GetComponent(args[0]) - if !found { - err = errors.New("component not found. Try running 'lacework component list'") - return - } - - if component.UnderDevelopment() { - cli.OutputHuman("Component '%s' in under development. Bypassing checks.\n\n", - color.HiYellowString(component.Name)) - } else if !component.IsInstalled() { - err = errors.Errorf( - "component not installed. Try running 'lacework component install %s'", - args[0], - ) - return - } - - cli.StartProgress("Cleaning component data...") - // component life cycle: cleanup - stdout, stderr, errCmd := component.RunAndReturn([]string{"cdk-cleanup"}, nil, cli.envs()...) - if errCmd != nil { - cli.Log.Warnw("component life cycle", - "error", errCmd.Error(), "stdout", stdout, "stderr", stderr) - } else { - cli.Log.Infow("component life cycle", "stdout", stdout, "stderr", stderr) - } - cli.StopProgress() - - cli.OutputChecklist(successIcon, "Component data removed\n") - - cli.StartProgress("Deleting component...") - defer cli.StopProgress() - - cPath, err := component.RootPath() - if err != nil { - err = errors.Wrap(err, "unable to delete component") - return - } - - err = os.RemoveAll(cPath) - if err != nil { - err = errors.Wrap(err, "unable to delete component") - return - } - - cli.StopProgress() - - cli.OutputChecklist(successIcon, "Component %s deleted\n", color.HiYellowString(component.Name)) - cli.OutputHuman("\n- We will do better next time.\n") - cli.OutputHuman("\nDo you want to provide feedback?\n") - cli.OutputHuman("Reach out to us at %s\n", color.HiCyanString("support@lacework.net")) - return -} - -func downloadProgress(complete chan int8, path string, sizeB int64) { - file, err := os.Open(path) - if err != nil { - cli.Log.Errorf("Failed to open component file: %s", err.Error()) - return - } - defer file.Close() - - var ( - previous float64 = 0 - stop bool = false - spinnerSuffix string = "" - ) - - if cli.spinner != nil { - spinnerSuffix = cli.spinner.Suffix - } - - for !stop { - select { - case <-complete: - stop = true - default: - info, err := file.Stat() - if err != nil { - cli.Log.Errorf("Failed to stat component file: %s", err.Error()) - return - } - - size := info.Size() - if size == 0 { - size = 1 - } - - if sizeB == 0 { - mb := float64(size) / (1 << 20) - - if mb > previous { - sizeString := fmt.Sprintf("%.0fmb", mb) - cli.Log.Infow("downloading component", "size", sizeString) - cli.StartProgress(fmt.Sprintf("%s (%s downloaded)", spinnerSuffix, sizeString)) - previous = mb - } - } else { - percent := float64(size) / float64(sizeB) * 100 - - if percent > previous { - percentString := fmt.Sprintf("%.0f%%", percent) - cli.Log.Infow("downloading component", "percent", percentString) - cli.StartProgress(fmt.Sprintf("%s (%s downloaded)", spinnerSuffix, percentString)) - previous = percent - } - } - } - - time.Sleep(time.Second) - } -} - -func LoadCatalog(componentName string, getAllVersions bool) (*lwcomponent.Catalog, error) { - cli.StartProgress("Loading component catalog...") - defer cli.StopProgress() - - var componentsApiInfo map[string]*lwcomponent.ApiInfo - - // try to load components Catalog from cache - if !cli.noCache { - expired := cli.ReadCachedAsset(componentsCacheKey, &componentsApiInfo) - if !expired && !getAllVersions { - cli.Log.Infow("loaded components from cache", "components", componentsApiInfo) - return lwcomponent.NewCachedCatalog(cli.LwApi, lwcomponent.NewStageTarGz, componentsApiInfo) - } - } - - // load components Catalog from API - catalog, err := lwcomponent.NewCatalog(cli.LwApi, lwcomponent.NewStageTarGz) - if err != nil { - return nil, err - } - - // Retrieve the list of all available versions for a single component - if getAllVersions { - component, err := catalog.GetComponent(componentName) - if err != nil { - return nil, err - } - - if component.HostInfo == nil || (component.HostInfo != nil && !component.HostInfo.Development()) { - vers, err := catalog.ListComponentVersions(component) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("unable to fetch component '%s' versions", componentName)) - } - - component.ApiInfo.AllVersions = vers - catalog.Components[componentName] = lwcomponent.NewCDKComponent(component.ApiInfo, component.HostInfo) - } - } - - componentsApiInfo = make(map[string]*lwcomponent.ApiInfo, len(catalog.Components)) - - for _, c := range catalog.Components { - if c.ApiInfo != nil { - componentsApiInfo[c.Name] = c.ApiInfo - } - } - - cli.WriteAssetToCache(componentsCacheKey, time.Now().Add(time.Hour*12), componentsApiInfo) - - return catalog, nil -} - -type componentJSON struct { - Name string `json:"name"` - Description string `json:"description"` - Version string `json:"version"` - LatestVersion string `json:"latest_version"` - ComponentType string `json:"type"` - Status string `json:"status"` -} - -func newComponentJSON(component *lwcomponent.CDKComponent) *componentJSON { - semver := component.InstalledVersion() - version := "" - - if semver != nil { - version = semver.String() - } - - latestVersion := "" - if component.LatestVersion() != nil { - latestVersion = component.LatestVersion().String() - } - - return &componentJSON{ - Name: component.Name, - Description: component.Description, - Version: version, - LatestVersion: latestVersion, - ComponentType: string(component.Type), - Status: component.Status.String(), - } -} - -func CDKComponentsJSON(catalog *lwcomponent.Catalog) error { - type catalogJSON struct { - Components []*componentJSON `json:"components"` - } - - output := catalogJSON{} - - for _, component := range catalog.Components { - output.Components = append(output.Components, newComponentJSON(&component)) - } - - return cli.OutputJSON(output) -} - -func CDKComponentJSON(component *lwcomponent.CDKComponent) error { - return cli.OutputJSON(newComponentJSON(component)) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/component_args.go b/vendor/github.com/lacework/go-sdk/cli/cmd/component_args.go deleted file mode 100644 index eaaee5eaf..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/component_args.go +++ /dev/null @@ -1,179 +0,0 @@ -// -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strings" - - "github.com/spf13/pflag" -) - -type componentArgParser struct { - componentArgs []string - cliArgs []string -} - -// Parsing component args is surprisingly tricky. What we do here is mirror -// what pflags does itself, but instead of parsing arguments set aside (in componentArgs) -// those arguments that we should pass to the component, and keep track of the -// arguments that we should parse ourselves in cliArgs. - -func (p *componentArgParser) parseArgs(globalFlags *pflag.FlagSet, args []string) { - p.componentArgs = []string{} - p.cliArgs = []string{} - for len(args) > 0 { - s := args[0] - args = args[1:] - if s == "help" || s == "--help" || s == "-h" { - // always pass help down to the component - p.componentArgs = append(p.componentArgs, s) - continue - } - if len(s) == 0 || s[0] != '-' || len(s) == 1 { - // not a flag, passthrough - p.componentArgs = append(p.componentArgs, s) - continue - } - if s[1] == '-' { - if len(s) == 2 { - // "--" terminates the flags, but we do want to pass along the -- - // to the compoent - p.componentArgs = append(p.componentArgs, s) - p.componentArgs = append(p.componentArgs, args...) - break - } - args = p.parseLongArg(globalFlags, s, args) - } else { - args = p.parseShortArg(globalFlags, s, args) - } - } -} - -func (p *componentArgParser) parseLongArg(flags *pflag.FlagSet, s string, args []string) []string { - name := s[2:] - if len(name) == 0 || name[0] == '-' || name[0] == '=' { - // ---, or --= are not legal cobra flags, but we'll just pass - // it through to the component and let that deal with it - p.componentArgs = append(p.componentArgs, s) - return args - } - split := strings.SplitN(name, "=", 2) - name = split[0] - - flag := flags.Lookup(name) - - if flag == nil { - p.componentArgs = append(p.componentArgs, s) - // We're actually a bit stuck here as we don't know if this flag - // takes an argument or not, so we don't know whether or not to consume - // the next arg. What we'll do is peek ahead, and if the next arg does - // not start with '--' then we'll take it. - if len(args) > 0 && len(args[0]) > 0 && (args[0][0] == '-' && args[0][1] == '-') { - // This component flag does not take an arg - return args - } - if len(args) > 0 { - p.componentArgs = append(p.componentArgs, args[0]) - return args[1:] - } - return args - } - - if len(split) == 2 { - // '--flag=arg' - p.cliArgs = append(p.cliArgs, s) - return args - } - if flag.NoOptDefVal != "" { - // '--flag' (arg was optional) - p.cliArgs = append(p.cliArgs, s) - return args - } - if len(args) > 0 { - // '--flag arg' - p.cliArgs = append(p.cliArgs, s, args[0]) - return args[1:] - } - // '--flag' (arg was required) - p.cliArgs = append(p.cliArgs, s) - return args -} - -func (p *componentArgParser) parseShortArg(flags *pflag.FlagSet, s string, args []string) []string { - shorthands := s[1:] - - // shorthands can be a repeated list, e.g. -vvv. - for len(shorthands) > 0 { - shorthand := shorthands[0:1] - - flag := flags.ShorthandLookup(shorthand) - if flag == nil { - // Not our flag, pass to the component. Like the long form above we - // don't know whether to consume an extra arg, so we'll do the same - // thing: if the next arg does not start with `--`` then pass it along - - // handles case - // -n - if len(shorthands) == 1 { - p.componentArgs = append(p.componentArgs, fmt.Sprintf("-%s", shorthand)) - return args - } - - // handles cases - // -n=1234 - // -n1234 - if len(shorthands) > 2 { - p.componentArgs = append(p.componentArgs, fmt.Sprintf("-%s", shorthands)) - return args - } - - // handles cases - // -n 24h - // -n -24h - if len(shorthands) == 1 && len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' && args[0][1] != '-' { - p.componentArgs = append(p.componentArgs, fmt.Sprintf("-%s", shorthand)) - p.componentArgs = append(p.componentArgs, args[0]) - return args[2:] - } - } - - if len(shorthands) > 2 && shorthands[1] == '=' { - // '-f=arg' - p.cliArgs = append(p.cliArgs, s) - return args - } else if flag.NoOptDefVal != "" { - // '-f' (arg was optional) - p.cliArgs = append(p.cliArgs, s) - return args - } else if len(shorthands) > 1 { - // '-farg' - p.cliArgs = append(p.cliArgs, s) - return args - } else if len(args) > 0 { - // '-f arg' - p.cliArgs = append(p.cliArgs, s, args[0]) - return args[1:] - } else { - // '-f' (arg was required) - p.cliArgs = append(p.cliArgs, s) - return args - } - } - return args -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/component_dev.go b/vendor/github.com/lacework/go-sdk/cli/cmd/component_dev.go deleted file mode 100644 index 23007dfe9..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/component_dev.go +++ /dev/null @@ -1,675 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "bytes" - "fmt" - "html/template" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/abiosoft/colima/util/terminal" - "github.com/fatih/color" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/internal/databox" - "github.com/lacework/go-sdk/lwcomponent" -) - -var cdkDevState = struct { - Type string - Scaffolding string - Description string -}{} - -var cdkGolangScaffoldingRequirements = map[string]string{ - "go": "https://go.dev/dl/", -} - -var cdkPythonScaffoldingRequirements = map[string]string{ - "python3": "https://www.python.org/downloads/", - "poetry": "https://python-poetry.org/docs/", -} - -func init() { - componentsDevModeCmd.Flags().StringVar( - &cdkDevState.Type, - "type", "", - fmt.Sprintf("component type (%s, %s, %s)", - lwcomponent.BinaryType, - lwcomponent.CommandType, - lwcomponent.LibraryType, - ), - ) - - componentsDevModeCmd.Flags().StringVar( - &cdkDevState.Description, - "description", "", - "component description", - ) - - componentsDevModeCmd.Flags().StringVar( - &cdkDevState.Scaffolding, "scaffolding", "", - "autogenerate code for a new component (available: Golang, Python)", - ) -} - -func runComponentsDevMode(_ *cobra.Command, args []string) error { - if !lwcomponent.CatalogV1Enabled(cli.LwApi) { - return prototypeRunComponentsDevMode(args) - } - - return devModeComponent(args) -} - -func devModeComponent(args []string) error { - var ( - componentName string = args[0] - componentDescription string = "" - componentDir string = "" - ) - - cli.StartProgress("Loading components Catalog...") - - catalog, err := LoadCatalog(componentName, false) - - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to load component Catalog") - } - - component, _ := catalog.GetComponent(componentName) - if component == nil { - - cli.OutputHuman("Component '%s' not found. Defining a new component.\n", - color.HiYellowString(componentName)) - - var ( - helpMsg = fmt.Sprintf("What are these component types ?\n"+ - "\n'%s' - A binary accessible via the Lacework CLI (Users will run 'lacework ')"+ - "\n'%s' - A regular standalone-binary (this component type is not accessible via the CLI)"+ - "\n'%s' - A library that only provides content for the Lacework CLI or other components\n", - lwcomponent.CommandType, lwcomponent.BinaryType, lwcomponent.LibraryType) - ) - if cdkDevState.Type == "" { - if err := survey.AskOne(&survey.Select{ - Message: "Select the type of component you are developing:", - Help: helpMsg, - Options: []string{ - lwcomponent.CommandType, - lwcomponent.BinaryType, - lwcomponent.LibraryType, - }, - }, &cdkDevState.Type); err != nil { - return err - } - } - - componentType := lwcomponent.Type(cdkDevState.Type) - - if cdkDevState.Description == "" { - if err := survey.AskOne(&survey.Input{ - Message: "What is this component about? (component description):", - }, &componentDescription); err != nil { - return err - } - } else { - componentDescription = cdkDevState.Description - } - - dir, err := lwcomponent.CatalogCacheDir() - if err != nil { - return err - } - - componentDir = filepath.Join(dir, componentName) - - if err := os.MkdirAll(componentDir, os.ModePerm); err != nil { - return err - } - - hostInfo, err := lwcomponent.NewHostInfo(componentDir, componentDescription, componentType) - if err != nil { - return err - } - - newComponent := lwcomponent.NewCDKComponent(nil, hostInfo) - - component = &newComponent - } - - if err := component.EnterDevMode(); err != nil { - return errors.Wrap(err, "unable to enter development mode") - } - - cli.OutputHuman("Component '%s' in now in development mode.\n", - color.HiYellowString(component.Name)) - - if component.Type == lwcomponent.CommandType { - // Offer the creation of a component scaffolding - if cdkDevState.Scaffolding == "" && cli.InteractiveMode() { - if err := survey.AskOne(&survey.Select{ - Message: "Would you like to initialize your component with scaffolding? ", - Options: []string{"No. Start from scratch", "Golang", "Python"}, - }, &cdkDevState.Scaffolding); err != nil { - return err - } - } - - switch cdkDevState.Scaffolding { - case "Golang": - if err := cdkGolangScaffolding(component.Name, componentDir); err != nil { - return err - } - - case "Python": - if err := cdkPythonScaffolding(component.Name, componentDir); err != nil { - return err - } - - default: - cli.OutputHuman("\nDeploy your dev component at: %s\n", - color.HiYellowString(filepath.Join(componentDir, component.Name))) - } - } - - cli.OutputHuman("\nComponent directory: %s\n", color.HiCyanString(componentDir)) - cli.OutputHuman("Dev specs: %s\n", color.HiCyanString(filepath.Join(componentDir, ".dev"))) - return nil -} - -func prototypeRunComponentsDevMode(args []string) error { - cli.StartProgress("Loading components state...") - var err error - cli.LwComponents, err = lwcomponent.LoadState(cli.LwApi) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to load components") - } - - component, found := cli.LwComponents.GetComponent(args[0]) - if !found { - component = &lwcomponent.Component{ - Name: args[0], - } - - if component.UnderDevelopment() { - return errors.New("component already under development.") - } - - cli.OutputHuman("Component '%s' not found. Defining a new component.\n", - color.HiYellowString(component.Name)) - - var ( - helpMsg = fmt.Sprintf("What are these component types ?\n"+ - "\n'%s' - A binary accessible via the Lacework CLI (Users will run 'lacework ')"+ - "\n'%s' - A regular standalone-binary (this component type is not accessible via the CLI)"+ - "\n'%s' - A library that only provides content for the Lacework CLI or other components\n", - lwcomponent.CommandType, lwcomponent.BinaryType, lwcomponent.LibraryType) - ) - if cdkDevState.Type == "" { - if err := survey.AskOne(&survey.Select{ - Message: "Select the type of component you are developing:", - Help: helpMsg, - Options: []string{ - lwcomponent.CommandType, - lwcomponent.BinaryType, - lwcomponent.LibraryType, - }, - }, &cdkDevState.Type); err != nil { - return err - } - } - - component.Type = lwcomponent.Type(cdkDevState.Type) - - if cdkDevState.Description == "" { - if err := survey.AskOne(&survey.Input{ - Message: "What is this component about? (component description):", - }, &component.Description); err != nil { - return err - } - } else { - component.Description = cdkDevState.Description - } - } - - if err := component.EnterDevelopmentMode(); err != nil { - return errors.Wrap(err, "unable to enter development mode") - } - - rPath, err := component.RootPath() - if err != nil { - return errors.New("unable to detect RootPath") - } - - cli.OutputHuman("Component '%s' in now in development mode.\n", - color.HiYellowString(component.Name)) - - if component.Type == lwcomponent.CommandType { - // Offer the creation of a component scaffolding - if cdkDevState.Scaffolding == "" && cli.InteractiveMode() { - if err := survey.AskOne(&survey.Select{ - Message: "Would you like to initialize your component with scaffolding? ", - Options: []string{"No. Start from scratch", "Golang", "Python"}, - }, &cdkDevState.Scaffolding); err != nil { - return err - } - } - - switch cdkDevState.Scaffolding { - case "Golang": - if err := cdkGolangScaffolding(component.Name, rPath); err != nil { - return err - } - - case "Python": - if err := cdkPythonScaffolding(component.Name, rPath); err != nil { - return err - } - - default: - cli.OutputHuman("\nDeploy your dev component at: %s\n", - color.HiYellowString(filepath.Join(rPath, component.Name))) - } - } - - cli.OutputHuman("\nRoot path: %s\n", color.HiCyanString(rPath)) - cli.OutputHuman("Dev specs: %s\n", color.HiCyanString(filepath.Join(rPath, ".dev"))) - return nil -} - -func cdkGolangScaffolding(componentName string, componentDir string) error { - if err := cdkScaffoldingPreflightCheck("Golang", cdkGolangScaffoldingRequirements); err != nil { - return err - } - - cli.OutputHuman("\nDeploying %s scaffolding:\n", color.HiMagentaString("Golang")) - - for _, file := range databox.ListFilesFromDir("/scaffoldings/golang") { - content, found := databox.Get(file) - if found { - // Create directory, if needed - subDir := filepath.Dir(file) - subDir = strings.TrimPrefix(subDir, "/scaffoldings/golang") - fileDir := filepath.Join(componentDir, subDir) - if subDir != "" { - if err := os.MkdirAll(fileDir, 0755); err != nil { - return errors.Wrap(err, "unable to create subdirectory from scaffolding") - } - } - - var ( - buff = &bytes.Buffer{} - fileName = filepath.Base(file) - filePath = filepath.Join(fileDir, fileName) - tmpl = template.Must(template.New(fileName).Delims("[[", "]]").Parse(string(content))) - cData = struct{ Component string }{ - Component: componentName, - } - ) - if err := tmpl.Execute(buff, cData); err != nil { - return errors.Wrap(err, "unable to generate files from go scaffolding") - } - if err := os.WriteFile(filePath, buff.Bytes(), os.ModePerm); err != nil { - cli.OutputChecklist(failureIcon, "Unable to write file %s\n", color.HiRedString(filePath)) - cli.Log.Debugw("unable to write file", "error", err) - } else { - cli.OutputChecklist(successIcon, "File %s deployed\n", color.HiYellowString(filePath)) - } - } - } - - // Missing tasks we can do for the developer - // - // 1) Change directory to Root path - // > Command: 'cd ...' - // 2) Initialize git repository - // > Command: 'git init' - // 3) Create your initial commit - // > Command: 'git add .; git commit -m "feat: init component"' - // 4) Dowload Go dependencies - // > Command: 'make go-vendor' - // 5) Build the component - // > Command: 'make build' - // 6) Run the component via the Lacework CLI - // > Command: 'lacework placeholder' - // - cli.StartProgress("Initializing Git repository...") - err := cdkInitGitRepo(componentDir) - cli.StopProgress() - if err != nil { - cli.OutputChecklist(failureIcon, "Unable to initialize Git repository\n") - cli.Log.Debugw("unable to initialize Git repository", "error", err) - } else { - cli.OutputChecklist(successIcon, "Git repository initialized\n") - } - - cli.StartProgress("Downloading Go dependencies...") - err = cdkGoVendor(componentDir) - cli.StopProgress() - if err != nil { - cli.OutputChecklist(failureIcon, "Unable to download Go dependencies\n") - cli.Log.Debugw("unable to download Go dependencies", "error", err) - } else { - cli.OutputChecklist(successIcon, "Go dependencies downloaded\n") - } - - cli.StartProgress("Building your component...") - err = cdkGoBuild(componentDir) - cli.StopProgress() - if err != nil { - cli.OutputChecklist(failureIcon, "Unable to build your Go component\n") - cli.Log.Debugw("unable to build your Go component", "error", err) - } else { - cli.OutputChecklist(successIcon, "Dev component built at %s\n", - color.HiYellowString(filepath.Join(componentDir, componentName))) - } - - cli.StartProgress("Verifying component...") - err = cdkGoRunVerify(componentName) - cli.StopProgress() - if err != nil { - // this is not on the developer, it's on this codebase, notify to fix it - cli.OutputChecklist(failureIcon, "Unable run scaffolding component\n") - cli.Log.Debugw("unable to run scaffolding component", "error", err) - } else { - cli.OutputChecklist(successIcon, "Component verified\n") - } - - cli.OutputHuman("\nDeployment completed! Time for %s\n", randomEmoji()) - return nil -} - -func cdkPythonScaffolding(componentName string, componentDir string) error { - if err := cdkScaffoldingPreflightCheck("Python", cdkPythonScaffoldingRequirements); err != nil { - return err - } - - cli.OutputHuman("\nDeploying %s scaffolding:\n", color.HiMagentaString("Python")) - - for _, file := range databox.ListFilesFromDir("/scaffoldings/python") { - content, found := databox.Get(file) - if found { - // Create directory, if needed - subDir := filepath.Dir(file) - subDir = strings.TrimPrefix(subDir, "/scaffoldings/python") - fileDir := filepath.Join(componentDir, subDir) - if subDir != "" { - if err := os.MkdirAll(fileDir, 0755); err != nil { - return errors.Wrap(err, "unable to create subdirectory from scaffolding") - } - } - - var ( - fileName = filepath.Base(file) - filePath = filepath.Join(fileDir, fileName) - ) - if err := os.WriteFile(filePath, content, os.ModePerm); err != nil { - cli.OutputChecklist(failureIcon, "Unable to write file %s\n", color.HiRedString(filePath)) - cli.Log.Debugw("unable to write file", "error", err) - } else { - cli.OutputChecklist(successIcon, "File %s deployed\n", color.HiYellowString(filePath)) - } - } - } - - cli.StartProgress("Initializing Git repository...") - err := cdkInitGitRepo(componentDir) - cli.StopProgress() - if err != nil { - cli.OutputChecklist(failureIcon, "Unable to initialize Git repository\n") - cli.Log.Debugw("unable to initialize Git repository", "error", err) - } else { - cli.OutputChecklist(successIcon, "Git repository initialized\n") - } - - // Poetry repository structure `project-name/src/project-name/__init__.py` - err = os.Rename(filepath.Join(componentDir, "src/package"), filepath.Join(componentDir, "src", componentName)) - if err != nil { - cli.OutputChecklist(failureIcon, "Unable to rename package directory\n") - cli.Log.Debugw("unable to rename package directory", "error", err) - return err - } - - cli.StartProgress("Poetry init...") - err = cdkExec(componentDir, - "poetry", - "init", - "--no-interaction", - // Because of https://github.com/python-poetry/poetry/issues/5975 - fmt.Sprintf("--name=%s", strings.ReplaceAll(componentName, "-", "")), - "--python=^3.11,<3.12", - "--dev-dependency=pyinstaller", - "--dev-dependency=poethepoet") - cli.StopProgress() - if err != nil { - cli.OutputChecklist(failureIcon, "Unable to initialize Poetry\n") - cli.Log.Debugw("unable to initialize Poetry", "error", err) - return err - } else { - cli.OutputChecklist(successIcon, "Poetry init\n") - } - - f, err := os.OpenFile(filepath.Join(componentDir, "pyproject.toml"), os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return err - } - _, err = f.WriteString("[tool.poe.tasks]\n") - if err != nil { - return err - } - - _, err = f.WriteString("build.shell = \"poetry run pyinstaller src/") - if err != nil { - return err - } - _, err = f.WriteString(fmt.Sprintf( - "%s/__main__.py --collect-submodules application -D --name %s --distpath dist;", - componentName, componentName, - )) - if err != nil { - return err - } - _, err = f.WriteString(fmt.Sprintf( - " mv dist/%s/* .\"\n", - componentName, - )) - if err != nil { - return err - } - _, err = f.WriteString(fmt.Sprintf( - "clean = \"rm -r build/ %s %s.spec\"\n", - componentName, componentName, - )) - if err != nil { - return err - } - err = f.Close() - if err != nil { - return err - } - - cli.StartProgress("Poetry install...") - err = cdkExec(componentDir, "poetry", "install") - cli.StopProgress() - if err != nil { - cli.OutputChecklist(failureIcon, "Unable to Poetry install\n") - cli.Log.Debugw("unable to Poetry install", "error", err) - return err - } else { - cli.OutputChecklist(successIcon, "Poetry install\n") - } - - cli.StartProgress("Building your component...") - err = cdkExec(componentDir, "poetry", "run", "poe", "build") - cli.StopProgress() - if err != nil { - cli.OutputChecklist(failureIcon, "Unable to build your Python component\n") - cli.Log.Debugw("unable to build your Python component", "error", err) - } else { - cli.OutputChecklist(successIcon, "Dev component built at %s\n", - color.HiYellowString(filepath.Join(componentDir, componentName))) - } - - cli.OutputHuman("\nDeployment completed! Time for %s\n", randomEmoji()) - return nil -} - -func cdkScaffoldingPreflightCheck(scaffolding string, requirements map[string]string) error { - errMessage := "" - for file, site := range requirements { - if _, err := exec.LookPath(file); err != nil { - errMessage += fmt.Sprintf(` -%s is required to create the %s scaffolding. Please install it before proceeding: - %s: %s`, file, scaffolding, file, site) - } - } - if errMessage != "" { - return errors.New(errMessage) - } - return nil -} - -func cdkExec(rootPath string, name string, args ...string) error { - var ( - vw = terminal.NewVerboseWriter(10) - cmd = exec.Command(name, args...) - ) - if _, err := vw.Write([]byte(fmt.Sprintf("Command: %s %v\n", name, args))); err != nil { - cli.Log.Debugw("unable to write to virtual terminal", "error", err) - } - cmd.Env = os.Environ() - cmd.Dir = rootPath - cmd.Stdout = vw - cmd.Stderr = vw - - defer func() { - if _, err := vw.Write([]byte("\n")); err != nil { - cli.Log.Debugw("unable to write to virtual terminal", "error", err) - } - }() - - return cmd.Run() -} - -func cdkInitGitRepo(rootPath string) error { - eMsg := "unable to initialize Git repo" - - repo, err := git.PlainInit(rootPath, false) - if err != nil { - return errors.Wrap(err, eMsg) - } - - w, err := repo.Worktree() - if err != nil { - return errors.Wrap(err, eMsg) - } - - _, err = w.Add(".") - if err != nil { - return errors.Wrap(err, eMsg) - } - - _, err = w.Commit("example go-git commit", &git.CommitOptions{ - Author: &object.Signature{ - Name: "Component Scaffolding", - Email: "support@lacework.net", - When: time.Now(), - }, - }) - - return err -} - -func cdkGoVendor(rootPath string) error { - var ( - vw = terminal.NewVerboseWriter(10) - cmd = exec.Command("make", "go-vendor") - ) - if _, err := vw.Write([]byte("Command: make go-vendor\n")); err != nil { - cli.Log.Debugw("unable to write to virtual terminal", "error", err) - } - cmd.Env = os.Environ() - cmd.Dir = rootPath - cmd.Stdout = vw - cmd.Stderr = vw - - // @afiune silly workaround to clean the spinner output - defer func() { - if _, err := vw.Write([]byte("\n")); err != nil { - cli.Log.Debugw("unable to write to virtual terminal", "error", err) - } - }() - return cmd.Run() -} - -func cdkGoBuild(rootPath string) error { - var ( - vw = terminal.NewVerboseWriter(10) - cmd = exec.Command("make", "build") - ) - if _, err := vw.Write([]byte("Command: make build\n")); err != nil { - cli.Log.Debugw("unable to write to virtual terminal", "error", err) - } - cmd.Env = os.Environ() - cmd.Dir = rootPath - cmd.Stdout = vw - cmd.Stderr = vw - return cmd.Run() -} - -func cdkGoRunVerify(componentName string) error { - var ( - vw = terminal.NewVerboseWriter(10) - cmd = exec.Command(laceworkCLIBinary(), componentName, "placeholder") - ) - _, err := vw.Write([]byte(fmt.Sprintf("Command: %s\n", strings.Join(cmd.Args, " ")))) - if err != nil { - cli.Log.Debugw("unable to write to virtual terminal", "error", err) - } - cmd.Env = os.Environ() - cmd.Stdout = vw - cmd.Stderr = vw - return cmd.Run() -} - -func laceworkCLIBinary() string { - if bin := os.Getenv("LW_CLI_BIN"); bin != "" { - return bin - } - - if os.Getenv("LW_CLI_INTEGRATION_MODE") != "" { - return fmt.Sprintf( - "lacework-cli-%s-%s", - runtime.GOOS, runtime.GOARCH, - ) - } - - return "lacework" -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/configure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/configure.go deleted file mode 100644 index 420c5d822..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/configure.go +++ /dev/null @@ -1,426 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "os" - "regexp" - "sort" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/lacework/go-sdk/internal/format" - "github.com/lacework/go-sdk/lwconfig" - "github.com/lacework/go-sdk/lwdomain" -) - -var ( - // configureJsonFile is the API key file downloaded form the Lacework WebUI - configureJsonFile string - - // configureCmd represents the configure command - configureCmd = &cobra.Command{ - Use: "configure", - Short: "Configure the Lacework CLI", - Args: cobra.NoArgs, - Long: `Configure settings that the Lacework CLI uses to interact with the Lacework -platform. These include your Lacework account, API access key and secret. - -To create a set of API keys, log in to your Lacework account via WebUI and -navigate to Settings > API Keys and click + Create New. Enter a name for -the key and an optional description, then click Save. To get the secret key, -download the generated API key file. - -Use the flag --json_file to preload the downloaded API key file. - -If this command is run with no flags, the Lacework CLI will store all -settings under the default profile. The information in the default profile -is used any time you run a Lacework CLI command that doesn't explicitly -specify a profile to use. - -You can configure multiple profiles by using the --profile flag. If a -config file does not exist (the default location is ~/.lacework.toml), -the Lacework CLI will create it for you.`, - RunE: func(_ *cobra.Command, _ []string) error { - return runConfigureSetup() - }, - } - - configureListCmd = &cobra.Command{ - Use: "list", - Short: "List all configured profiles at ~/.lacework.toml", - Aliases: []string{"ls"}, - Args: cobra.NoArgs, - Long: `List all profiles configured into the config file ~/.lacework.toml - -To switch profiles permanently use the command. - - lacework configure switch-profile profile2`, - RunE: func(_ *cobra.Command, _ []string) error { - profiles, err := cli.LoadProfiles() - if err != nil { - return err - } - - cli.OutputHuman( - renderSimpleTable( - []string{"Profile", "Account", "Subaccount", "API Key", "API Secret", "V"}, - buildProfilesTableContent(cli.Profile, profiles), - ), - ) - - cli.OutputHuman("\nTo switch profiles use 'lacework configure switch-profile '\n") - return nil - }, - } - - configureGetCmd = &cobra.Command{ - Use: "show ", - Short: "Show current configuration data", - Args: cobra.ExactArgs(1), - Long: `Prints the current computed configuration data from the specified configuration -key. The order of precedence to compute the configuration is flags, environment -variables, and the configuration file ~/.lacework.toml. - -The available configuration keys are: - -* profile -* account -* subaccount -* api_secret -* api_key - -To show the configuration from a different profile, use the flag --profile. - - lacework configure show account --profile my-profile`, - RunE: func(_ *cobra.Command, args []string) error { - data, ok := showConfigurationDataFromKey(args[0]) - if !ok { - // TODO change this to be dynamic - return errors.New( - "unknown configuration key. (available: profile, account, subaccount, api_secret, api_key, version)") - } - - if data != "" { - cli.OutputHuman(data) - cli.OutputHuman("\n") - } - - return nil - }, - } -) - -func showConfigurationDataFromKey(key string) (string, bool) { - switch key { - case "profile": - return cli.Profile, true - case "account": - return cli.Account, true - case "subaccount": - return cli.Subaccount, true - case "api_secret": - return cli.Secret, true - case "api_key": - return cli.KeyID, true - case "version": - return fmt.Sprintf("%d", cli.CfgVersion), true - default: - return "", false - } -} - -func init() { - rootCmd.AddCommand(configureCmd) - configureCmd.AddCommand(configureListCmd) - configureCmd.AddCommand(configureGetCmd) - - configureCmd.Flags().StringVarP(&configureJsonFile, - "json_file", "j", "", "loads the API key JSON file downloaded from the WebUI", - ) -} - -func runConfigureSetup() error { - cli.Log.Debugw("configuring cli", "profile", cli.Profile) - - // make sure that the state is loaded to use during configuration - cli.loadStateFromViper() - - // if the Lacework account is empty, and the profile that is being configured is - // not the 'default' profile, auto-populate the account with the provided profile - if cli.Account == "" && cli.Profile != "default" { - cli.Account = cli.Profile - } - - if len(configureJsonFile) != 0 { - err := loadUIJsonFile(configureJsonFile) - if err != nil { - return errors.Wrap(err, "unable to load keys from the provided json file") - } - } else { - if match, _ := regexp.MatchString(".lacework.net", cli.Account); match { - d, err := lwdomain.New(cli.Account) - if err != nil { - return errors.Wrap(err, "unable to configure the command-line: account") - } - cli.Account = d.String() - } - } - - // all new configurations should default to version 2 - cli.CfgVersion = 2 - - newProfile := lwconfig.Profile{ - Version: cli.CfgVersion, - Subaccount: cli.Subaccount, - Account: cli.Account, - ApiKey: cli.KeyID, - ApiSecret: cli.Secret, - } - if cli.InteractiveMode() { - if err := promptConfigureSetup(&newProfile); err != nil { - return err - } - - // before trying to detect if the account is organizational or not, and to - // check if there are sub-accounts, we need to update the CLI settings - cli.Log.Debug("storing interactive information into the cli state") - cli.Secret = newProfile.ApiSecret - cli.KeyID = newProfile.ApiKey - if cli.Account != newProfile.Account { - // if the account provided by the interactive prompt is different, - // we need to remove the previous sub-account since it's a reconfiguration - cli.Account = newProfile.Account - cli.Subaccount = "" - newProfile.Subaccount = "" - } - - // generate a new API client to connect and check for sub-accounts - if err := cli.NewClient(); err != nil { - return err - } - - // get sub-accounts from organizational accounts - subaccount, err := getSubAccountForOrgAdmins() - if err != nil { - // We do NOT error here since API v2 is sending 500 errors - // for mortal users, we need to fix this on the server side - cli.Log.Warnw("unable to get sub-accounts for org admins", "error", err) - } else { - newProfile.Subaccount = subaccount - } - cli.OutputHuman("\n") - } - - if err := newProfile.Verify(); err != nil { - return errors.Wrap(err, "unable to configure the command-line") - } - - if err := lwconfig.StoreProfileAt(viper.ConfigFileUsed(), cli.Profile, newProfile); err != nil { - return errors.Wrap(err, "unable to configure the command-line") - } - - cli.OutputHuman("You are all set!\n") - return nil -} - -func promptConfigureSetup(newProfile *lwconfig.Profile) error { - questions := []*survey.Question{ - { - Name: "account", - Prompt: &survey.Input{ - Message: "Account:", - Default: cli.Account, - }, - Validate: promptRequiredStringLen(1, - "The account subdomain of URL is required. (i.e. .lacework.net)", - ), - Transform: func(ans interface{}) interface{} { - answer, ok := ans.(string) - if ok && strings.Contains(answer, ".lacework.net") { - - d, err := lwdomain.New(answer) - if err != nil { - cli.Log.Warn(err) - return answer - } - cli.OutputHuman("\nPassing full 'lacework.net' domain not required. Using '%s'\n", d.String()) - return d.String() - } - - return ans - }, - }, - { - Name: "api_key", - Prompt: &survey.Input{ - Message: "Access Key ID:", - Default: cli.KeyID, - }, - Validate: promptRequiredStringLen(lwconfig.ApiKeyMinLength, - fmt.Sprintf("The API access key id must have more than %d characters.", lwconfig.ApiKeyMinLength), - ), - }, - } - - secretQuest := &survey.Question{ - Name: "api_secret", - Validate: func(input interface{}) error { - str, ok := input.(string) - if !ok || len(str) < lwconfig.ApiSecretMinLength { - if len(str) == 0 && len(cli.Secret) != 0 { - return nil - } - return errors.New(fmt.Sprintf( - "The API secret access key must have more than %d characters.", - lwconfig.ApiSecretMinLength, - )) - } - return nil - }, - } - - secretMessage := "Secret Access Key:" - if len(cli.Secret) != 0 { - secretMessage = fmt.Sprintf("Secret Access Key: (%s)", format.Secret(4, cli.Secret)) - } - secretQuest.Prompt = &survey.Password{ - Message: secretMessage, - } - - err := survey.Ask(append(questions, secretQuest), newProfile, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - if len(newProfile.ApiSecret) == 0 { - newProfile.ApiSecret = cli.Secret - } - - return nil -} - -func getSubAccountForOrgAdmins() (string, error) { - cli.StartProgress(" Verifying credentials ...") - user, err := cli.LwApi.V2.UserProfile.Get() - cli.StopProgress() - if err != nil { - return "", errors.Wrap(err, "unable to access UserProfile endpoint") - } - - // We only ask for the sub-account if the account is an organizational account - // and it has at least one sub-account other than the primary account - if len(user.Data) != 0 && - user.Data[0].OrgAccount && - len(user.Data[0].SubAccountNames()) > 0 { - - var ( - subaccount string - primary = fmt.Sprintf("PRIMARY (%s)", cli.Account) - ) - err := survey.AskOne(&survey.Select{ - Message: "(Org Admins) Managing a sub-account?", - Default: strings.ToLower(cli.Subaccount), - Options: append([]string{primary}, user.Data[0].SubAccountNames()...), - }, &subaccount, survey.WithIcons(promptIconsFunc)) - if err != nil { - return "", err - } - - if subaccount != primary { - return subaccount, nil - } - } - - return "", nil -} - -// apiKeyDetails represents the details of an API key, we use this struct -// internally to unmarshal the JSON file provided by the Lacework WebUI -type apiKeyDetails struct { - Account string `json:"account,omitempty"` - SubAccount string `json:"subAccount,omitempty"` - KeyID string `json:"keyId"` - Secret string `json:"secret"` -} - -func loadUIJsonFile(file string) error { - cli.Log.Debugw("loading API key JSON file", "path", file) - jsonData, err := os.ReadFile(file) - if err != nil { - return err - } - - cli.Log.Debugw("JSON file", "raw", string(jsonData)) - var auth apiKeyDetails - err = json.Unmarshal(jsonData, &auth) - if err != nil { - return err - } - - cli.KeyID = auth.KeyID - cli.Secret = auth.Secret - cli.Subaccount = strings.ToLower(auth.SubAccount) - if auth.Account != "" { - d, err := lwdomain.New(auth.Account) - if err != nil { - return err - } - cli.Account = d.String() - } - - return nil -} - -func buildProfilesTableContent(current string, profiles lwconfig.Profiles) [][]string { - out := [][]string{} - for profile, creds := range profiles { - out = append(out, []string{ - profile, - creds.Account, - creds.Subaccount, - creds.ApiKey, - format.Secret(4, creds.ApiSecret), - fmt.Sprintf("%d", creds.Version), - }) - } - - // order by severity - sort.Slice(out, func(i, j int) bool { - return out[i][0] < out[j][0] - }) - - for i := range out { - if out[i][0] == current { - out[i][0] = fmt.Sprintf("> %s", out[i][0]) - } else { - out[i][0] = fmt.Sprintf(" %s", out[i][0]) - } - } - - return out -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/configure_switch_profile.go b/vendor/github.com/lacework/go-sdk/cli/cmd/configure_switch_profile.go deleted file mode 100644 index 1b17aa196..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/configure_switch_profile.go +++ /dev/null @@ -1,79 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "os" - - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - configureSwitchProfileCmd = &cobra.Command{ - Use: "switch-profile ", - Aliases: []string{"switch", "use"}, - Args: cobra.ExactArgs(1), - Short: "Switch between configured profiles", - Long: `Switch between profiles configured into the config file ~/.lacework.toml - -An alternative to temporarily switch to a different profile in your current terminal -is to export the environment variable: - - ` + configureListCmdSetProfileEnv, - RunE: func(_ *cobra.Command, args []string) error { - if args[0] == "default" { - cli.Log.Debug("removing global profile cache, going back to default") - if err := cli.Cache.Erase("global/profile"); err != nil { - if !os.IsNotExist(err) { - return errors.Wrap(err, "unable to switch profile") - } - } - cli.OutputHuman("Profile switched back to default.\n") - return nil - } - - profiles, err := cli.LoadProfiles() - if err != nil { - return err - } - - if _, ok := profiles[args[0]]; ok { - cli.Log.Debugw("storing global profile cache", - "current_profile", cli.Profile, - "new_profile", args[0], - ) - if err := cli.Cache.Write("global/profile", []byte(args[0])); err != nil { - return errors.Wrap(err, "unable to switch profile") - } - cli.OutputHuman("Profile switched to '%s'.\n", args[0]) - return nil - } - - return errors.Errorf( - "Profile '%s' not found. Try 'lacework configure list' to see all configured profiles.", - args[0], - ) - }, - } -) - -func init() { - configureCmd.AddCommand(configureSwitchProfileCmd) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/container_registry.go b/vendor/github.com/lacework/go-sdk/cli/cmd/container_registry.go deleted file mode 100644 index 17ad68e61..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/container_registry.go +++ /dev/null @@ -1,199 +0,0 @@ -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - containerRegistryCommand = &cobra.Command{ - Use: "container-registry", - Aliases: []string{"container-registries", "cr"}, - Short: "Manage container registries", - Long: "Manage container registry integrations with Lacework", - } - - // containerRegistriesListCmd represents the list sub-command inside the container registries command - containerRegistryListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all available container registry integrations", - Args: cobra.NoArgs, - RunE: containerRegistryList, - } - - // containerRegistryShowCmd represents the show sub-command inside the container registry command - containerRegistryShowCmd = &cobra.Command{ - Use: "show", - Aliases: []string{"get"}, - Short: "Show a single container registry integration", - Args: cobra.ExactArgs(1), - RunE: containerRegistryShow, - } - - // containerRegistryCreateCmd represents the show sub-command inside the container registries command - containerRegistryCreateCmd = &cobra.Command{ - Use: "create", - Short: "Create a new container registry integration", - Args: cobra.NoArgs, - RunE: containerRegistryCreate, - } - - // containerRegistryDeleteCmd represents the delete sub-command inside the container registries command - containerRegistryDeleteCmd = &cobra.Command{ - Use: "delete", - Aliases: []string{"rm"}, - Short: "Delete a container registry integration", - Args: cobra.ExactArgs(1), - RunE: containerRegistryDelete, - } -) - -func init() { - // add the container-registry command - rootCmd.AddCommand(containerRegistryCommand) - containerRegistryCommand.AddCommand(containerRegistryListCmd) - containerRegistryCommand.AddCommand(containerRegistryShowCmd) - containerRegistryCommand.AddCommand(containerRegistryCreateCmd) - containerRegistryCommand.AddCommand(containerRegistryDeleteCmd) -} - -func containerRegistriesToTable(containerRegistries []api.ContainerRegistryRaw) [][]string { - var out [][]string - for _, cadata := range containerRegistries { - out = append(out, []string{ - cadata.IntgGuid, - cadata.Name, - cadata.Type, - cadata.Status(), - cadata.StateString(), - }) - } - return out -} - -func containerRegistryList(_ *cobra.Command, _ []string) error { - containerRegistries, err := cli.LwApi.V2.ContainerRegistries.List() - - if err != nil { - return errors.Wrap(err, "unable to get container registries") - } - - if cli.JSONOutput() { - return cli.OutputJSON(containerRegistries.Data) - } - - if len(containerRegistries.Data) == 0 { - cli.OutputHuman("No container registries found.\n") - return nil - } - - cli.OutputHuman( - renderSimpleTable( - []string{"container registry GUID", "Name", "Type", "Status", "State"}, - containerRegistriesToTable(containerRegistries.Data), - ), - ) - return nil -} - -func containerRegistryShow(_ *cobra.Command, args []string) error { - var ( - containerRegistry api.ContainerRegistryResponse - out [][]string - ) - cli.StartProgress(" Fetching container registry...") - err := cli.LwApi.V2.ContainerRegistries.Get(args[0], &containerRegistry) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to retrieve container registry") - } - - out = append(out, []string{containerRegistry.Data.IntgGuid, - containerRegistry.Data.Name, - containerRegistry.Data.Type, - containerRegistry.Data.Status(), - containerRegistry.Data.StateString(), - }) - - if cli.JSONOutput() { - return cli.OutputJSON(containerRegistry.Data) - } - - cli.OutputHuman(renderSimpleTable([]string{"Container Registry GUID", "Name", "Type", "Status", "State"}, out)) - cli.OutputHuman("\n") - cli.OutputHuman(buildDetailsTable(containerRegistry.Data)) - - return nil -} - -func containerRegistryCreate(_ *cobra.Command, _ []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - - err := promptCreateContainerRegistry() - if err != nil { - return errors.Wrap(err, "unable to create container registry") - } - - cli.OutputHuman("The container registry was created.\n") - return nil -} - -func containerRegistryDelete(_ *cobra.Command, args []string) error { - cli.StartProgress(" Deleting container registry...") - err := cli.LwApi.V2.ContainerRegistries.Delete(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to delete container registry") - } - cli.OutputHuman("The container registry %s was deleted.\n", args[0]) - return nil -} - -func promptCreateContainerRegistry() error { - var ( - containerRegistry = "" - prompt = &survey.Select{ - Message: "Choose a container registry type to create: ", - Options: []string{ - "Docker Hub Registry", - "Docker V2 Registry", - "Amazon Container Registry (ECR)", - "Google Container Registry (GCR)", - "Google Artifact Registry (GAR)", - "Github Container Registry (GHCR)", - "Inline Scanner Container Registry", - "Proxy Scanner Container Registry", - }, - } - err = survey.AskOne(prompt, &containerRegistry) - ) - if err != nil { - return err - } - - switch containerRegistry { - case "Docker Hub Registry": - return createDockerHubIntegration() - case "Docker V2 Registry": - return createDockerV2Integration() - case "Amazon Container Registry (ECR)": - return createAwsEcrIntegration() - case "Google Artifact Registry (GAR)": - return createGarIntegration() - case "Inline Scanner Container Registry": - return createInlineScannerIntegration() - case "Proxy Scanner Container Registry": - return createProxyScannerIntegration() - case "Github Container Registry (GHCR)": - return createGhcrIntegration() - case "Google Container Registry (GCR)": - return createGcrIntegration() - default: - return errors.New("unknown container registry type") - } -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/content_library.go b/vendor/github.com/lacework/go-sdk/cli/cmd/content_library.go deleted file mode 100644 index b274de98a..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/content_library.go +++ /dev/null @@ -1,205 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - - "github.com/pkg/errors" - - "github.com/lacework/go-sdk/lwcomponent" -) - -const ( - lclComponentName string = "content-library" - lclIndexPath string = "content.index" -) - -type LCLContentType string - -const ( - LCLQueryType LCLContentType = "query" - LCLPolicyType LCLContentType = "policy" -) - -type LCLReference struct { - ID string `json:"id"` - Type LCLContentType `json:"content_type"` - Path string `json:"path"` - URI string `json:"uri"` -} - -func getPolicyReference(refs []LCLReference) (LCLReference, error) { - for i := range refs { - if refs[i].Type == LCLPolicyType { - return refs[i], nil - } - } - return LCLReference{}, errors.New("policy reference not found") -} - -type LCLQuery struct { - References []LCLReference `json:"references"` -} - -type LCLPolicy struct { - PolicyID string `json:"policyId"` - Title string `json:"title"` - Description string `json:"description"` - Tags []string `json:"tags"` - QueryID string `json:"queryId"` - References []LCLReference `json:"references"` -} - -type LaceworkContentLibrary struct { - Component *lwcomponent.Component - Queries map[string]LCLQuery `json:"queries"` - Policies map[string]LCLPolicy `json:"policies"` - PolicyTags map[string][]string `json:"policy_tags"` -} - -func (c *cliState) isLCLInstalled() bool { - return c.IsComponentInstalled(lclComponentName) -} - -func (c *cliState) LoadLCL() (*LaceworkContentLibrary, error) { - var ( - baseErr = "unable to load Lacework Content Library" - lcl = new(LaceworkContentLibrary) - found bool - ) - - if c.LwComponents == nil { - return lcl, errors.New(baseErr) - } - - lcl.Component, found = c.LwComponents.GetComponent(lclComponentName) - if !found { - return lcl, errors.Wrap(errors.New("component not installed"), baseErr) - } - - index, err := lcl.run(lclIndexPath) - if err != nil { - return new(LaceworkContentLibrary), errors.Wrap(err, baseErr) - } - - if err := json.Unmarshal([]byte(index), lcl); err != nil { - return new(LaceworkContentLibrary), errors.Wrap(err, baseErr) - } - - for policyID, policy := range lcl.Policies { - for i := range policy.References { - if policy.References[i].Type == LCLQueryType { - policy.QueryID = policy.References[i].ID - } - if policy.References[i].Type == LCLPolicyType { - policy.PolicyID = policy.References[i].ID - } - } - lcl.Policies[policyID] = policy - } - - return lcl, nil -} - -func (lcl *LaceworkContentLibrary) run(path string) (string, error) { - if lcl.Component == nil || !lcl.Component.IsInstalled() { - return "", errors.New("Lacework Content Library is not installed") - } - stdout, _, err := lcl.Component.RunAndReturn([]string{path}, nil) - return stdout, err -} - -func (lcl *LaceworkContentLibrary) getReferenceForQuery(id string) (LCLReference, error) { - var ref LCLReference - - if id == "" { - return ref, errors.New("query ID must be provided") - } - if _, ok := lcl.Queries[id]; !ok { - return ref, errors.New("query does not exist in library") - } - if len(lcl.Queries[id].References) < 1 { - return ref, errors.New("query exists but is malformed") - } - return lcl.Queries[id].References[0], nil -} - -func (lcl *LaceworkContentLibrary) getReferencesForPolicy(id string) ([]LCLReference, error) { - var refs []LCLReference - - if id == "" { - return refs, errors.New("policy ID must be provided") - } - if _, ok := lcl.Policies[id]; !ok { - return refs, errors.New("policy does not exist in library") - } - if len(lcl.Policies[id].References) < 2 { - return refs, errors.New("policy exists but is malformed") - } - return lcl.Policies[id].References, nil -} - -func (lcl *LaceworkContentLibrary) GetQuery(id string) (string, error) { - // get query reference - ref, err := lcl.getReferenceForQuery(id) - if err != nil { - return "", err - } - // check query path - if ref.Path == "" { - return "", errors.New("query exists but is malformed") - } - // get query string - return lcl.run(ref.Path) -} - -func (lcl *LaceworkContentLibrary) GetPolicy(id string) (string, error) { - // get policy references - refs, err := lcl.getReferencesForPolicy(id) - if err != nil { - return "", err - } - ref, err := getPolicyReference(refs) - if err != nil || ref.Path == "" { - return "", errors.New("policy exists but is malformed") - } - // get policy string - return lcl.run(ref.Path) -} - -func (lcl *LaceworkContentLibrary) GetPoliciesByTag(t string) map[string]LCLPolicy { - var ( - policies map[string]LCLPolicy = map[string]LCLPolicy{} - policyIDs []string - ok bool - ) - - if policyIDs, ok = lcl.PolicyTags[t]; !ok { - return policies - } - - for _, policyID := range policyIDs { - if lclPolicy, ok := lcl.Policies[policyID]; ok { - policies[policyID] = lclPolicy - } - } - - return policies -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/docs.go b/vendor/github.com/lacework/go-sdk/cli/cmd/docs.go deleted file mode 100644 index 950dc22e7..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/docs.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:generate go run ../../scripts/docs/doc_gen.go -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// The commands behind the Lacework command-line interface (CLI) -package cmd - -import ( - "fmt" - "os" - "path" - "path/filepath" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/cobra/doc" -) - -var ( - - // docsLink is the custom link used to render internal links - docsLink = "" - - // docsCmd is a hidden command that generates automatic documentation in Markdown - docsCmd = &cobra.Command{ - Use: "docs ", - Hidden: true, - Short: "Generate Markdown documentation", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - return GenerateMarkdownDocs(args[0]) - }, - } - - // headerTemplate adds front matter to generated documentation, this is how - // we automatically generate documentation at docs.lacework.com - headerTemplate = `--- -title: "%s" -slug: %s -hide_title: true ---- - -` -) - -func init() { - rootCmd.AddCommand(docsCmd) - docsCmd.Flags().StringVarP(&docsLink, - "link", "l", "", "customize the rendered internal links to the commands") -} - -func GenerateMarkdownDocs(location string) error { - // remove location before generating to ensure deleted commands are removed - err := os.RemoveAll(location) - if err != nil { - return err - } - - if err = os.MkdirAll(location, 0755); err != nil { - return err - } - - // given a filename, linkHandler is used to customize the rendered internal links - // to the commands, only if docsLinks was provided - linkHandler := func(name string) string { - if docsLink != "" { - base := strings.TrimSuffix(name, path.Ext(name)) - return docsLink + strings.ToLower(base) + "/" - } - return name - } - - // filePrepender uses headerTemplate to prepend front matter to the rendered Markdown - filePrepender := func(filename string) string { - var ( - name = filepath.Base(filename) - base = strings.TrimSuffix(name, path.Ext(name)) - ) - return fmt.Sprintf(headerTemplate, strings.Replace(base, "_", " ", -1), base) - } - - return doc.GenMarkdownTreeCustom(rootCmd, location, filePrepender, linkHandler) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/emoji.go b/vendor/github.com/lacework/go-sdk/cli/cmd/emoji.go deleted file mode 100644 index 92f0b2f1e..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/emoji.go +++ /dev/null @@ -1,30 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "math/rand" - "time" -) - -var emojis = []string{":beer:", ":pizza:", ":taco:"} - -func init() { - rand.New(rand.NewSource(time.Now().UTC().UnixNano())) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_unix.go b/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_unix.go deleted file mode 100644 index 1bb77773f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_unix.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build !windows - -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "math/rand" - - "github.com/kyokomi/emoji/v2" -) - -func randomEmoji() string { - return emoji.Sprint(emojis[rand.Intn(len(emojis))]) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_windows.go b/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_windows.go deleted file mode 100644 index eaa48e542..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/emoji_windows.go +++ /dev/null @@ -1,27 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "math/rand" -) - -func randomEmoji() string { - return emojis[rand.Intn(len(emojis))] -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/errors.go b/vendor/github.com/lacework/go-sdk/cli/cmd/errors.go deleted file mode 100644 index 5b318c695..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/errors.go +++ /dev/null @@ -1,137 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/lwseverity" - "github.com/pkg/errors" -) - -type vulnerabilityPolicyError struct { - SeverityRating string - FixableSeverityRating string - FixableVulnCount int32 - FailOnSeverity string - FailOnFixable bool - ExitCode int - Message string - Err error -} - -func NewVulnerabilityPolicyErrorV2( - assessment api.VulnerabilitiesContainersResponse, - failOnSeverity string, failOnFixable bool, -) *vulnerabilityPolicyError { - return &vulnerabilityPolicyError{ - SeverityRating: assessment.HighestSeverity(), - FixableSeverityRating: assessment.HighestFixableSeverity(), - FixableVulnCount: assessment.TotalFixableVulnerabilities(), - FailOnSeverity: failOnSeverity, - FailOnFixable: failOnFixable, - // we use a default exit code that might change - // during NonCompliant() or Compliant() - ExitCode: 9, - } -} - -func NewVulnerabilityPolicyError( - assessment api.VulnerabilityAssessment, - failOnSeverity string, failOnFixable bool, -) *vulnerabilityPolicyError { - return &vulnerabilityPolicyError{ - SeverityRating: assessment.HighestSeverity(), - FixableSeverityRating: assessment.HighestFixableSeverity(), - FixableVulnCount: assessment.TotalFixableVulnerabilities(), - FailOnSeverity: failOnSeverity, - FailOnFixable: failOnFixable, - // we use a default exit code that might change - // during NonCompliant() or Compliant() - ExitCode: 9, - } -} - -// Example of an error message sent to the end-user: -// -// ERROR (FAIL-ON): fixable vulnerabilities found with threshold 'critical' (exit code: 9) -func (e *vulnerabilityPolicyError) Error() string { - if e.ExitCode == 0 { - return "" - } - return fmt.Sprintf("(FAIL-ON): %s (exit code: %d)", e.Message, e.ExitCode) -} - -func (e *vulnerabilityPolicyError) Unwrap() error { - return e.Err -} - -func (e *vulnerabilityPolicyError) NonCompliant() bool { - return !e.validate() -} - -func (e *vulnerabilityPolicyError) Compliant() bool { - return e.validate() -} - -// validate returns true if the error policy is compliant, -// that is, when the provided assessment doesn't meet the -// thresholds. It returns false if the policy is NOT compliant -func (e *vulnerabilityPolicyError) validate() bool { - severityRating, _ := lwseverity.Normalize(e.SeverityRating) - fixableSeverityRating, _ := lwseverity.Normalize(e.FixableSeverityRating) - threshold, _ := lwseverity.Normalize(e.FailOnSeverity) - - cli.Log.Debugw("validating policy", - "severity_rating", severityRating, - "fixable_severity_rating", fixableSeverityRating, - "threshold", threshold, - "fixable_vuln_count", e.FixableVulnCount, - ) - if e.FailOnSeverity == "" && e.FailOnFixable && e.FixableVulnCount > 0 { - e.Message = "fixable vulnerabilities found" - return false - } - - if e.FailOnFixable && e.FixableVulnCount > 0 && fixableSeverityRating <= threshold { - e.Message = fmt.Sprintf( - "fixable vulnerabilities found with threshold '%s'", - e.FailOnSeverity) - return false - } - - if e.FailOnSeverity != "" && (severityRating <= threshold && severityRating != 0) { - e.Message = fmt.Sprintf( - "vulnerabilities found with threshold '%s'", - e.FailOnSeverity) - return false - } - - e.Message = "Compliant policy" - e.ExitCode = 0 - return true -} - -func yikes(msg string) error { - return errors.Wrap( - errors.New("something went pretty wrong here, contact support@lacework.net"), - msg, - ) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/errors_lql.go b/vendor/github.com/lacework/go-sdk/cli/cmd/errors_lql.go deleted file mode 100644 index 2d5c02b50..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/errors_lql.go +++ /dev/null @@ -1,101 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - - "github.com/lacework/go-sdk/internal/failon" -) - -type queryFailonError struct { - ExitCode int - Message string - Err error - FailonCount string - Count int -} - -func NewQueryFailonError(failonCount string, count int) *queryFailonError { - return &queryFailonError{ - FailonCount: failonCount, - Count: count, - // we use a default exit code that might change - // during NonCompliant() or Compliant() - ExitCode: 9, - } -} - -// Example of an error message sent to the end-user: -// -// ERROR (FAIL-ON): query matched fail_on_count expression [count:5] [expr:!=0] (exit code: 9) -func (e *queryFailonError) Error() string { - if e.ExitCode == 0 { - return "" - } - return fmt.Sprintf("(FAIL-ON): %s (exit code: %d)", e.Message, e.ExitCode) -} - -func (e *queryFailonError) Unwrap() error { - return e.Err -} - -func (e *queryFailonError) NonCompliant() bool { - return !e.validate() -} - -func (e *queryFailonError) Compliant() bool { - return e.validate() -} - -// validate returns true if the error query is compliant, that is, -// when the provided count doesn't match the provided fail on count -// expression. It returns false if the query count matches -func (e *queryFailonError) validate() bool { - cli.Log.Debugw("validating policy", - "count", e.Count, - "fail_on_count", e.FailonCount, - ) - - co := failon.CountOperation{} - if err := co.Parse(e.FailonCount); err != nil { - e.ExitCode = 123 - e.Message = err.Error() - return false - } - - isFail, err := co.IsFail(e.Count) - if err != nil { - e.ExitCode = 123 - e.Message = err.Error() - return false - } - - if isFail { - e.Message = fmt.Sprintf( - "query matched fail_on_count expression. [count:%d] [expr:%s]", - e.Count, e.FailonCount, - ) - return false - } - - e.Message = "Compliant query" - e.ExitCode = 0 - return true -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/flags.go b/vendor/github.com/lacework/go-sdk/cli/cmd/flags.go deleted file mode 100644 index 7b96e9b69..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/flags.go +++ /dev/null @@ -1,124 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "reflect" - "strings" - - "github.com/pkg/errors" -) - -// Used to store the list of available filters from a CLI command -// -// E.g. get available filters for a cobra.Command.Long -// -// ```go -// -// dummyCmdState = struct { -// // The available filters -// AvailableFilters CmdFilters -// -// // List of filters to apply -// Filters []string -// }{} -// -// dummyCmdState := &cobra.Command{ -// Long: `The available keys for this command are: -// -// ` + stringSliceToMarkdownList( -// -// dummyCmdState.AvailableFilters.GetFiltersFrom( -// api.MachineDetailEntity{}, -// ), -// -// )} -// ``` -type CmdFilters struct { - Filters []string -} - -func (f *CmdFilters) GetFiltersFrom(T interface{}) []string { - if len(f.Filters) == 0 { - f.Filters = getFiltersFrom(T, "") - } - - return f.Filters -} - -func getFiltersFrom(T interface{}, prefix string) []string { - var ( - filters = []string{} - rt = reflect.TypeOf(T) - rv = reflect.Indirect(reflect.ValueOf(T)) - ) - - for i := 0; i < rt.NumField(); i++ { - v := rv.Field(i) - - // only use a field if it has a 'json' tag - if fieldJSON, ok := rt.Field(i).Tag.Lookup("json"); ok { - // split fieldJSON by comma to handle omitempty/omitzero modifiers - fieldJSONSlice := strings.Split(fieldJSON, ",") - if len(fieldJSONSlice) > 0 { - fieldJSON = fieldJSONSlice[0] - } - - // if there is any prefix, we need to append it to the JSON field - if prefix != "" { - fieldJSON = fmt.Sprintf("%s.%s", prefix, fieldJSON) - } - - // if the field is a struct we recursively get the fields inside - if v.Kind() == reflect.Struct { - filters = append(filters, getFiltersFrom(v.Interface(), fieldJSON)...) - } else { - filters = append(filters, fieldJSON) - } - - } - } - - return filters -} - -// validateKeyValuePairs returns and error if any filter is malformed -func validateKeyValuePairs(filters []string) error { - for _, pair := range filters { - kv := strings.Split(pair, ":") - if len(kv) != 2 || kv[0] == "" || kv[1] == "" { - return errors.Errorf("malformed filter '%s'. Expected format 'key:value'", pair) - } - } - return nil -} - -// stringSliceToMarkdownList display a list of filters in Markdown format. -// -// E.g. The list []string{"a","b","c"} will return -// - a -// - b -// - c -func stringSliceToMarkdownList(filters []string) string { - if len(filters) == 0 { - return "" - } - return fmt.Sprintf(" * %s", strings.Join(filters, "\n * ")) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/gcp.go deleted file mode 100644 index 1e2057584..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/gcp.go +++ /dev/null @@ -1,111 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "context" - "fmt" - "net/http" - - "cloud.google.com/go/compute/metadata" - instances "github.com/lacework/go-sdk/lwcloud/gcp/resources/instances" - resources "github.com/lacework/go-sdk/lwcloud/gcp/resources/models" - "github.com/lacework/go-sdk/lwrunner" -) - -// gcpDescribeInstancesInProject takes a GCP project ID and the username of an IAM username in the -// project associated with the credentials in use as input, and outputs a list of GCP instances -// in the project. It reads the flag value `InstallIncludeRegions` if populated to filter on regions, -// and the flag values `InstallTag` and `InstallTagKey` if populated to filter on tag. -func gcpDescribeInstancesInProject(parentUsername, projectID string) ([]*lwrunner.GCPRunner, error) { - var discoveredInstances []resources.InstanceDetails - var err error - - // Filter instances by region, if provided as CLI flag value - if len(agentCmdState.InstallIncludeRegions) > 0 { - cli.Log.Debugw("filtering on regions", "regions", agentCmdState.InstallIncludeRegions) - for _, region := range agentCmdState.InstallIncludeRegions { - discoveredInstances, err = instances.EnumerateInstancesInProject(context.Background(), nil, region, projectID) - if err != nil { - return nil, err - } - } - } else { - discoveredInstances, err = instances.EnumerateInstancesInProject(context.Background(), nil, "", projectID) - if err != nil { - return nil, err - } - } - cli.Log.Debugw("found instances", "instances", discoveredInstances) - - runners := []*lwrunner.GCPRunner{} - - for _, instance := range discoveredInstances { - // Filter out instances that are not in the RUNNING state - if instance.State != "RUNNING" { - continue - } - - // Filter instances by tag and tag key, if provided as CLI flag values - // NB that tags are another name for GCP "metadata" - if len(agentCmdState.InstallTag) == 2 { - cli.Log.Debugw("filtering on tag (metadata)", "tag", agentCmdState.InstallTag) - if tagVal, ok := instance.Props[agentCmdState.InstallTag[0]]; ok { // is tag key present? - if tagVal != agentCmdState.InstallTag[1] { // does tag value match? - continue // skip this instance if filter tag key and value are not present - } - } else { // tag key was not present, skip - continue - } - } - if agentCmdState.InstallTagKey != "" { - cli.Log.Debugw("filtering on tag (metadata) key", "tag key", agentCmdState.InstallTagKey) - if _, ok := instance.Props[agentCmdState.InstallTagKey]; !ok { - continue // skip this instance if filter tag key is not present - } - } - runner, err := lwrunner.NewGCPRunner( - instance.PublicIP, - parentUsername, - projectID, - instance.Zone, - instance.InstanceID, - verifyHostCallback, - ) - if err != nil { - return nil, err - } - runners = append(runners, runner) - } - cli.Log.Debugw("filtered list of runners", "runners", runners) - - return runners, nil -} - -func gcpGetProjectIDFromMetadataServer() (string, error) { - client := metadata.NewClient(&http.Client{}) - - projectID, err := client.ProjectID() - if err != nil { - err = fmt.Errorf("cannot get project details due to %s", err.Error()) - return "", err - } - - return projectID, nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate.go deleted file mode 100644 index 6bb5b640e..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate.go +++ /dev/null @@ -1,323 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - - "github.com/AlecAivazis/survey/v2" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - QuestionRunTfPlan = "Run Terraform plan now?" - QuestionUsePreviousCache = "Previous IaC generation detected, load cached values?" - - iacGenerateTfCommand = &cobra.Command{ - Use: "iac-generate", - Aliases: []string{"iac"}, - Short: "Create IaC code", - Long: "Create IaC content for various different cloud environments and configurations", - Deprecated: "This command is deprecated. Use 'generate'.", - Hidden: true, - } - - generateTfCommand = &cobra.Command{ - Use: "generate", - Aliases: []string{"gen"}, - Short: "Generate code to onboard your account", - Long: `Generate code to onboard your account and deploy Lacework into various cloud environments. - -This command creates Infrastructure as Code (IaC) in the form of Terraform HCL, with the option of running -Terraform and deploying Lacework into AWS, Azure, GCP or OCI. -`, - } -) - -func init() { - rootCmd.AddCommand(generateTfCommand) - - //Deprecated - cloudAccountCommand.AddCommand(iacGenerateTfCommand) - initGenerateAwsTfCommandFlags() - initGenerateGcpTfCommandFlags() - initGenerateAzureTfCommandFlags() - iacGenerateTfCommand.AddCommand(generateAwsTfCommand) - iacGenerateTfCommand.AddCommand(generateGcpTfCommand) - iacGenerateTfCommand.AddCommand(generateAzureTfCommand) - - // aws subcommands - generateAwsTfCommand.AddCommand(generateAwsControlTowerTfCommand) - - // Common flags - generateTfCommand.PersistentFlags().Bool( - "apply", - false, - "run terraform apply without executing plan or prompting", - ) - generateTfCommand.PersistentFlags().String( - "output", - "", - "location to write generated content", - ) -} - -type SurveyQuestionWithValidationArgs struct { - Prompt survey.Prompt - // Supplied checks can be used to validate IF the question should be asked - Checks []*bool - Response interface{} - Opts []survey.AskOpt - Required bool - Icon string -} - -// SurveyQuestionInteractiveOnly Prompt use for question, only if the CLI is in interactive mode -func SurveyQuestionInteractiveOnly(question SurveyQuestionWithValidationArgs) error { - // Do validations pass? - ok := true - for _, v := range question.Checks { - if !*v { - ok = false - } - } - - // If the optional check doesn't pass, skip - if !ok { - return nil - } - - // If required is set, add that question opt - if question.Required { - question.Opts = append(question.Opts, survey.WithValidator(survey.Required)) - } - - // Add custom icon - if question.Icon != "" { - question.Opts = append(question.Opts, survey.WithIcons(customPromptIconsFunc(question.Icon))) - } else { - question.Opts = append(question.Opts, survey.WithIcons(promptIconsFunc)) - } - - // If noninteractive is not set, ask the question - if !cli.nonInteractive { - err := survey.AskOne(question.Prompt, question.Response, question.Opts...) - if err != nil { - return err - } - } - - return nil -} - -// SurveyMultipleQuestionWithValidation Prompt for many values at once -// -// checks: If supplied check(s) are true, questions will be asked -func SurveyMultipleQuestionWithValidation(questions []SurveyQuestionWithValidationArgs, checks ...bool) error { - // Do validations pass? - ok := true - for _, v := range checks { - if !v { - ok = false - } - } - - // Ask questions - if ok { - for _, qs := range questions { - if err := SurveyQuestionInteractiveOnly(qs); err != nil { - return err - } - } - } - return nil -} - -// determineOutputDirPath get output directory location based on how the output location was set -func determineOutputDirPath(location string, cloud string) (string, error) { - // determine code output path - dirname, err := os.UserHomeDir() - if err != nil { - return "", err - } - - // If location was passed, return that location - if location != "" { - return filepath.FromSlash(location), nil - } - - // If location was not passed, assemble it with lacework from os homedir - return filepath.FromSlash(fmt.Sprintf("%s/%s/%s", dirname, "lacework", cloud)), nil -} - -// writeHclOutputPreCheck Prompt for confirmation if main.tf already exists; return true to continue -func writeHclOutputPreCheck(outputLocation string, cloud string) (bool, error) { - // If noninteractive, continue - if !cli.InteractiveMode() { - return true, nil - } - - outputDir, err := determineOutputDirPath(outputLocation, cloud) - if err != nil { - return false, err - } - - hclPath := filepath.FromSlash(fmt.Sprintf("%s/main.tf", outputDir)) - - // If the file doesn't exist, carry on - if _, err := os.Stat(hclPath); os.IsNotExist(err) { - return true, nil - } - - // If it does exist; confirm overwrite - answer := false - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: fmt.Sprintf("%s already exists, overwrite?", hclPath)}, - Response: &answer, - }); err != nil { - return false, err - } - - return answer, nil -} - -// writeHclOutput Write HCL output -func writeHclOutput(hcl string, location string, cloud string) (string, error) { - // Determine write location - dirname, err := determineOutputDirPath(location, cloud) - if err != nil { - return "", err - } - - // check if output location exists and if it's a file - outputDirLocation, err := os.Stat(dirname) - if !os.IsNotExist(err) && !outputDirLocation.IsDir() { - return "", fmt.Errorf("output location %s already exists and is a file", dirname) - } - - // Create directory, if needed - if os.IsNotExist(err) { - directory := filepath.FromSlash(dirname) - if _, err := os.Stat(directory); os.IsNotExist(err) { - err = os.MkdirAll(directory, 0700) - if err != nil { - return "", err - } - } - } - - // Create HCL file - outputLocation := filepath.FromSlash(fmt.Sprintf("%s/main.tf", dirname)) - err = os.WriteFile( - filepath.FromSlash(outputLocation), - []byte(hcl), - 0700, - ) - if err != nil { - return "", err - } - - cli.StopProgress() - return outputLocation, nil -} - -// validateOutputLocation This function used to validate provided output location exists and is a directory -func validateOutputLocation(dirname string) error { - // If output location was supplied, validate it exists - if dirname != "" { - outputLocation, err := os.Stat(dirname) - if err != nil && !os.IsNotExist(err) { - return errors.Wrap(err, "could not access specified output location") - } - - if err == nil && !outputLocation.IsDir() { - return errors.New("output location already exists and is a file") - } - } - - return nil -} - -// validateStringWithRegex create survey.Validator for string with regex -func validateStringWithRegex(val interface{}, regex string, errorString string) error { - switch value := val.(type) { - case string: - // if value doesn't match regex, return invalid arn - ok, err := regexp.MatchString(regex, value) - if err != nil { - return errors.Wrap(err, "failed to validate input") - } - - if !ok { - return errors.New(errorString) - } - default: - // if the value passed is not a string - return errors.New("value must be a string") - } - - return nil -} - -// Used to test if path supplied for output exists -func validPathExists(val interface{}) error { - switch value := val.(type) { - case string: - // Test if supplied path exists - if err := validateOutputLocation(value); err != nil { - return err - } - default: - // if the value passed is not a string - return errors.New("value must be a string") - } - - return nil -} - -// writeGeneratedCodeToLocation Write-out generated code to location specified -func writeGeneratedCodeToLocation(cmd *cobra.Command, hcl string, cloud string) (string, string, error) { - //dirname, ok, location := "", false, "" - // Write-out generated code to location specified - dirname, err := cmd.Flags().GetString("output") - if err != nil { - return dirname, "", errors.Wrap(err, "failed to parse output location") - } - - ok, err := writeHclOutputPreCheck(dirname, cloud) - if err != nil { - return dirname, "", errors.Wrap(err, "failed to validate output location") - } - - if !ok { - return dirname, "", errors.Wrap(err, "aborting to avoid overwriting existing terraform code") - } - - location, err := writeHclOutput(hcl, dirname, cloud) - if err != nil { - return dirname, location, errors.Wrap(err, "failed to write terraform code to disk") - } - - return dirname, location, nil -} - -// executionPreRunChecks Execution pre-run check -func executionPreRunChecks(dirname string, locationDir string, cloud string) error { - ok, err := TerraformExecutePreRunCheck(dirname, cloud) - if err != nil { - return errors.Wrap(err, "failed to check for existing terraform state") - } - - if !ok { - cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) - return nil - } - - if err := TerraformPlanAndExecute(locationDir); err != nil { - return errors.Wrap(err, "failed to run terraform apply") - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws.go deleted file mode 100644 index 9ac57d32e..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws.go +++ /dev/null @@ -1,1645 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "regexp" - "strings" - "time" - - "github.com/imdario/mergo" - "github.com/spf13/cobra" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/internal/format" - "github.com/lacework/go-sdk/lwgenerate/aws" - "github.com/pkg/errors" -) - -// Question labels -const ( - IconAgentless = "[Agentless]" - IconConfig = "[Configuration]" - IconCloudTrail = "[CloudTrail]" -) - -var ( - // Define question text here so they can be reused in testing - // Core questions - QuestionEnableAwsOrganization = "Enable integrations for AWS organization?" - QuestionMainAwsProfile = "Main AWS account profile:" - QuestionMainAwsRegion = "Main AWS account region:" - - // Agentless questions - QuestionEnableAgentless = "Enable Agentless integration?" - QuestionAgentlessManagementAccountID = "AWS management account ID:" - QuestionAgentlessManagementAccountRegion = "AWS management account region:" - - QuestionAgentlessScanningAccountProfile = "Scanning AWS account profile:" - QuestionAgentlessScanningAccountRegion = "Scanning AWS account region:" - QuestionAgentlessScanningAccountAddMore = "Add another scanning AWS account?" - - QuestionAgentlessScanningAccountsReplace = "Currently configured scanning accounts: %s, replace?" - QuestionAgentlessMonitoredAccountIDs = "Monitored AWS account ID list:" - QuestionAgentlessMonitoredAccountIDsHelp = "Please provide a comma separated list that may " + - "contain account IDs, OUs, or the organization root (e.g. 123456789000,ou-abcd-12345678,r-abcd)." - - QuestionAgentlessMonitoredAccountProfile = "Monitored AWS account profile:" - QuestionAgentlessMonitoredAccountRegion = "Monitored AWS account region:" - - // Config questions - QuestionEnableConfig = "Enable Configuration integration?" - QuestionConfigAdditionalAccountProfile = "Additional AWS account profile:" - QuestionConfigAdditionalAccountRegion = "Additional AWS account region:" - QuestionConfigAdditionalAccountsReplace = "Currently configured additional accounts: %s, replace?" - QuestionConfigAdditionalAccountAddMore = "Add another AWS account?" - - // Config Org questions - QuestionConfigOrgLWAccount = "Lacework account:" - QuestionConfigOrgLWSubaccount = "Lacework subaccount (optional):" - QuestionConfigOrgLWAccessKeyId = "Lacework access key ID:" - QuestionConfigOrgLWSecretKey = "Lacework secret key:" - QuestionConfigOrgId = "AWS organization ID:" - QuestionConfigOrgUnits = "AWS organization units (multiple can be supplied comma separated):" - QuestionConfigOrgCfResourcePrefix = "Cloudformation resource prefix:" - - // CloudTrail questions - QuestionEnableCloudtrail = "Enable CloudTrail integration?" - QuestionCloudtrailName = "Existing trail name:" - QuestionCloudtrailAdvanced = "Configure advanced options?" - - // CloudTrail Control Tower questions - QuestionControlTower = "Is your AWS organzation using Control Tower?" - QuestionControlTowerS3BucketArn = "AWS Control Tower S3 bucket ARN:" - QuestionControlTowerSnsTopicArn = "AWS Control Tower SNS topic ARN:" - QuestionControlTowerAuditAccountProfile = "AWS Control Tower audit account profile:" - QuestionControlTowerAuditAccountRegion = "AWS Control Tower audit account region:" - QuestionControlTowerLogArchiveAccountProfile = "AWS Control Tower log archive account profile:" - QuestionControlTowerLogArchiveAccountRegion = "AWS Control Tower log archive account region:" - QuestionControlTowerKmsKeyArn = "AWS Control Tower custom KMS Key ARN (optional):" - - // CloudTrail advanced options - OptCloudtrailMessage = "Which options would you like to configure?" - - OptCloudtrailOrg = "Configure org account mappings" - OptCloudtrailKmsKeyArn = "Configure custom KMS key" - OptCloudtrailS3 = "Configure S3 bucket" - OptCloudtrailSNS = "Configure SNS topic" - OptCloudtrailSQS = "Configure SQS queue" - OptCloudtrailIAM = "Configure an existing IAM role" - OptCloudtrailDone = "Done" - - // CloudTrail Org questions - QuestionCloudtrailOrgAccountMappingsDefaultLWAccount = "Org account mappings default Lacework account:" - QuestionCloudtrailOrgAccountMappingsAnotherAddMore = "Add another org account mapping?" - QuestionCloudtrailOrgAccountMappingsLWAccount = "Lacework account:" - QuestionCloudtrailOrgAccountMappingsAwsAccounts = "AWS accounts:" - - // CloudTrail S3 Bucket Questions - QuestionCloudtrailUseConsolidated = "Use consolidated CloudTrail?" - QuestionCloudtrailUseExistingTrail = "Use an existing CloudTrail?" - QuestionCloudtrailS3ExistingBucketArn = "Existing S3 bucket ARN used for CloudTrail logs:" - QuestionCloudtrailS3BucketEnableEncryption = "Enable S3 bucket encryption" - - QuestionCloudtrailS3BucketSseKeyArn = "Existing KMS encryption key arn for S3 bucket (optional):" - QuestionCloudtrailS3BucketName = "New S3 bucket name (optional):" - QuestionCloudtrailS3BucketNotification = "Enable S3 bucket notifications" - - // CloudTrail SNS Topic Questions - QuestionCloudtrailUseExistingSNSTopic = "Use an existing SNS topic? (If not, S3 notification will be used)" - QuestionCloudtrailSnsExistingTopicArn = "Existing SNS topic arn:" - QuestionCloudtrailSnsEnableEncryption = "Enable encryption on SNS topic?" - QuestionCloudtrailSnsEncryptionKeyArn = "Existing KMS encryption key arn for SNS topic (optional):" - QuestionCloudtrailSnsTopicName = "New SNS topic name (optional):" - - // CloudTrail SQS Queue Questions - QuestionCloudtrailSqsEnableEncryption = "Enable encryption on SQS queue:" - QuestionCloudtrailSqsEncryptionKeyArn = "Existing KMS encryption key arn for SQS queue (optional):" - QuestionCloudtrailSqsQueueName = "New SQS queue name (optional):" - - // CloudTrail IAM Role Questions - QuestionCloudtrailExistingIamRoleName = "Existing IAM role name for CloudTrail access:" - QuestionCloudtrailExistingIamRoleArn = "Existing IAM role ARN for CloudTrail access:" - QuestionCloudtrailExistingIamRoleExtID = "External ID for the existing IAM role:" - - // Custom location Question - QuestionAwsOutputLocation = "Custom output location (optional):" - - // Other options - AwsAdvancedOptDone = "Done" // Used in aws controltower and eks_audit - - // AwsArnRegex original source: https://regex101.com/r/pOfxYN/1 - AwsArnRegex = `^arn:(?P[^:\n]*):(?P[^:\n]*):(?P[^:\n]*):(?P[^:\n]*):(?P(?P[^:\/\n]*)[:\/])?(?P.*)$` //nolint - // AwsRegionRegex regex used for validating region input; note intentionally does not match gov cloud - AwsRegionRegex = `(af|ap|ca|eu|me|sa|us)-(central|(north|south)?(east|west)?)-\d` - AwsProfileRegex = `([A-Za-z_0-9-]+)` - AwsAccountIDRegex = `^\d{12}$` - AwsOUIDRegex = `^ou-[0-9a-z]{4,32}-[a-z0-9]{8,32}$` - AWSRootIDRegex = `^r-[0-9a-z]{4,32}$` - AwsAssumeRoleRegex = `^arn:aws:iam::\d{12}:role\/.*$` - ValidateSubAccountFlagRegex = fmt.Sprintf(`%s:%s`, AwsProfileRegex, AwsRegionRegex) - AwsCfResourcePrefixRegex = `^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$` - - GenerateAwsCommandState = &aws.GenerateAwsTfConfigurationArgs{ - ExistingIamRole: &aws.ExistingIamRoleDetails{}, - } - GenerateAwsCommandExtraState = &aws.AwsGenerateCommandExtraState{} - - CachedAwsArgsKey = "iac-aws-generate-args" - CachedAwsExtraStateKey = "iac-aws-extra-state" - - // aws command is used to generate TF code for aws - generateAwsTfCommand = &cobra.Command{ - Use: "aws", - Short: "Generate and/or execute Terraform code for AWS integration", - Long: `Use this command to generate Terraform code for deploying Lacework into an AWS environment. - -By default, this command interactively prompts for the required information to setup the new cloud account. -In interactive mode, this command will: - -* Prompt for the required information to setup the integration -* Generate new Terraform code using the inputs -* Optionally, run the generated Terraform code: - * If Terraform is already installed, the version is verified as compatible for use - * If Terraform is not installed, or the version installed is not compatible, a new - version will be installed into a temporary location - * Once Terraform is detected or installed, Terraform plan will be executed - * The command will prompt with the outcome of the plan and allow to view more details - or continue with Terraform apply - * If confirmed, Terraform apply will be run, completing the setup of the cloud account - -This command can also be run in noninteractive mode. -See help output for more details on the parameter value(s) required for Terraform code generation. -`, - RunE: func(cmd *cobra.Command, args []string) error { - // Generate TF Code - cli.StartProgress("Generating Terraform Code...") - - // Explicitly set Lacework profile if it was passed in main args - if cli.Profile != "default" { - GenerateAwsCommandState.LaceworkProfile = cli.Profile - } - - // Setup modifiers for NewTerraform constructor - mods := []aws.AwsTerraformModifier{ - aws.WithAwsProfile(GenerateAwsCommandState.AwsProfile), - aws.WithAwsRegion(GenerateAwsCommandState.AwsRegion), - aws.WithAwsAssumeRole(GenerateAwsCommandState.AwsAssumeRole), - aws.WithLaceworkProfile(GenerateAwsCommandState.LaceworkProfile), - aws.WithLaceworkAccountID(GenerateAwsCommandState.LaceworkAccountID), - aws.WithAgentlessManagementAccountID(GenerateAwsCommandState.AgentlessManagementAccountID), - aws.WithAgentlessMonitoredAccountIDs(GenerateAwsCommandState.AgentlessMonitoredAccountIDs), - aws.WithAgentlessMonitoredAccounts(GenerateAwsCommandState.AgentlessMonitoredAccounts...), - aws.WithAgentlessScanningAccounts(GenerateAwsCommandState.AgentlessScanningAccounts...), - aws.WithConfigAdditionalAccounts(GenerateAwsCommandState.ConfigAdditionalAccounts...), - aws.WithConfigOrgLWAccount(GenerateAwsCommandState.ConfigOrgLWAccount), - aws.WithConfigOrgLWSubaccount(GenerateAwsCommandState.ConfigOrgLWSubaccount), - aws.WithConfigOrgLWAccessKeyId(GenerateAwsCommandState.ConfigOrgLWAccessKeyId), - aws.WithConfigOrgLWSecretKey(GenerateAwsCommandState.ConfigOrgLWSecretKey), - aws.WithConfigOrgId(GenerateAwsCommandState.ConfigOrgId), - aws.WithConfigOrgUnits(GenerateAwsCommandState.ConfigOrgUnits), - aws.WithConfigOrgCfResourcePrefix(GenerateAwsCommandState.ConfigOrgCfResourcePrefix), - aws.WithControlTower(GenerateAwsCommandState.ControlTower), - aws.WithControlTowerAuditAccount(GenerateAwsCommandState.ControlTowerAuditAccount), - aws.WithControlTowerLogArchiveAccount(GenerateAwsCommandState.ControlTowerLogArchiveAccount), - aws.WithControlTowerKmsKeyArn(GenerateAwsCommandState.ControlTowerKmsKeyArn), - aws.WithConsolidatedCloudtrail(GenerateAwsCommandState.ConsolidatedCloudtrail), - aws.WithCloudtrailUseExistingTrail(GenerateAwsCommandState.CloudtrailUseExistingTrail), - aws.WithCloudtrailUseExistingSNSTopic(GenerateAwsCommandState.CloudtrailUseExistingSNSTopic), - aws.WithExistingCloudtrailBucketArn(GenerateAwsCommandState.ExistingCloudtrailBucketArn), - aws.WithExistingSnsTopicArn(GenerateAwsCommandState.ExistingSnsTopicArn), - aws.WithSubaccounts(GenerateAwsCommandState.SubAccounts...), - aws.WithExistingIamRole(GenerateAwsCommandState.ExistingIamRole), - aws.WithCloudtrailName(GenerateAwsCommandState.CloudtrailName), - aws.WithOrgAccountMappings(GenerateAwsCommandState.OrgAccountMappings), - aws.WithBucketName(GenerateAwsCommandState.BucketName), - aws.WithBucketEncryptionEnabled(GenerateAwsCommandState.BucketEncryptionEnabled), - aws.WithBucketSSEKeyArn(GenerateAwsCommandState.BucketSseKeyArn), - aws.WithSnsTopicName(GenerateAwsCommandState.SnsTopicName), - aws.WithSnsTopicEncryptionEnabled(GenerateAwsCommandState.SnsTopicEncryptionEnabled), - aws.WithSnsTopicEncryptionKeyArn(GenerateAwsCommandState.SnsTopicEncryptionKeyArn), - aws.WithSqsQueueName(GenerateAwsCommandState.SqsQueueName), - aws.WithSqsEncryptionEnabled(GenerateAwsCommandState.SqsEncryptionEnabled), - aws.WithSqsEncryptionKeyArn(GenerateAwsCommandState.SqsEncryptionKeyArn), - aws.WithS3BucketNotification(GenerateAwsCommandState.S3BucketNotification), - } - - // Create new struct - data := aws.NewTerraform( - GenerateAwsCommandState.AwsOrganization, - GenerateAwsCommandState.Agentless, - GenerateAwsCommandState.Config, - GenerateAwsCommandState.Cloudtrail, - mods..., - ) - - // Generate - hcl, err := data.Generate() - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "failed to generate terraform code") - } - - // Write-out generated code to location specified - dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "aws") - if err != nil { - return err - } - - // Prompt to execute - err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Default: GenerateAwsCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, - Response: &GenerateAwsCommandExtraState.TerraformApply, - }) - - if err != nil { - return errors.Wrap(err, "failed to prompt for terraform execution") - } - - locationDir, _ := determineOutputDirPath(dirname, "aws") - if GenerateAwsCommandExtraState.TerraformApply { - // Execution pre-run check - err := executionPreRunChecks(dirname, locationDir, "aws") - if err != nil { - return err - } - } - - // Output where code was generated - if !GenerateAwsCommandExtraState.TerraformApply { - cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) - } - - return nil - }, - PreRunE: func(cmd *cobra.Command, _ []string) error { - // Validate output location is OK if supplied - dirname, err := cmd.Flags().GetString("output") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateOutputLocation(dirname); err != nil { - return err - } - - // Validate aws assume role, if passed - assumeRole, err := cmd.Flags().GetString("aws_assume_role") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsAssumeRole(assumeRole); assumeRole != "" && err != nil { - return err - } - - // Validate aws profile, if passed - profile, err := cmd.Flags().GetString("aws_profile") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsProfile(profile); profile != "" && err != nil { - return err - } - - // Validate aws region, if passed - region, err := cmd.Flags().GetString("aws_region") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsRegion(region); region != "" && err != nil { - return err - } - - // Parse cloudtrail org_account_mapping json, if passed - if cmd.Flags().Changed("cloudtrail_org_account_mapping") { - if err := parseCloudtrailOrgAccountMappingsFlag(GenerateAwsCommandState); err != nil { - return err - } - } - - // Validate cloudtrail bucket arn, if passed - arn, err := cmd.Flags().GetString("existing_bucket_arn") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsArnFormat(arn); arn != "" && err != nil { - return err - } - if arn != "" { - GenerateAwsCommandState.CloudtrailUseExistingTrail = true - } - - // Validate SNS Topic Arn if passed - arn, err = cmd.Flags().GetString("existing_sns_topic_arn") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsArnFormat(arn); arn != "" && err != nil { - return err - } - if arn != "" { - GenerateAwsCommandState.CloudtrailUseExistingSNSTopic = true - } - - // Load any cached inputs if interactive - if cli.InteractiveMode() { - cachedOptions := &aws.GenerateAwsTfConfigurationArgs{} - awsArgsExpired := cli.ReadCachedAsset(CachedAwsArgsKey, &cachedOptions) - if awsArgsExpired { - cli.Log.Debug("loaded previously set values for AWS iac generation") - } - - extraState := &aws.AwsGenerateCommandExtraState{} - extraStateExpired := cli.ReadCachedAsset(CachedAwsExtraStateKey, &extraState) - if extraStateExpired { - cli.Log.Debug("loaded previously set values for AWS iac generation (extra state)") - } - - // Determine if previously cached options exists; prompt user if they'd like to continue - answer := false - if !awsArgsExpired || !extraStateExpired { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, - Response: &answer, - }); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - - // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs - // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get - // re-prompted. - if answer { - // Merge cached inputs to current options (current options win) - if err := mergo.Merge(GenerateAwsCommandState, cachedOptions); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - if err := mergo.Merge(GenerateAwsCommandExtraState, extraState); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - - // Collect and/or confirm parameters - err = promptAwsGenerate(GenerateAwsCommandState, GenerateAwsCommandExtraState) - if err != nil { - return errors.Wrap(err, "collecting/confirming parameters") - } - } - - // Parse passed in AWS accounts - if len(GenerateAwsCommandExtraState.AwsSubAccounts) > 0 { - accounts, err := parseAwsAccountsFromCommandFlag(GenerateAwsCommandExtraState.AwsSubAccounts) - if err != nil { - return err - } - GenerateAwsCommandState.SubAccounts = accounts - GenerateAwsCommandState.ConfigAdditionalAccounts = accounts - } - - // Parse passed in Agentless monirtoed AWS accounts - if len(GenerateAwsCommandExtraState.AgentlessMonitoredAccounts) > 0 { - accounts, err := parseAwsAccountsFromCommandFlag(GenerateAwsCommandExtraState.AgentlessMonitoredAccounts) - if err != nil { - return err - } - GenerateAwsCommandState.AgentlessMonitoredAccounts = accounts - } - - // Parse passed in Agentless scanning AWS accounts - if len(GenerateAwsCommandExtraState.AgentlessScanningAccounts) > 0 { - accounts, err := parseAwsAccountsFromCommandFlag(GenerateAwsCommandExtraState.AgentlessScanningAccounts) - if err != nil { - return err - } - GenerateAwsCommandState.AgentlessScanningAccounts = accounts - } - - // Parse passed in Control Tower Audit account - if GenerateAwsCommandExtraState.ControlTowerAuditAccount != "" { - accounts, err := parseAwsAccountsFromCommandFlag( - []string{GenerateAwsCommandExtraState.ControlTowerAuditAccount}, - ) - if err != nil { - return err - } - GenerateAwsCommandState.ControlTowerAuditAccount = &accounts[0] - } - - // Parse passed in Control Tower Log Archive account - if GenerateAwsCommandExtraState.ControlTowerLogArchiveAccount != "" { - accounts, err := parseAwsAccountsFromCommandFlag( - []string{GenerateAwsCommandExtraState.ControlTowerLogArchiveAccount}, - ) - if err != nil { - return err - } - GenerateAwsCommandState.ControlTowerLogArchiveAccount = &accounts[0] - } - - return nil - }, - } -) - -func parseAwsAccountsFromCommandFlag(accountsInput []string) ([]aws.AwsSubAccount, error) { - // Validate the format of supplied values is correct - if err := validateAwsSubAccounts(accountsInput); err != nil { - return nil, err - } - accounts := []aws.AwsSubAccount{} - for _, account := range accountsInput { - accountDetails := strings.Split(account, ":") - profile := accountDetails[0] - region := accountDetails[1] - alias := fmt.Sprintf("%s-%s", profile, region) - accounts = append(accounts, aws.NewAwsSubAccount(profile, region, alias)) - } - return accounts, nil -} - -func parseCloudtrailOrgAccountMappingsFlag(args *aws.GenerateAwsTfConfigurationArgs) error { - if err := json.Unmarshal([]byte(args.OrgAccountMappingsJson), &args.OrgAccountMappings); err != nil { - return errors.Wrap(err, "failed to parse 'cloudtrail_org_account_mapping'") - } - return nil -} - -func initGenerateAwsTfCommandFlags() { - // add flags to sub commands - // TODO Share the help with the interactive generation - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.AwsOrganization, - "aws_organization", - false, - "enable organization integration") - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.Agentless, - "agentless", - false, - "enable agentless integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.AgentlessManagementAccountID, - "agentless_management_account_id", - "", - "AWS management account ID for Agentless integration") - generateAwsTfCommand.PersistentFlags().StringSliceVar( - &GenerateAwsCommandState.AgentlessMonitoredAccountIDs, - "agentless_monitored_account_ids", - []string{}, - "AWS monitored account IDs for Agentless integrations; may "+ - "contain account IDs, OUs, or the organization root (e.g. 123456789000,ou-abcd-12345678,r-abcd)") - generateAwsTfCommand.PersistentFlags().StringSliceVar( - &GenerateAwsCommandExtraState.AgentlessMonitoredAccounts, - "agentless_monitored_accounts", - []string{}, - "AWS monitored accounts for Agentless integrations; value format must be :") - generateAwsTfCommand.PersistentFlags().StringSliceVar( - &GenerateAwsCommandExtraState.AgentlessScanningAccounts, - "agentless_scanning_accounts", - []string{}, - "AWS scanning accounts for Agentless integrations; value format must be :") - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.ControlTower, - "controltower", - false, - "enable Control Tower integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandExtraState.ControlTowerAuditAccount, - "controltower_audit_account", - "", - "specify AWS Control Tower Audit account; value format must be :") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandExtraState.ControlTowerLogArchiveAccount, - "controltower_log_archive_account", - "", - "specify AWS Control Tower Log Archive account; value format must be :") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ControlTowerKmsKeyArn, - "controltower_kms_key_arn", - "", - "specify AWS Control Tower custom kMS key ARN") - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.Cloudtrail, - "cloudtrail", - false, - "enable cloudtrail integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.CloudtrailName, - "cloudtrail_name", - "", - "specify name of cloudtrail integration") - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.Config, - "config", - false, - "enable config integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ConfigOrgLWAccount, - "config_lacework_account", - "", - "specify lacework account for Config organization integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ConfigOrgLWSubaccount, - "config_lacework_sub_account", - "", - "specify lacework sub-account for Config organization integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ConfigOrgLWAccessKeyId, - "config_lacework_access_key_id", - "", - "specify AWS access key ID for Config organization integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ConfigOrgLWSecretKey, - "config_lacework_secret_key", - "", - "specify AWS secret key for Config organization integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ConfigOrgId, - "config_organization_id", - "", - "specify AWS organization ID for Config organization integration") - generateAwsTfCommand.PersistentFlags().StringSliceVar( - &GenerateAwsCommandState.ConfigOrgUnits, - "config_organization_units", - nil, - "specify AWS organization units for Config organization integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ConfigOrgCfResourcePrefix, - "config_cf_resource_prefix", - "", - "specify Cloudformation resource prefix for Config organization integration") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.AwsRegion, - "aws_region", - "", - "specify aws region") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.AwsProfile, - "aws_profile", - "", - "specify aws profile") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.AwsAssumeRole, - "aws_assume_role", - "", - "specify aws assume role") - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.BucketEncryptionEnabled, - "bucket_encryption_enabled", - true, - "enable S3 bucket encryption when creating bucket") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.BucketName, - "bucket_name", - "", - "specify bucket name when creating bucket") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.BucketSseKeyArn, - "bucket_sse_key_arn", - "", - "specify existing KMS encryption key arn for bucket") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ExistingCloudtrailBucketArn, - "existing_bucket_arn", - "", - "specify existing cloudtrail S3 bucket ARN") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ExistingIamRole.Arn, - "existing_iam_role_arn", - "", - "specify existing iam role arn to use") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ExistingIamRole.Name, - "existing_iam_role_name", - "", - "specify existing iam role name to use") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ExistingIamRole.ExternalId, - "existing_iam_role_externalid", - "", - "specify existing iam role external_id to use") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ExistingSnsTopicArn, - "existing_sns_topic_arn", - "", - "specify existing SNS topic arn") - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.ConsolidatedCloudtrail, - "consolidated_cloudtrail", - false, - "use consolidated trail") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.OrgAccountMappingsJson, - "cloudtrail_org_account_mapping", "", "Org account mapping json string. Example: "+ - "'{\"default_lacework_account\":\"main\", \"mapping\": [{ \"aws_accounts\": [\"123456789011\"], "+ - "\"lacework_account\": \"sub-account-1\"}]}'") - - // DEPRECATED - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.ForceDestroyS3Bucket, - "force_destroy_s3", - true, - "enable force destroy S3 bucket") - errcheckWARN(generateAwsTfCommand.PersistentFlags().MarkDeprecated( - "force_destroy_s3", "by default, force destroy is enabled.", - )) - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.ConfigName, - "config_name", - "", - "specify name of config integration") - errcheckWARN(generateAwsTfCommand.PersistentFlags().MarkDeprecated( - "config_name", "default config is used.", - )) - // --- - - generateAwsTfCommand.PersistentFlags().StringSliceVar( - &GenerateAwsCommandExtraState.AwsSubAccounts, - "aws_subaccount", - []string{}, - "configure an additional aws account; value format must be :") - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandExtraState.TerraformApply, - "apply", - false, - "run terraform apply without executing plan or prompting", - ) - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandExtraState.Output, - "output", - "", - "location to write generated content (default is ~/lacework/aws)", - ) - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.SnsTopicEncryptionEnabled, - "sns_topic_encryption_enabled", - true, - "enable encryption on SNS topic when creating one") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.SnsTopicEncryptionKeyArn, - "sns_topic_encryption_key_arn", - "", - "specify existing KMS encryption key arn for SNS topic") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.SnsTopicName, - "sns_topic_name", - "", - "specify SNS topic name if creating new one") - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.SqsEncryptionEnabled, - "sqs_encryption_enabled", - true, - "enable encryption on SQS queue when creating") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.SqsEncryptionKeyArn, - "sqs_encryption_key_arn", - "", - "specify existing KMS encryption key arn for SQS queue") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.SqsQueueName, - "sqs_queue_name", - "", - "specify SQS queue name if creating new one") - generateAwsTfCommand.PersistentFlags().StringVar( - &GenerateAwsCommandState.LaceworkAccountID, - "lacework_aws_account_id", - "", - "the Lacework AWS root account id") - generateAwsTfCommand.PersistentFlags().BoolVar( - &GenerateAwsCommandState.S3BucketNotification, - "use_s3_bucket_notification", - false, - "enable S3 bucket notifications") -} - -// survey.Validator for aws ARNs -// -// This isn't service/type specific but rather just validates that an ARN was entered that matches valid ARN formats -func validateAwsArnFormat(val interface{}) error { - return validateStringWithRegex(val, AwsArnRegex, "invalid arn supplied") -} - -// Validate AWS Arn only if a value is set, this can be used for optional ARN cofiguration -func validateOptionalAwsArnFormat(val interface{}) error { - if val.(string) != "" { - return validateAwsArnFormat(val) - } - return nil -} - -func validateAwsAccountID(val interface{}) error { - return validateStringWithRegex(val, AwsAccountIDRegex, "invalid account ID supplied") -} - -func validateAwsSubAccounts(subaccounts []string) error { - // validate the format of supplied values is correct - for _, account := range subaccounts { - if ok, err := regexp.MatchString(ValidateSubAccountFlagRegex, account); !ok { - if err != nil { - return errors.Wrap(err, "failed to validate supplied subaccount format") - } - return errors.New("supplied aws subaccount in invalid format") - } - } - - return nil -} - -func validateAgentlessMonitoredAccountIDList(val interface{}) error { - switch value := val.(type) { - case string: - regex := fmt.Sprintf(`%s|%s|%s`, AwsAccountIDRegex, AwsOUIDRegex, AWSRootIDRegex) - ids := strings.Split(value, ",") - for _, id := range ids { - if err := validateStringWithRegex( - id, - regex, - fmt.Sprintf("invalid account ID, OU ID or root ID supplied: %s", id), - ); err != nil { - return err - } - } - default: - // if the value passed is not a string - return errors.New("value must be a string") - } - return nil -} - -// survey.Validator for aws region -func validateAwsRegion(val interface{}) error { - return validateStringWithRegex(val, AwsRegionRegex, "invalid region supplied") -} - -// survey.Validator for aws profile -func validateAwsProfile(val interface{}) error { - return validateStringWithRegex(val, fmt.Sprintf(`^%s$`, AwsProfileRegex), "invalid profile name supplied") -} - -// survey.Validator for aws assume role -func validateAwsAssumeRole(val interface{}) error { - return validateStringWithRegex(val, AwsAssumeRoleRegex, "invalid assume name supplied") -} - -// survey.Validator for CloudFormation resource prefix -func validateAwsCfResourcePrefix(val interface{}) error { - return validateStringWithRegex(val, AwsCfResourcePrefixRegex, "invalid CloudFormation resource name prefix supplied") -} - -func promptAgentlessQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - if !config.Agentless { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: IconAgentless, - Prompt: &survey.Confirm{Message: QuestionEnableAgentless, Default: config.Agentless}, - Response: &config.Agentless, - }); err != nil { - return err - } - } - - if !config.Agentless { - return nil - } - - if config.AwsOrganization { - monitoredAccountIDListInput := "" - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconAgentless, - Prompt: &survey.Input{ - Message: QuestionAgentlessManagementAccountID, - Default: config.AgentlessManagementAccountID, - }, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsAccountID)}, - Response: &config.AgentlessManagementAccountID, - Required: true, - }, - { - Icon: IconAgentless, - Prompt: &survey.Input{ - Message: QuestionAgentlessMonitoredAccountIDs, - Default: strings.Join(config.AgentlessMonitoredAccountIDs, ","), - Help: QuestionAgentlessMonitoredAccountIDsHelp, - }, - Opts: []survey.AskOpt{survey.WithValidator(validateAgentlessMonitoredAccountIDList)}, - Response: &monitoredAccountIDListInput, - Required: true, - }, - }, config.AwsOrganization); err != nil { - return err - } - - config.AgentlessMonitoredAccountIDs = strings.Split(monitoredAccountIDListInput, ",") - config.AgentlessMonitoredAccounts = []aws.AwsSubAccount{} - - // Prompt user to enter profile/region for single accounts - for _, accountID := range config.AgentlessMonitoredAccountIDs { - err := validateAwsAccountID(accountID) - if err != nil { - continue - } - var profile, region string - profileMessage := fmt.Sprintf( - "%s for account %s:", - QuestionAgentlessMonitoredAccountProfile[:len(QuestionAgentlessMonitoredAccountProfile)-1], - accountID, - ) - regionMessage := fmt.Sprintf( - "%s for account %s:", - QuestionAgentlessMonitoredAccountRegion[:len(QuestionAgentlessMonitoredAccountRegion)-1], - accountID, - ) - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconAgentless, - Prompt: &survey.Input{Message: profileMessage}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsProfile)}, - Required: true, - Response: &profile, - }, - { - Icon: IconAgentless, - Prompt: &survey.Input{Message: regionMessage}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, - Required: true, - Response: ®ion, - }, - }); err != nil { - return err - } - alias := fmt.Sprintf("%s-%s", profile, region) - config.AgentlessMonitoredAccounts = append( - config.AgentlessMonitoredAccounts, - aws.AwsSubAccount{AwsProfile: profile, AwsRegion: region, Alias: alias}, - ) - } - } - - if err := promptAwsAccountsQuestions( - &config.AgentlessScanningAccounts, - IconAgentless, - QuestionAgentlessScanningAccountProfile, - QuestionAgentlessScanningAccountRegion, - QuestionAgentlessScanningAccountAddMore, - QuestionAgentlessScanningAccountsReplace, - !config.AwsOrganization, - ); err != nil { - return err - } - - return nil -} - -func promptConfigQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - if !config.Config { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: IconConfig, - Prompt: &survey.Confirm{Message: QuestionEnableConfig, Default: config.Config}, - Response: &config.Config, - }); err != nil { - return err - } - } - - if !config.Config { - return nil - } - - tempLwSecretKey := "" - lwSecretKeyMessage := QuestionConfigOrgLWSecretKey - if len(config.ConfigOrgLWSecretKey) > 0 { - lwSecretKeyMessage = fmt.Sprintf( - "%s: (%s)", - QuestionConfigOrgLWSecretKey, - format.Secret(4, config.ConfigOrgLWSecretKey), - ) - } - - if config.AwsOrganization { - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconConfig, - Prompt: &survey.Input{Message: QuestionConfigOrgLWAccount, Default: config.ConfigOrgLWAccount}, - Response: &config.ConfigOrgLWAccount, - Required: true, - }, - { - Icon: IconConfig, - Prompt: &survey.Input{Message: QuestionConfigOrgLWSubaccount, Default: config.ConfigOrgLWSubaccount}, - Response: &config.ConfigOrgLWSubaccount, - }, - { - Icon: IconConfig, - Prompt: &survey.Input{Message: QuestionConfigOrgLWAccessKeyId, Default: config.ConfigOrgLWAccessKeyId}, - Response: &config.ConfigOrgLWAccessKeyId, - Required: true, - }, - { - Icon: IconConfig, - Prompt: &survey.Password{Message: lwSecretKeyMessage}, - Response: &tempLwSecretKey, - Required: config.ConfigOrgLWSecretKey == "", - }, - { - Icon: IconConfig, - Prompt: &survey.Input{Message: QuestionConfigOrgId, Default: config.ConfigOrgId}, - Response: &config.ConfigOrgId, - Required: true, - }, - }); err != nil { - return err - } - - // Use newly entered secret key, otherwise use the cached value - if tempLwSecretKey != "" { - config.ConfigOrgLWSecretKey = tempLwSecretKey - } - - var orgUnitsInput string - if err := survey.AskOne( - &survey.Input{Message: QuestionConfigOrgUnits, Default: strings.Join(config.ConfigOrgUnits, ",")}, &orgUnitsInput, - survey.WithValidator(survey.Required), survey.WithIcons(customPromptIconsFunc(IconConfig)), - ); err != nil { - return err - } - config.ConfigOrgUnits = strings.Split(orgUnitsInput, ",") - - if err := survey.AskOne( - &survey.Input{ - Message: QuestionConfigOrgCfResourcePrefix, Default: config.ConfigOrgCfResourcePrefix, - }, &config.ConfigOrgCfResourcePrefix, - survey.WithValidator(survey.Required), - survey.WithValidator(validateAwsCfResourcePrefix), - survey.WithIcons(customPromptIconsFunc(IconConfig)), - ); err != nil { - return err - } - - return nil - } - - if err := promptAwsAccountsQuestions( - &config.ConfigAdditionalAccounts, - IconConfig, - QuestionConfigAdditionalAccountProfile, - QuestionConfigAdditionalAccountRegion, - QuestionConfigAdditionalAccountAddMore, - QuestionConfigAdditionalAccountsReplace, - true, - ); err != nil { - return err - } - - return nil -} - -func promptCloudtrailQuestions( - config *aws.GenerateAwsTfConfigurationArgs, - extraState *aws.AwsGenerateCommandExtraState, -) error { - if !config.Cloudtrail { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: IconCloudTrail, - Prompt: &survey.Confirm{Message: QuestionEnableCloudtrail, Default: config.Cloudtrail}, - Response: &config.Cloudtrail, - }); err != nil { - return err - } - } - - if !config.Cloudtrail { - return nil - } - - if err := promptCloudtrailControlTowerQuestions(config); err != nil { - return err - } - - noControlTower := !config.ControlTower - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: IconCloudTrail, - Prompt: &survey.Confirm{Message: QuestionCloudtrailUseConsolidated, Default: config.ConsolidatedCloudtrail}, - Response: &config.ConsolidatedCloudtrail, - Checks: []*bool{&noControlTower}, - }); err != nil { - return err - } - - if err := promptCloudtrailExistingTrailQuestions(config); err != nil { - return err - } - - // Find out if the customer wants to specify more advanced features - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: IconCloudTrail, - Prompt: &survey.Confirm{Message: QuestionCloudtrailAdvanced, Default: extraState.CloudtrailAdvanced}, - Response: &extraState.CloudtrailAdvanced, - }); err != nil { - return err - } - - // Keep prompting for advanced options until the say done - if extraState.CloudtrailAdvanced { - answer := "" - options := []string{ - OptCloudtrailS3, - OptCloudtrailSNS, - OptCloudtrailSQS, - OptCloudtrailIAM, - OptCloudtrailDone, - } - if config.CloudtrailUseExistingTrail { - options = []string{ - OptCloudtrailSQS, - OptCloudtrailIAM, - OptCloudtrailDone, - } - } - if config.AwsOrganization { - if config.ControlTower { - options = []string{ - OptCloudtrailKmsKeyArn, - OptCloudtrailIAM, - OptCloudtrailDone, - } - } - options = append([]string{OptCloudtrailOrg}, options...) - } - for answer != OptCloudtrailDone { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: IconCloudTrail, - Prompt: &survey.Select{ - Message: OptCloudtrailMessage, - Options: options, - }, - Response: &answer, - }); err != nil { - return err - } - switch answer { - case OptCloudtrailOrg: - if err := promptCloudtrailOrgQuestions(config); err != nil { - return err - } - case OptCloudtrailKmsKeyArn: - if err := promptCloudtrailKmsKeyQuestions(config); err != nil { - return err - } - case OptCloudtrailS3: - if err := promptCloudtrailS3Questions(config); err != nil { - return err - } - case OptCloudtrailSNS: - if err := promptCloudtrailSNSQuestions(config); err != nil { - return err - } - case OptCloudtrailSQS: - if err := promptCloudtrailSQSQuestions(config); err != nil { - return err - } - case OptCloudtrailIAM: - if err := promptCloudtrailIAMQuestions(config); err != nil { - return err - } - } - } - } - - return nil -} - -func promptCloudtrailControlTowerQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: IconCloudTrail, - Prompt: &survey.Confirm{Message: QuestionControlTower, Default: config.ControlTower}, - Response: &config.ControlTower, - Checks: []*bool{&config.AwsOrganization}, - }); err != nil { - return err - } - - if !config.ControlTower { - return nil - } - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{ - Message: QuestionControlTowerS3BucketArn, - Default: config.ExistingCloudtrailBucketArn, - }, - Required: true, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &config.ExistingCloudtrailBucketArn, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{ - Message: QuestionControlTowerSnsTopicArn, - Default: config.ExistingSnsTopicArn, - }, - Required: true, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &config.ExistingSnsTopicArn, - }, - }); err != nil { - return err - } - - profile := "" - region := "" - defaultProfile := "" - defaultRegion := "" - if config.ControlTowerAuditAccount != nil { - defaultProfile = config.ControlTowerAuditAccount.AwsProfile - } - if config.ControlTowerAuditAccount != nil { - defaultRegion = config.ControlTowerAuditAccount.AwsRegion - } - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{ - Message: QuestionControlTowerAuditAccountProfile, - Default: defaultProfile, - }, - Required: true, - Response: &profile, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{ - Message: QuestionControlTowerAuditAccountRegion, - Default: defaultRegion, - }, - Required: true, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, - Response: ®ion, - }, - }); err != nil { - return err - } - - config.ControlTowerAuditAccount = &aws.AwsSubAccount{ - AwsProfile: profile, - AwsRegion: region, - Alias: fmt.Sprintf("%s-%s", profile, region), - } - - defaultProfile = "" - defaultRegion = "" - if config.ControlTowerLogArchiveAccount != nil { - defaultProfile = config.ControlTowerLogArchiveAccount.AwsProfile - } - if config.ControlTowerLogArchiveAccount != nil { - defaultRegion = config.ControlTowerLogArchiveAccount.AwsRegion - } - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{ - Message: QuestionControlTowerLogArchiveAccountProfile, - Default: defaultProfile, - }, - Required: true, - Response: &profile, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{ - Message: QuestionControlTowerLogArchiveAccountRegion, - Default: defaultRegion, - }, - Required: true, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, - Response: ®ion, - }, - }); err != nil { - return err - } - - config.ControlTowerLogArchiveAccount = &aws.AwsSubAccount{ - AwsProfile: profile, - AwsRegion: region, - Alias: fmt.Sprintf("%s-%s", profile, region), - } - - return nil -} - -func promptCloudtrailOrgAccountMappingQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - mapping := aws.OrgAccountMap{} - var accountsAnswer string - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailOrgAccountMappingsLWAccount}, - Response: &mapping.LaceworkAccount, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Multiline{Message: QuestionCloudtrailOrgAccountMappingsAwsAccounts}, - Response: &accountsAnswer, - }, - }); err != nil { - return err - } - mapping.AwsAccounts = strings.Split(accountsAnswer, "\n") - config.OrgAccountMappings.Mapping = append(config.OrgAccountMappings.Mapping, mapping) - return nil -} - -func promptCloudtrailOrgQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{ - Message: QuestionCloudtrailOrgAccountMappingsDefaultLWAccount, - Default: config.OrgAccountMappings.DefaultLaceworkAccount}, - Response: &config.OrgAccountMappings.DefaultLaceworkAccount, - }, - }); err != nil { - return err - } - - askAgain := true - for askAgain { - if err := promptCloudtrailOrgAccountMappingQuestions(config); err != nil { - return err - } - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: IconCloudTrail, - Prompt: &survey.Confirm{Message: QuestionCloudtrailOrgAccountMappingsAnotherAddMore}, - Response: &askAgain}); err != nil { - return err - } - } - - return nil -} - -func promptCloudtrailKmsKeyQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: IconCloudTrail, - Prompt: &survey.Input{ - Message: QuestionControlTowerKmsKeyArn, - Default: config.ControlTowerKmsKeyArn, - }, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &config.ControlTowerKmsKeyArn, - }); err != nil { - return err - } - - return nil -} - -func promptCloudtrailExistingTrailQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - if config.ControlTower { - return nil - } - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Confirm{Message: QuestionCloudtrailUseExistingTrail, Default: config.CloudtrailUseExistingTrail}, - Response: &config.CloudtrailUseExistingTrail, - }, - }); err != nil { - return err - } - - if !config.CloudtrailUseExistingTrail { - return nil - } - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailName, Default: config.CloudtrailName}, - Response: &config.CloudtrailName, - Required: true, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{ - Message: QuestionCloudtrailS3ExistingBucketArn, - Default: config.ExistingCloudtrailBucketArn, - }, - Required: true, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &config.ExistingCloudtrailBucketArn, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Confirm{ - Message: QuestionCloudtrailUseExistingSNSTopic, - Default: config.CloudtrailUseExistingSNSTopic, - }, - Response: &config.CloudtrailUseExistingSNSTopic, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailSnsExistingTopicArn, Default: config.ExistingSnsTopicArn}, - Checks: []*bool{&config.CloudtrailUseExistingSNSTopic}, - Required: true, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &config.ExistingSnsTopicArn, - }, - }); err != nil { - return err - } - - // If no SNS topic is provided for the existing trail, fallback to use S3 notification - if !config.CloudtrailUseExistingSNSTopic { - config.S3BucketNotification = true - } - - return nil -} - -func promptCloudtrailS3Questions(config *aws.GenerateAwsTfConfigurationArgs) error { - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailS3BucketName, Default: config.BucketName}, - Response: &config.BucketName, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Confirm{ - Message: QuestionCloudtrailS3BucketEnableEncryption, - Default: config.BucketEncryptionEnabled, - }, - Response: &config.BucketEncryptionEnabled, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailS3BucketSseKeyArn, Default: config.BucketSseKeyArn}, - Response: &config.BucketSseKeyArn, - Opts: []survey.AskOpt{survey.WithValidator(validateOptionalAwsArnFormat)}, - Checks: []*bool{&config.BucketEncryptionEnabled}, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Confirm{Message: QuestionCloudtrailS3BucketNotification, Default: config.S3BucketNotification}, - Response: &config.S3BucketNotification, - }, - }, !config.CloudtrailUseExistingTrail); err != nil { - return err - } - - return nil -} - -func promptCloudtrailSNSQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailSnsTopicName, Default: config.SnsTopicName}, - Response: &config.SnsTopicName, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Confirm{Message: QuestionCloudtrailSnsEnableEncryption, Default: config.SnsTopicEncryptionEnabled}, - Response: &config.SnsTopicEncryptionEnabled, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailSnsEncryptionKeyArn, Default: config.SnsTopicEncryptionKeyArn}, - Response: &config.SnsTopicEncryptionKeyArn, - Opts: []survey.AskOpt{survey.WithValidator(validateOptionalAwsArnFormat)}, - Checks: []*bool{&config.SnsTopicEncryptionEnabled}, - }, - }, !config.CloudtrailUseExistingSNSTopic); err != nil { - return err - } - - return nil -} - -func promptCloudtrailSQSQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailSqsQueueName, Default: config.SqsQueueName}, - Response: &config.SqsQueueName, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Confirm{Message: QuestionCloudtrailSqsEnableEncryption, Default: config.SqsEncryptionEnabled}, - Response: &config.SqsEncryptionEnabled, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailSqsEncryptionKeyArn, Default: config.SqsEncryptionKeyArn}, - Response: &config.SqsEncryptionKeyArn, - Opts: []survey.AskOpt{survey.WithValidator(validateOptionalAwsArnFormat)}, - Checks: []*bool{&config.SqsEncryptionEnabled}, - }, - }); err != nil { - return err - } - return nil -} - -func promptCloudtrailIAMQuestions(config *aws.GenerateAwsTfConfigurationArgs) error { - // ensure struct is initialized - if config.ExistingIamRole == nil { - config.ExistingIamRole = &aws.ExistingIamRoleDetails{} - } - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailExistingIamRoleName, Default: config.ExistingIamRole.Name}, - Response: &config.ExistingIamRole.Name, - Required: true, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailExistingIamRoleArn, Default: config.ExistingIamRole.Arn}, - Response: &config.ExistingIamRole.Arn, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Required: true, - }, - { - Icon: IconCloudTrail, - Prompt: &survey.Input{Message: QuestionCloudtrailExistingIamRoleExtID, Default: config.ExistingIamRole.ExternalId}, - Response: &config.ExistingIamRole.ExternalId, - Required: true, - }}); err != nil { - return err - } - - return nil -} - -func promptAwsAccountsQuestions( - accounts *[]aws.AwsSubAccount, - questionIcon string, - questionProfile string, - questionRegion string, - questionAddMore string, - questionReplace string, - askFirst bool, -) error { - if !cli.InteractiveMode() { - return nil - } - - askAgain := true - newAccounts := []aws.AwsSubAccount{} - - // Ask if replacing existing accounts - if len(*accounts) > 0 { - accountListing := []string{} - for _, account := range *accounts { - accountListing = append( - accountListing, - fmt.Sprintf("%s:%s", account.AwsProfile, account.AwsRegion), - ) - } - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: questionIcon, - Prompt: &survey.Confirm{ - Message: fmt.Sprintf( - questionReplace, - strings.Trim(strings.Join(strings.Fields(fmt.Sprint(accountListing)), ", "), "[]"), - ), - }, - Response: &askAgain, - }); err != nil { - return err - } - } - - if askFirst { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: questionIcon, - Prompt: &survey.Confirm{Message: questionAddMore}, - Response: &askAgain, - }); err != nil { - return err - } - } - - for askAgain { - var profile, region string - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Icon: questionIcon, - Prompt: &survey.Input{Message: questionProfile}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsProfile)}, - Required: true, - Response: &profile, - }, - { - Icon: questionIcon, - Prompt: &survey.Input{Message: questionRegion}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, - Required: true, - Response: ®ion, - }, - }); err != nil { - return err - } - alias := fmt.Sprintf("%s-%s", profile, region) - newAccounts = append( - newAccounts, - aws.AwsSubAccount{AwsProfile: profile, AwsRegion: region, Alias: alias}, - ) - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Icon: questionIcon, - Prompt: &survey.Confirm{Message: questionAddMore}, - Response: &askAgain, - }); err != nil { - return err - } - } - - if len(newAccounts) > 0 { - *accounts = newAccounts - } - - return nil -} - -func writeArgsCache(a *aws.GenerateAwsTfConfigurationArgs) { - if !a.IsEmpty() { - // If ExistingIamRole is partially set, don't write this to cache; the values won't work when loaded - if a.ExistingIamRole.IsPartial() { - a.ExistingIamRole = nil - } - cli.WriteAssetToCache(CachedAwsArgsKey, time.Now().Add(time.Hour*1), a) - } -} - -func writeExtraStateCache(a *aws.AwsGenerateCommandExtraState) { - if !a.IsEmpty() { - cli.WriteAssetToCache(CachedAwsExtraStateKey, time.Now().Add(time.Hour*1), a) - } -} - -// Entry point for launching a survey to build out the required generation parameters -func promptAwsGenerate( - config *aws.GenerateAwsTfConfigurationArgs, - extraState *aws.AwsGenerateCommandExtraState, -) error { - // Cache for later use if generation is abandon and in interactive mode - if cli.InteractiveMode() { - defer writeArgsCache(config) - defer writeExtraStateCache(extraState) - } - - // Core questions - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{ - Message: QuestionEnableAwsOrganization, - Default: config.AwsOrganization, - }, - Response: &config.AwsOrganization, - }, - { - Prompt: &survey.Input{Message: QuestionMainAwsProfile, Default: config.AwsProfile}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsProfile)}, - Response: &config.AwsProfile, - Required: true, - }, - { - Prompt: &survey.Input{Message: QuestionMainAwsRegion, Default: config.AwsRegion}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, - Response: &config.AwsRegion, - Required: true, - }, - }); err != nil { - return err - } - - if err := promptAgentlessQuestions(config); err != nil { - return err - } - if err := promptConfigQuestions(config); err != nil { - return err - } - if err := promptCloudtrailQuestions(config, extraState); err != nil { - return err - } - - // Custom ouput location - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionAwsOutputLocation, Default: extraState.Output}, - Response: &extraState.Output, - Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, - }); err != nil { - return err - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_controltower.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_controltower.go deleted file mode 100644 index 8eede5f4e..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_controltower.go +++ /dev/null @@ -1,730 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/lacework/go-sdk/lwgenerate/aws_controltower" - - "github.com/imdario/mergo" - "github.com/spf13/cobra" - - "github.com/AlecAivazis/survey/v2" - "github.com/pkg/errors" -) - -var ( - QuestionAwsControlTowerCoreS3Bucket = "Provide the Arn of the S3 Bucket for consolidated CloudTrail:" - QuestionAwsControlTowerCoreSnsTopic = "Provide the Arn of the SNS Topic:" - QuestionAwsControlTowerCoreLogProfile = "Provide the aws profile of the 'log_archive' account:" - QuestionAwsControlTowerCoreLogRegion = "Provide the aws region of the 'log_archive' account:" - QuestionAwsControlTowerCoreAuditProfile = "Provide the aws profile of the 'audit' account:" - QuestionAwsControlTowerCoreAuditRegion = "Provide the aws region of the 'audit' account:" - QuestionAwsControlTowerConfigureAdvanced = "Configure advanced integration options?" - QuestionAwsControlTowerCustomizeOutputLocation = "Provide the location for the output to be written:" - - ControlTowerConfigureExistingIamRoleOpt = "Configure existing Iam Role?" - QuestionAwsControlTowerCoreIamRoleName = "Specify Existing Iam Role name:" - QuestionAwsControlTowerCoreIamRoleArn = "Specify Existing Iam Arn:" - QuestionAwsControlTowerCoreIamRoleExternalID = "Specify Existing Iam Role external ID:" - ControlTowerIntegrationNameOpt = "Customize integration name?" - QuestionControlTowerIntegrationName = "Specify a custom integration name:" - ControlTowerIntegrationPrefixOpt = "Customize resource prefix name?" - QuestionControlTowerPrefix = "Specify a prefix name for resources:" - ControlTowerIntegrationSqsOpt = "Customize sqs queue name?" - QuestionControlTowerSqsQueueName = "Specify a name for sqs queue:" - QuestionControlTowerOrgAccountMappingsLWDefaultAccount = "Specify org account mappings default Lacework account:" - QuestionControlTowerOrgAccountMappingAnotherAdvancedOpt = "Configure another org account mapping?" - QuestionControlTowerOrgAccountMappingsLWAccount = "Specify lacework account: " - QuestionControlTowerOrgAccountMappingsAwsAccounts = "Specify aws accounts:" - ControlTowerAdvancedOptLocation = "Customize output location" - ControlTowerAdvancedOptMappings = "Configure Org Account Mappings" - QuestionControlTowerAnotherAdvancedOpt = "Configure another advanced integration option?" - ControlTowerAdvancedOptDone = "Done" - - GenerateAwsControlTowerCommandState = &aws_controltower.GenerateAwsControlTowerTfConfigurationArgs{} - GenerateAwsControlTowerCommandExtraState = &AwsControlTowerGenerateCommandExtraState{} - CachedAssetAwsControlTowerIacParams = "iac-aws-controltower-generate-params" - CachedAssetAwsControlTowerExtraState = "iac-aws-controltower-extra-state" - - generateAwsControlTowerTfCommand = &cobra.Command{ - Use: "controltower", - Short: "Generate and/or execute Terraform code for ControlTower integration", - Long: `Use this command to generate Terraform code for deploying Lacework with Aws Cloudtrail and -ControlTower. - -By default, this command interactively prompts for the required information to set up the new cloud account. -In interactive mode, this command will: - -* Prompt for the required information to set up the integration -* Generate new Terraform code using the inputs -* Optionally, run the generated Terraform code: - * If Terraform is already installed, the version is verified as compatible for use - * If Terraform is not installed, or the version installed is not compatible, a new - version will be installed into a temporary location - * Once Terraform is detected or installed, the Terraform plan is executed - * The command prompts you with the outcome of the plan and allows you to view more - details or continue with Terraform apply - * If confirmed, Terraform apply runs, completing the setup of the cloud account - -This command can also be run in noninteractive mode. -See help output for more details on the parameter values required for Terraform code generation. -`, - RunE: func(cmd *cobra.Command, args []string) error { - // Generate TF Code - cli.StartProgress("Generating Terraform Code...") - - // Explicitly set Lacework profile if it was passed in main args - if cli.Profile != "default" { - GenerateAwsControlTowerCommandState.LaceworkProfile = cli.Profile - } - - // Setup modifiers for NewTerraform constructor - mods := []aws_controltower.AwsControlTowerTerraformModifier{ - aws_controltower.WithLaceworkAccountID(GenerateAwsControlTowerCommandState.LaceworkAccountID), - aws_controltower.WithSubaccounts(GenerateAwsControlTowerCommandState.SubAccounts...), - aws_controltower.WithSqsQueueName(GenerateAwsControlTowerCommandState.SqsQueueName), - aws_controltower.WithPrefix(GenerateAwsControlTowerCommandState.Prefix), - aws_controltower.WithCrossAccountPolicyName(GenerateAwsControlTowerCommandState.CrossAccountPolicyName), - aws_controltower.WithSubaccounts(GenerateAwsControlTowerCommandState.SubAccounts...), - aws_controltower.WithLaceworkProfile(GenerateAwsControlTowerCommandState.LaceworkProfile), - aws_controltower.WithExternalIdLength(GenerateAwsControlTowerCommandState.ExternalIdLength), - aws_controltower.WithWaitTime(GenerateAwsControlTowerCommandState.WaitTime), - aws_controltower.WithTags(GenerateAwsControlTowerCommandState.Tags), - aws_controltower.WithKmsKeyArn(GenerateAwsControlTowerCommandState.KmsKeyArn), - aws_controltower.WithLaceworkIntegrationName(GenerateAwsControlTowerCommandState.LaceworkIntegrationName), - } - - if useExistingIamRole(GenerateAwsControlTowerCommandState) { - mods = append(mods, aws_controltower.WithExisitingIamRole( - GenerateAwsControlTowerCommandState.IamRoleArn, - GenerateAwsControlTowerCommandState.IamRoleName, - GenerateAwsControlTowerCommandState.IamRoleExternalID, - )) - } - - if !GenerateAwsControlTowerCommandState.OrgAccountMappings.IsEmpty() { - mods = append(mods, aws_controltower.WithOrgAccountMappings(GenerateAwsControlTowerCommandState.OrgAccountMappings)) - } - - if GenerateAwsControlTowerCommandState.EnableLogFileValidation { - mods = append(mods, aws_controltower.WithEnableLogFileValidation()) - } - - data := aws_controltower.NewTerraform( - GenerateAwsControlTowerCommandState.S3BucketArn, - GenerateAwsControlTowerCommandState.SNSTopicArn, - mods...) - - // Generate - hcl, err := data.Generate() - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "failed to generate terraform code") - } - - // Write-out generated code to location specified - dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "aws_controltower") - if err != nil { - return err - } - - // Prompt to execute - err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{ - Default: GenerateAwsControlTowerCommandExtraState.TerraformApply, - Message: QuestionRunTfPlan, - }, - Response: &GenerateAwsControlTowerCommandExtraState.TerraformApply, - }) - - if err != nil { - return errors.Wrap(err, "failed to prompt for terraform execution") - } - - // Execute - locationDir, _ := determineOutputDirPath(dirname, "aws_controltower") - if GenerateAwsControlTowerCommandExtraState.TerraformApply { - // Execution pre-run check - err := executionPreRunChecks(dirname, locationDir, "aws_controltower") - if err != nil { - return err - } - } - - // Output where code was generated - if !GenerateAwsControlTowerCommandExtraState.TerraformApply { - cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) - } - - return nil - }, - PreRunE: func(cmd *cobra.Command, _ []string) error { - // Validate output location is OK if supplied - dirname, err := cmd.Flags().GetString("output") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateOutputLocation(dirname); err != nil { - return err - } - - // Validate aws profile, if passed - profile, err := cmd.Flags().GetString("aws_profile") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsProfile(profile); profile != "" && err != nil { - return err - } - - // Validate s3_bucket_arn, if passed - s3BucketArn, err := cmd.Flags().GetString("s3_bucket_arn") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsArnFormat(s3BucketArn); s3BucketArn != "" && err != nil { - return err - } - - // Validate sns_topic_arn, if passed - snsTopicArn, err := cmd.Flags().GetString("sns_topic_arn") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsArnFormat(snsTopicArn); snsTopicArn != "" && err != nil { - return err - } - - // Parse audit_account, if passed - if cmd.Flags().Changed("audit_account") { - if err := parseAuditAccountFlag(GenerateAwsControlTowerCommandState); err != nil { - return err - } - } - - GenerateAwsControlTowerCommandState.SubAccounts = append(GenerateAwsControlTowerCommandState.SubAccounts, - aws_controltower.AwsSubAccount{ - AwsProfile: GenerateAwsControlTowerCommandState.AuditProfile, - AwsRegion: GenerateAwsControlTowerCommandState.AuditRegion}) - - // Parse log_archive_account, if passed - if cmd.Flags().Changed("log_archive_account") { - if err := parseLogArchiveAccountFlag(GenerateAwsControlTowerCommandState); err != nil { - return err - } - } - - GenerateAwsControlTowerCommandState.SubAccounts = append(GenerateAwsControlTowerCommandState.SubAccounts, - aws_controltower.AwsSubAccount{ - AwsProfile: GenerateAwsControlTowerCommandState.LogArchiveProfile, - AwsRegion: GenerateAwsControlTowerCommandState.LogArchiveRegion}) - - // Parse org_account_mapping json, if passed - if cmd.Flags().Changed("org_account_mapping") { - if err := parseOrgAccountMappingsFlag(GenerateAwsControlTowerCommandState); err != nil { - return err - } - } - - // Load any cached inputs if interactive - if cli.InteractiveMode() { - cachedOptions := &aws_controltower.GenerateAwsControlTowerTfConfigurationArgs{} - iacParamsExpired := cli.ReadCachedAsset(CachedAssetAwsControlTowerIacParams, &cachedOptions) - if iacParamsExpired { - cli.Log.Debug("loaded previously set values for AWS ControlTower IAC generation") - } - - extraState := &AwsControlTowerGenerateCommandExtraState{} - extraStateParamsExpired := cli.ReadCachedAsset(CachedAssetAwsControlTowerExtraState, &extraState) - if extraStateParamsExpired { - cli.Log.Debug("loaded previously set values for AWS ControlTower IAC generation (extra state)") - } - - // Determine if previously cached options exists; prompt user if they'd like to continue - answer := false - if !iacParamsExpired || !extraStateParamsExpired { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, - Response: &answer, - }); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - - // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs - // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get - // re-prompted. - if answer { - // Merge cached inputs to current options (current options win) - if err := mergo.Merge(GenerateAwsControlTowerCommandState, cachedOptions); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - if err := mergo.Merge(GenerateAwsControlTowerCommandExtraState, extraState); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - } - - // Collect and/or confirm parameters - err = promptAwsControlTowerGenerate(GenerateAwsControlTowerCommandState, - GenerateAwsControlTowerCommandExtraState) - if err != nil { - return errors.Wrap(err, "collecting/confirming parameters") - } - - return nil - }, - } -) - -func useExistingIamRole(args *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) bool { - return args.IamRoleArn != "" && args.IamRoleExternalID != "" && args.IamRoleName != "" -} - -func parseAuditAccountFlag(args *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { - parsedAccount := strings.Split(args.AuditAccount, ":") - if len(parsedAccount) != 2 { - return errors.New("invalid audit_account. Format must be 'profile:region'") - } - - args.AuditProfile = parsedAccount[0] - args.AuditRegion = parsedAccount[1] - return nil -} - -func parseLogArchiveAccountFlag(args *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { - parsedAccount := strings.Split(args.LogArchiveAccount, ":") - - if len(parsedAccount) != 2 { - fmt.Println(parsedAccount) - return errors.New("invalid log_archive_account. Format must be 'profile:region'") - } - - args.LogArchiveProfile = parsedAccount[0] - args.LogArchiveRegion = parsedAccount[1] - return nil -} - -func parseOrgAccountMappingsFlag(args *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { - if err := json.Unmarshal([]byte(args.OrgAccountMappingsJson), &args.OrgAccountMappings); err != nil { - return errors.Wrap(err, "failed to parse 'org_account_mapping'") - } - - return nil -} - -type AwsControlTowerGenerateCommandExtraState struct { - AskAdvanced bool - Output string - ConfigureBucketSettings bool - UseExistingKmsKey bool - MultiRegion bool - TerraformApply bool -} - -func (controltower *AwsControlTowerGenerateCommandExtraState) isEmpty() bool { - return controltower.Output == "" && - !controltower.AskAdvanced && - !controltower.ConfigureBucketSettings && - !controltower.UseExistingKmsKey && - !controltower.TerraformApply -} - -// Flush current state of the struct to disk, provided it's not empty -func (controltower *AwsControlTowerGenerateCommandExtraState) writeCache() { - if !controltower.isEmpty() { - cli.WriteAssetToCache(CachedAssetAwsControlTowerExtraState, time.Now().Add(time.Hour*1), controltower) - } -} - -func initGenerateAwsControlTowerTfCommandFlags() { - // add flags to sub commands - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.LaceworkAccountID, - "lacework_aws_account_id", "", "the Lacework AWS root account id") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.S3BucketArn, - "s3_bucket_arn", "", "the S3 Bucket for consolidated CloudTrail") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.SNSTopicArn, - "sns_topic_arn", "", "the SNS Topic") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.LogArchiveAccount, - "log_archive_account", "", "The log archive account flag input in the format profile:region") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.AuditAccount, - "audit_account", "", "The audit account flag input in the format profile:region") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.OrgAccountMappingsJson, - "org_account_mapping", "", "Org account mapping json string. Example: "+ - "'{\"default_lacework_account\":\"main\", \"mapping\": [{ \"aws_accounts\": [\"123456789011\"], "+ - "\"lacework_account\": \"sub-account-1\"}]}'") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.IamRoleExternalID, - "iam_role_external_id", - "", - "specify the external id of the existing iam role") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.IamRoleName, - "iam_role_name", - "", - "specify the name of the existing iam role") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.IamRoleArn, - "iam_role_arn", - "", - "specify the arn of the existing iam role") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.SqsQueueName, - "sqs_queue_name", - "", - "specify the name of the sqs queue") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandState.Prefix, - "prefix", - "", - "specify the prefix that will be used at the beginning of every generated resource") - - generateAwsControlTowerTfCommand.PersistentFlags().BoolVar( - &GenerateAwsControlTowerCommandExtraState.TerraformApply, - "apply", - false, - "run terraform apply without executing plan or prompting") - - generateAwsControlTowerTfCommand.PersistentFlags().StringVar( - &GenerateAwsControlTowerCommandExtraState.Output, - "output", - "", - "location to write generated content") -} - -func promptCustomizeControlTowerOutputLocation(extraState *AwsControlTowerGenerateCommandExtraState) error { - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionAwsControlTowerCustomizeOutputLocation, - Default: extraState.Output}, - Response: &extraState.Output, - Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, - Required: true, - }) - - return err -} - -func promptAwsIamRoleQuestions(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionAwsControlTowerCoreIamRoleName, - Default: input.IamRoleName, - }, - Opts: []survey.AskOpt{}, - Response: &input.IamRoleName, - }); err != nil { - return err - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionAwsControlTowerCoreIamRoleArn, - Default: input.IamRoleArn, - }, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &input.IamRoleArn, - }); err != nil { - return err - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionAwsControlTowerCoreIamRoleExternalID, - Default: input.IamRoleExternalID, - }, - Response: &input.IamRoleExternalID, - }); err != nil { - return err - } - - return nil -} - -func promptCustomIntegrationName(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionControlTowerIntegrationName, - Default: input.LaceworkIntegrationName, - }, - Opts: []survey.AskOpt{}, - Response: &input.LaceworkIntegrationName, - }) - - return err -} - -func promptCustomPrefix(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionControlTowerPrefix, - Default: input.Prefix, - }, - Opts: []survey.AskOpt{}, - Response: &input.Prefix, - }) - - return err -} - -func promptCustomSqsQueueName(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionControlTowerSqsQueueName, - Default: input.SqsQueueName, - }, - Opts: []survey.AskOpt{}, - Response: &input.SqsQueueName, - }) - - return err -} - -func promptControlTowerAddOrgAccountMappings(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { - mapping := aws_controltower.OrgAccountMap{} - var accountsAnswer string - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionControlTowerOrgAccountMappingsLWAccount}, - Response: &mapping.LaceworkAccount, - }, - { - Prompt: &survey.Multiline{Message: QuestionControlTowerOrgAccountMappingsAwsAccounts}, - Response: &accountsAnswer, - }, - }); err != nil { - return err - } - mapping.AwsAccounts = strings.Split(accountsAnswer, "\n") - input.OrgAccountMappings.Mapping = append(input.OrgAccountMappings.Mapping, mapping) - return nil -} - -func promptControlTowerOrgAccountMappings(input *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) error { - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{ - Message: QuestionControlTowerOrgAccountMappingsLWDefaultAccount, - Default: input.OrgAccountMappings.DefaultLaceworkAccount}, - Response: &input.OrgAccountMappings.DefaultLaceworkAccount, - }, - }); err != nil { - return err - } - - if err := promptControlTowerAddOrgAccountMappings(input); err != nil { - return err - } - - var askAgain bool - for { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionControlTowerOrgAccountMappingAnotherAdvancedOpt}, - Response: &askAgain}); err != nil { - return err - } - - if !askAgain { - break - } - - if err := promptControlTowerAddOrgAccountMappings(input); err != nil { - return err - } - } - - return nil -} - -func askAdvancedControlTowerOptions(config *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs, - extraState *AwsControlTowerGenerateCommandExtraState) error { - answer := "" - - //Prompt for options - for answer != AwsAdvancedOptDone { - var options []string - - options = append(options, - ControlTowerConfigureExistingIamRoleOpt, - ControlTowerIntegrationNameOpt, - ControlTowerAdvancedOptLocation, - ControlTowerIntegrationPrefixOpt, - ControlTowerIntegrationSqsOpt, - ControlTowerAdvancedOptMappings, - ControlTowerAdvancedOptDone) - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: "Which options would you like to configure?", - Options: options, - }, - Response: &answer, - }); err != nil { - return err - } - - // Based on response, prompt for actions - switch answer { - case ControlTowerConfigureExistingIamRoleOpt: - if err := promptAwsIamRoleQuestions(config); err != nil { - return err - } - config.UseExistingIamRole = true - case ControlTowerIntegrationNameOpt: - if err := promptCustomIntegrationName(config); err != nil { - return err - } - case ControlTowerIntegrationPrefixOpt: - if err := promptCustomPrefix(config); err != nil { - return err - } - case ControlTowerIntegrationSqsOpt: - if err := promptCustomSqsQueueName(config); err != nil { - return err - } - case ControlTowerAdvancedOptLocation: - if err := promptCustomizeControlTowerOutputLocation(extraState); err != nil { - return err - } - case ControlTowerAdvancedOptMappings: - if err := promptControlTowerOrgAccountMappings(config); err != nil { - return err - } - } - - // Re-prompt if not done - innerAskAgain := true - if answer == ControlTowerAdvancedOptDone { - innerAskAgain = false - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Checks: []*bool{&innerAskAgain}, - Prompt: &survey.Confirm{Message: QuestionControlTowerAnotherAdvancedOpt, Default: false}, - Response: &innerAskAgain, - }); err != nil { - return err - } - - if !innerAskAgain { - answer = AwsAdvancedOptDone - } - } - - return nil -} - -func controltowerConfigIsEmpty(g *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) bool { - return g.SNSTopicArn == "" && - g.S3BucketArn == "" && - g.LaceworkProfile == "" -} - -func writeAwsControlTowerGenerationArgsCache(a *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs) { - if !controltowerConfigIsEmpty(a) { - cli.WriteAssetToCache(CachedAssetAwsControlTowerIacParams, time.Now().Add(time.Hour*1), a) - } -} - -// entry point for launching a survey to build out the required generation parameters -func promptAwsControlTowerGenerate( - config *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs, - extraState *AwsControlTowerGenerateCommandExtraState, -) error { - - // Cache for later use if generation is abandoned and in interactive mode - if cli.InteractiveMode() { - defer writeAwsControlTowerGenerationArgsCache(config) - defer extraState.writeCache() - } - - // Set Flags if set - - // prompt ControlTower core questions - if err := promptAwsControlTowerCoreQuestions(config, extraState); err != nil { - return err - } - - // Find out if the customer wants to specify more advanced features - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionAwsControlTowerConfigureAdvanced, - Default: extraState.AskAdvanced}, - Response: &extraState.AskAdvanced, - }); err != nil { - return err - } - - // Keep prompting for advanced options until the say done - if extraState.AskAdvanced { - if err := askAdvancedControlTowerOptions(config, extraState); err != nil { - return err - } - } - - return nil -} - -func init() { - initGenerateAwsControlTowerTfCommandFlags() -} - -func promptAwsControlTowerCoreQuestions(config *aws_controltower.GenerateAwsControlTowerTfConfigurationArgs, - state *AwsControlTowerGenerateCommandExtraState) error { - if err := SurveyMultipleQuestionWithValidation( - []SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreS3Bucket, Default: config.S3BucketArn}, - Response: &config.S3BucketArn, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - }, - { - Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreSnsTopic, Default: config.SNSTopicArn}, - Response: &config.SNSTopicArn, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - }, - { - Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreLogProfile, Default: config.LogArchiveProfile}, - Response: &config.LogArchiveProfile, - }, - { - Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreLogRegion, Default: config.LogArchiveRegion}, - Response: &config.LogArchiveRegion, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, - }, - { - Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreAuditProfile, Default: config.AuditProfile}, - Response: &config.AuditProfile, - }, - { - Prompt: &survey.Input{Message: QuestionAwsControlTowerCoreAuditRegion, Default: config.AuditRegion}, - Response: &config.AuditRegion, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, - }, - }); err != nil { - return err - } - config.SubAccounts = []aws_controltower.AwsSubAccount{ - aws_controltower.NewAwsSubAccount(config.LogArchiveProfile, config.LogArchiveRegion, "log_archive"), - aws_controltower.NewAwsSubAccount(config.AuditProfile, config.AuditRegion, "audit")} - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_eks_audit.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_eks_audit.go deleted file mode 100644 index e992fa2b1..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_aws_eks_audit.go +++ /dev/null @@ -1,983 +0,0 @@ -package cmd - -import ( - "fmt" - "strconv" - "strings" - "time" - - "github.com/lacework/go-sdk/lwgenerate/aws_eks_audit" - - "github.com/imdario/mergo" - "github.com/spf13/cobra" - - "github.com/AlecAivazis/survey/v2" - "github.com/pkg/errors" -) - -var ( - // Define question text here, so they can be reused in testing - QuestionEksAuditMultiRegion = "Integrate clusters in more than one region?" - QuestionEksAuditRegionClusterCurrent = "Currently configured regions and clusters: %s. " + - "Configure additional?" - QuestionEksAuditRegion = "Specify AWS region:" - QuestionEksAuditRegionClusters = "Specify a comma-seperated list of clusters in region" + - " to ingest EKS Audit Logs:" - QuestionEksAuditAdditionalRegion = "Configure another AWS region?" - - QuestionEksAuditConfigureAdvanced = "Configure advanced integration options?" - - // S3 Bucket Questions - QuestionUseExistingBucket = "Use existing bucket?" - QuestionExistingBucketArn = "Specify an existing bucket ARN used for EKS audit log:" - EksAuditConfigureBucket = "Configure bucket settings" - QuestionEksAuditBucketVersioning = "Enable access versioning on the new bucket?" - QuestionEksAuditMfaDeleteS3Bucket = "Should MFA object deletion be required for the new bucket?" - QuestionEksAuditBucketLifecycle = "Specify the bucket lifecycle expiration days: (optional)" - QuestionEksAuditBucketEncryption = "Enable encryption for the new bucket?" - QuestionEksAuditBucketSseAlgorithm = "Specify the bucket SSE Algorithm: (optional)" - QuestionEksAuditBucketExistingKey = "Use existing KMS key?" - QuestionEksAuditBucketKeyArn = "Specify the bucket existing SSE KMS key ARN:" - QuestionEksAuditKmsKeyRotation = "Should the KMS key have rotation enabled?" - QuestionEksAuditKmsKeyDeletionDays = "Specify the KMS key deletion days: (optional)" - - // SNS Topic Questions - EksAuditConfigureSns = "Configure SNS settings" - QuestionEksAuditSnsEncryption = "Enable encryption on SNS topic when creating?" - QuestionEksAuditSnsEncryptionKeyArn = "Specify existing KMS encryption key ARN for SNS topic (optional)" - - // Cloudwatch IAM Questions - EksAuditExistingCwIamRole = "Configure and use existing Cloudwatch IAM role" - QuestionEksAuditExistingCwIamArn = "Specify an existing Cloudwatch IAM role ARN:" - - // Firehose Questions - EksAuditConfigureFh = "Configure Firehose settings" - QuestionEksAuditExistingFhIamRole = "Use existing Firehose IAM role?" - QuestionEksAuditExistingFhIamArn = "Specify an existing Firehose IAM role ARN:" - QuestionEksAuditFhEncryption = "Enable encryption on Firehose when creating?" - QuestionEksAuditFhEncryptionKeyArn = "Specify existing KMS encryption key ARN for Firehose (optional)" - - // Cross Account IAM Questions - EksAuditExistingCaIamRole = "Configure and use existing Cross Account IAM role" - QuestionEksAuditExistingCaIamArn = "Specify an existing Cross Account IAM role ARN:" - QuestionEksAuditExistingCaIamExtID = "Specify the external ID to be used with the existing IAM role:" - - // Customize integration name - EksAuditIntegrationNameOpt = "Customize integration name" - QuestionEksAuditCustomIntegrationName = "Specify a custom integration name: (optional)" - - // Customize output location - EksAuditAdvancedOptLocation = "Customize output location" - QuestionEksAuditCustomizeOutputLocation = "Provide the location for the output to be written:" - - QuestionEksAuditAnotherAdvancedOpt = "Configure another advanced integration option" - EksAuditAdvancedOptDone = "Done" - - // AwsEksAuditRegionRegex regex used for validating region input; note intentionally does not match gov cloud - AwsEksAuditRegionRegex = `(af|ap|ca|eu|me|sa|us)-(central|(north|south)?(east|west)?)-\d` - - GenerateAwsEksAuditCommandState = &aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs{} - GenerateAwsEksAuditCommandExtraState = &AwsEksAuditGenerateCommandExtraState{} - GenerateAwsEksAuditExistingRoleState = &aws_eks_audit.ExistingCrossAccountIamRoleDetails{} - CachedAssetAwsEksAuditIacParams = "iac-aws-eks-audit-generate-params" - CachedAssetAwsEksAuditExtraState = "iac-aws-eks-audit-extra-state" - - // aws-eks-audit-log command is used to generate TF code for aws-eks-audit-log - generateAwsEksAuditTfCommand = &cobra.Command{ - Use: "eks", - Short: "Generate and/or execute Terraform code for EKS integration", - Long: `Use this command to generate Terraform code for deploying Lacework into an EKS -environment. - -By default, this command interactively prompts for the required information to set up the new cloud account. -In interactive mode, this command will: - -* Prompt for the required information to set up the integration -* Generate new Terraform code using the inputs -* Optionally, run the generated Terraform code: - * If Terraform is already installed, the version is verified as compatible for use - * If Terraform is not installed, or the version installed is not compatible, a new - version will be installed into a temporary location - * Once Terraform is detected or installed, the Terraform plan is executed - * The command prompts you with the outcome of the plan and allows you to view more - details or continue with Terraform apply - * If confirmed, Terraform apply runs, completing the setup of the cloud account - -This command can also be run in noninteractive mode. -See help output for more details on the parameter values required for Terraform code generation. -`, - RunE: func(cmd *cobra.Command, args []string) error { - // Generate TF Code - cli.StartProgress("Generating Terraform Code...") - - // Explicitly set Lacework profile if it was passed in main args - if cli.Profile != "default" { - GenerateAwsEksAuditCommandState.LaceworkProfile = cli.Profile - } - - // Setup modifiers for NewTerraform constructor - mods := []aws_eks_audit.AwsEksAuditTerraformModifier{ - aws_eks_audit.WithAwsProfile(GenerateAwsEksAuditCommandState.AwsProfile), - aws_eks_audit.WithLaceworkAccountID(GenerateAwsEksAuditCommandState.LaceworkAccountID), - aws_eks_audit.WithBucketLifecycleExpirationDays(GenerateAwsEksAuditCommandState.BucketLifecycleExpirationDays), - aws_eks_audit.WithExistingBucketArn(GenerateAwsEksAuditCommandState.ExistinglBucketArn), - aws_eks_audit.WithBucketSseAlgorithm(GenerateAwsEksAuditCommandState.BucketSseAlgorithm), - aws_eks_audit.WithBucketSseKeyArn(GenerateAwsEksAuditCommandState.BucketSseKeyArn), - aws_eks_audit.WithEksAuditIntegrationName(GenerateAwsEksAuditCommandState.EksAuditIntegrationName), - aws_eks_audit.WithExistingCloudWatchIamRoleArn(GenerateAwsEksAuditCommandState.ExistingCloudWatchIamRoleArn), - aws_eks_audit.WithExistingCrossAccountIamRole(GenerateAwsEksAuditCommandState.ExistingCrossAccountIamRole), - aws_eks_audit.WithExistingFirehoseIamRoleArn(GenerateAwsEksAuditCommandState.ExistingFirehoseIamRoleArn), - aws_eks_audit.WithFilterPattern(GenerateAwsEksAuditCommandState.FilterPattern), - aws_eks_audit.WithFirehoseEncryptionKeyArn(GenerateAwsEksAuditCommandState.FirehoseEncryptionKeyArn), - aws_eks_audit.WithKmsKeyDeletionDays(GenerateAwsEksAuditCommandState.KmsKeyDeletionDays), - aws_eks_audit.WithPrefix(GenerateAwsEksAuditCommandState.Prefix), - aws_eks_audit.WithParsedRegionClusterMap(GenerateAwsEksAuditCommandState.ParsedRegionClusterMap), - aws_eks_audit.WithSnsTopicEncryptionKeyArn(GenerateAwsEksAuditCommandState.SnsTopicEncryptionKeyArn), - aws_eks_audit.WithLaceworkProfile(GenerateAwsEksAuditCommandState.LaceworkProfile), - aws_eks_audit.EnableBucketEncryption(GenerateAwsEksAuditCommandState.BucketEnableEncryption), - aws_eks_audit.EnableBucketVersioning(GenerateAwsEksAuditCommandState.BucketVersioning), - aws_eks_audit.EnableFirehoseEncryption(GenerateAwsEksAuditCommandState.FirehoseEncryptionEnabled), - aws_eks_audit.EnableSnsTopicEncryption(GenerateAwsEksAuditCommandState.SnsTopicEncryptionEnabled), - aws_eks_audit.EnableBucketVersioning(GenerateAwsEksAuditCommandState.BucketVersioning), - aws_eks_audit.EnableKmsKeyRotation(GenerateAwsEksAuditCommandState.KmsKeyRotation), - } - - if GenerateAwsEksAuditCommandState.UseExistinglBucket { - mods = append(mods, aws_eks_audit.EnableUseExistingBucket()) - } - - if GenerateAwsEksAuditCommandState.BucketEnableMfaDelete { - mods = append(mods, aws_eks_audit.EnableBucketMfaDelete()) - } - - // Create new struct - data := aws_eks_audit.NewTerraform(mods...) - - // Generate - hcl, err := data.Generate() - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "failed to generate terraform code") - } - - // Write-out generated code to location specified - dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "aws_eks_audit") - if err != nil { - return err - } - - // Prompt to execute - err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{ - Default: GenerateAwsEksAuditCommandExtraState.TerraformApply, - Message: QuestionRunTfPlan, - }, - Response: &GenerateAwsEksAuditCommandExtraState.TerraformApply, - }) - - if err != nil { - return errors.Wrap(err, "failed to prompt for terraform execution") - } - - // Execute - locationDir, _ := determineOutputDirPath(dirname, "aws_eks_audit") - if GenerateAwsEksAuditCommandExtraState.TerraformApply { - // Execution pre-run check - err := executionPreRunChecks(dirname, locationDir, "aws_eks_audit") - if err != nil { - return err - } - } - - // Output where code was generated - if !GenerateAwsEksAuditCommandExtraState.TerraformApply { - cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) - } - - return nil - }, - PreRunE: func(cmd *cobra.Command, _ []string) error { - // Validate output location is OK if supplied - dirname, err := cmd.Flags().GetString("output") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateOutputLocation(dirname); err != nil { - return err - } - - // Validate aws profile, if passed - profile, err := cmd.Flags().GetString("aws_profile") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsProfile(profile); profile != "" && err != nil { - return err - } - - // Validate bucket sse key ARN, if passed - bucketSseKeyArn, err := cmd.Flags().GetString("bucket_sse_key_arn") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsArnFormat(bucketSseKeyArn); bucketSseKeyArn != "" && err != nil { - return err - } - - // Validate firehose key ARN, if passed - firehoseKeyArn, err := cmd.Flags().GetString("firehose_encryption_key_arn") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsArnFormat(firehoseKeyArn); firehoseKeyArn != "" && err != nil { - return err - } - - // Validate sns topic key ARN, if passed - snsKeyArn, err := cmd.Flags().GetString("sns_topic_encryption_key_arn") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAwsArnFormat(snsKeyArn); snsKeyArn != "" && err != nil { - return err - } - - // Load any cached inputs if interactive - if cli.InteractiveMode() { - cachedOptions := &aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs{} - iacParamsExpired := cli.ReadCachedAsset(CachedAssetAwsEksAuditIacParams, &cachedOptions) - if iacParamsExpired { - cli.Log.Debug("loaded previously set values for AWS EKS Audit IAC generation") - } - - extraState := &AwsEksAuditGenerateCommandExtraState{} - extraStateParamsExpired := cli.ReadCachedAsset(CachedAssetAwsEksAuditExtraState, &extraState) - if extraStateParamsExpired { - cli.Log.Debug("loaded previously set values for AWS EKS Audit IAC generation (extra state)") - } - - // Determine if previously cached options exists; prompt user if they'd like to continue - answer := false - if !iacParamsExpired || !extraStateParamsExpired { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, - Response: &answer, - }); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - - // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs - // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get - // re-prompted. - if answer { - // Merge cached inputs to current options (current options win) - if err := mergo.Merge(GenerateAwsEksAuditCommandState, cachedOptions); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - if err := mergo.Merge(GenerateAwsEksAuditCommandExtraState, extraState); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - } - - // Parse regions passed as part of the region cluster map - if len(GenerateAwsEksAuditCommandState.RegionClusterMap) > 0 { - // validate the format of supplied values is correct - - awsParsedRegionClusterMap := make(map[string][]string) - for region, clusters := range GenerateAwsEksAuditCommandState.RegionClusterMap { - // verify each region is a valid aws region - if err := validateStringWithRegex(region, AwsEksAuditRegionRegex, - "invalid region name supplied"); err != nil { - return err - } - // parse the cluster comma-seperated string into a list of clusters - parsedClusters := strings.Split(clusters, ",") - awsParsedRegionClusterMap[region] = append(awsParsedRegionClusterMap[region], parsedClusters...) - } - GenerateAwsEksAuditCommandState.ParsedRegionClusterMap = awsParsedRegionClusterMap - } - - // Collect and/or confirm parameters - err = promptAwsEksAuditGenerate(GenerateAwsEksAuditCommandState, GenerateAwsEksAuditExistingRoleState, - GenerateAwsEksAuditCommandExtraState) - if err != nil { - return errors.Wrap(err, "collecting/confirming parameters") - } - - return nil - }, - } -) - -type AwsEksAuditGenerateCommandExtraState struct { - AskAdvanced bool - Output string - ConfigureBucketSettings bool - UseExistingKmsKey bool - MultiRegion bool - TerraformApply bool -} - -func (eks *AwsEksAuditGenerateCommandExtraState) isEmpty() bool { - return eks.Output == "" && - !eks.AskAdvanced && - !eks.ConfigureBucketSettings && - !eks.UseExistingKmsKey && - !eks.TerraformApply -} - -// Flush current state of the struct to disk, provided it's not empty -func (eks *AwsEksAuditGenerateCommandExtraState) writeCache() { - if !eks.isEmpty() { - cli.WriteAssetToCache(CachedAssetAwsEksAuditExtraState, time.Now().Add(time.Hour*1), eks) - } -} - -func initGenerateAwsEksAuditTfCommandFlags() { - // add flags to sub commands - // TODO Share the help with the interactive generation - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.AwsProfile, "aws_profile", "", "specify aws profile") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.LaceworkAccountID, - "lacework_aws_account_id", "", "the Lacework AWS root account id") - generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( - &GenerateAwsEksAuditCommandState.BucketEnableMfaDelete, - "enable_mfa_delete_s3", - false, - "enable mfa delete on s3 bucket. Requires bucket versioning.") - generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( - &GenerateAwsEksAuditCommandState.BucketEnableEncryption, - "enable_encryption_s3", - true, - "enable encryption on s3 bucket") - - // DEPRECATED - generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( - &GenerateAwsEksAuditCommandState.BucketForceDestroy, - "enable_force_destroy", - true, - "enable force destroy s3 bucket") - errcheckWARN(generateAwsEksAuditTfCommand.PersistentFlags().MarkDeprecated( - "enable_force_destroy", "by default, force destroy is enabled.", - )) - // --- - - generateAwsEksAuditTfCommand.PersistentFlags().IntVar( - &GenerateAwsEksAuditCommandState.BucketLifecycleExpirationDays, - "bucket_lifecycle_exp_days", - 0, - "specify the s3 bucket lifecycle expiration days") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.BucketSseAlgorithm, - "bucket_sse_algorithm", - "", - "specify the encryption algorithm to use for S3 bucket server-side encryption") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.BucketSseKeyArn, - "bucket_sse_key_arn", - "", - "specify the kms key arn to be used for s3. "+ - "(required when bucket_sse_algorithm is aws:kms & using an existing kms key)") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.ExistinglBucketArn, - "existing_bucket_arn", - "", - "specify existing s3 bucket arn for the audit log") - generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( - &GenerateAwsEksAuditCommandState.UseExistinglBucket, - "use_existing_bucket", - false, - "use existing supplied s3 bucket (default false)") - generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( - &GenerateAwsEksAuditCommandState.BucketVersioning, - "enable_bucket_versioning", - true, - "enable s3 bucket versioning") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.EksAuditIntegrationName, - "integration_name", - "", - "specify the name of the eks audit integration") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.ExistingCloudWatchIamRoleArn, - "existing_cw_iam_role_arn", - "", - "specify existing cloudwatch iam role arn to use") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditExistingRoleState.Arn, - "existing_ca_iam_role_arn", - "", - "specify existing cross account iam role arn to use") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditExistingRoleState.ExternalId, - "existing_ca_iam_role_external_id", - "", - "specify existing cross account iam role external_id to use") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.ExistingFirehoseIamRoleArn, - "existing_firehose_iam_role_arn", - "", - "specify existing firehose iam role arn to use") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.FilterPattern, - "custom_filter_pattern", - "", - "specify a custom cloudwatch log filter pattern") - generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( - &GenerateAwsEksAuditCommandState.FirehoseEncryptionEnabled, - "enable_firehose_encryption", - true, - "enable firehose encryption") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.FirehoseEncryptionKeyArn, - "firehose_encryption_key_arn", - "", - "specify the kms key arn to be used with the Firehose") - generateAwsEksAuditTfCommand.PersistentFlags().IntVar( - &GenerateAwsEksAuditCommandState.KmsKeyDeletionDays, - "kms_key_deletion_days", - 0, - "specify the kms waiting period before deletion, in number of days") - generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( - &GenerateAwsEksAuditCommandState.KmsKeyRotation, - "enable_kms_key_rotation", - true, - "enable automatic kms key rotation") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.Prefix, - "prefix", - "", - "specify the prefix that will be used at the beginning of every generated resource") - generateAwsEksAuditTfCommand.PersistentFlags().StringToStringVar( - &GenerateAwsEksAuditCommandState.RegionClusterMap, - "region_clusters", - map[string]string{}, - "configure eks clusters per aws region. To configure multiple regions pass the flag"+ - " multiple times. Example format: --region_clusters =\"cluster,list\"") - generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( - &GenerateAwsEksAuditCommandState.SnsTopicEncryptionEnabled, - "enable_sns_topic_encryption", - true, - "enable encryption on the sns topic") - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandState.SnsTopicEncryptionKeyArn, - "sns_topic_encryption_key_arn", - "", - "specify the kms key arn to be used with the sns topic") - generateAwsEksAuditTfCommand.PersistentFlags().BoolVar( - &GenerateAwsEksAuditCommandExtraState.TerraformApply, - "apply", - false, - "run terraform apply without executing plan or prompting", - ) - generateAwsEksAuditTfCommand.PersistentFlags().StringVar( - &GenerateAwsEksAuditCommandExtraState.Output, - "output", - "", - "location to write generated content", - ) -} - -// Validate the response is of type int -func validateResponseTypeInt(val interface{}) error { - switch value := val.(type) { - case string: - if _, err := strconv.Atoi(value); err != nil { - // if the value passed is not of type int - return errors.New("value must be a number") - } - default: - // if the value passed is not a string - return errors.New("value must be a string") - } - return nil -} - -func promptAwsEksAuditBucketQuestions(config *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs) error { - // Only ask these questions if configure bucket is true - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionUseExistingBucket, Default: config.UseExistinglBucket}, - Response: &config.UseExistinglBucket, - }, - { - Prompt: &survey.Input{Message: QuestionExistingBucketArn, Default: config.ExistinglBucketArn}, - Checks: []*bool{&config.UseExistinglBucket}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &config.ExistinglBucketArn, - }, - }); err != nil { - return err - } - - // Only ask the next questions if the user did not indicate they wanted to use and existing bucket - if config.UseExistinglBucket { - return nil - } - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionEksAuditBucketVersioning, Default: config.BucketVersioning}, - Response: &config.BucketVersioning, - }, - { - Prompt: &survey.Confirm{Message: QuestionEksAuditMfaDeleteS3Bucket, Default: config.BucketEnableMfaDelete}, - Response: &config.BucketEnableMfaDelete, - }, - { - Prompt: &survey.Confirm{Message: QuestionEksAuditBucketEncryption, - Default: config.BucketEnableEncryption}, - Response: &config.BucketEnableEncryption, - }, - }); err != nil { - return err - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionEksAuditBucketExistingKey}, - Checks: []*bool{&config.BucketEnableEncryption}, - Opts: []survey.AskOpt{}, - Response: &config.ExistingBucketKmsKey, - }); err != nil { - return err - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionEksAuditBucketSseAlgorithm}, - Checks: []*bool{&config.BucketEnableEncryption}, - Opts: []survey.AskOpt{}, - Response: &config.BucketSseAlgorithm, - }); err != nil { - return err - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionEksAuditBucketKeyArn}, - Checks: []*bool{&config.BucketEnableEncryption, &config.ExistingBucketKmsKey}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &config.BucketSseKeyArn, - }); err != nil { - return err - } - - newKmsKey := config.BucketEnableEncryption && !config.ExistingBucketKmsKey - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionEksAuditKmsKeyRotation}, - Checks: []*bool{&config.BucketEnableEncryption, &newKmsKey}, - Required: true, - Opts: []survey.AskOpt{}, - Response: &config.KmsKeyRotation, - }, - { - Prompt: &survey.Input{ - Message: QuestionEksAuditKmsKeyDeletionDays, - Default: strconv.Itoa(config.KmsKeyDeletionDays), - }, - Checks: []*bool{&config.BucketEnableEncryption, &newKmsKey}, - Opts: []survey.AskOpt{survey.WithValidator(validateResponseTypeInt)}, - Response: &config.KmsKeyDeletionDays, - }, - { - Prompt: &survey.Input{ - Message: QuestionEksAuditBucketLifecycle, - Default: strconv.Itoa(config.BucketLifecycleExpirationDays), - }, - Opts: []survey.AskOpt{survey.WithValidator(validateResponseTypeInt)}, - Response: &config.BucketLifecycleExpirationDays, - }, - }); err != nil { - return err - } - - return nil -} - -func promptAwsEksAuditExistingCrossAccountIamQuestions(input *aws_eks_audit. - GenerateAwsEksAuditTfConfigurationArgs) error { - // ensure struct is initialized - if input.ExistingCrossAccountIamRole == nil { - input.ExistingCrossAccountIamRole = &aws_eks_audit.ExistingCrossAccountIamRoleDetails{} - } - - err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{ - Message: QuestionEksAuditExistingCaIamArn, - Default: input.ExistingCrossAccountIamRole.Arn, - }, - Response: &input.ExistingCrossAccountIamRole.Arn, - Opts: []survey.AskOpt{survey.WithValidator(survey.Required), survey.WithValidator(validateAwsArnFormat)}, - }, - { - Prompt: &survey.Input{ - Message: QuestionEksAuditExistingCaIamExtID, - Default: input.ExistingCrossAccountIamRole.ExternalId, - }, - Response: &input.ExistingCrossAccountIamRole.ExternalId, - Opts: []survey.AskOpt{survey.WithValidator(survey.Required)}, - }}) - return err -} - -func promptAwsEksAuditFirehoseQuestions(input *aws_eks_audit. - GenerateAwsEksAuditTfConfigurationArgs) error { - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{ - Message: QuestionEksAuditExistingFhIamRole, - Default: input.ExistingFirehoseIam, - }, - Opts: []survey.AskOpt{}, - Response: &input.ExistingFirehoseIam, - Required: false, - }); err != nil { - return err - } - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{ - Message: QuestionEksAuditExistingFhIamArn, - Default: input.ExistingFirehoseIamRoleArn, - }, - Checks: []*bool{&input.ExistingFirehoseIam}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &input.ExistingFirehoseIamRoleArn, - Required: true, - }, - { - Prompt: &survey.Confirm{ - Message: QuestionEksAuditFhEncryption, - Default: input.FirehoseEncryptionEnabled, - }, - Opts: []survey.AskOpt{}, - Response: &input.FirehoseEncryptionEnabled, - Required: true, - }, - }); err != nil { - return err - } - - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionEksAuditFhEncryptionKeyArn, - Default: input.FirehoseEncryptionKeyArn, - }, - Checks: []*bool{&input.FirehoseEncryptionEnabled}, - Opts: []survey.AskOpt{}, - Response: &input.FirehoseEncryptionKeyArn, - }) - return err -} - -func promptAwsEksAuditExistingCloudwatchIamQuestions(input *aws_eks_audit. - GenerateAwsEksAuditTfConfigurationArgs) error { - - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionEksAuditExistingCwIamArn, - Default: input.ExistingCloudWatchIamRoleArn, - }, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsArnFormat)}, - Response: &input.ExistingCloudWatchIamRoleArn, - Required: true, - }) - return err -} - -func promptAwsEksAuditSnsQuestions(input *aws_eks_audit. - GenerateAwsEksAuditTfConfigurationArgs) error { - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{ - Message: QuestionEksAuditSnsEncryption, - Default: input.SnsTopicEncryptionEnabled, - }, - Opts: []survey.AskOpt{}, - Response: &input.SnsTopicEncryptionEnabled, - Required: true, - }); err != nil { - return err - } - - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionEksAuditSnsEncryptionKeyArn, - Default: input.SnsTopicEncryptionKeyArn, - }, - Checks: []*bool{&input.SnsTopicEncryptionEnabled}, - Opts: []survey.AskOpt{}, - Response: &input.SnsTopicEncryptionKeyArn, - }) - return err -} - -func promptAwsEksAuditCustomIntegrationName(input *aws_eks_audit. - GenerateAwsEksAuditTfConfigurationArgs) error { - - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{ - Message: QuestionEksAuditCustomIntegrationName, - Default: input.EksAuditIntegrationName, - }, - Opts: []survey.AskOpt{}, - Response: &input.EksAuditIntegrationName, - }) - return err -} - -func promptAwsEksAuditAdditionalClusterRegionQuestions( - config *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs, - extraState *AwsEksAuditGenerateCommandExtraState, -) error { - // For each region, collect which clusters to integrate with - askAgain := false - if cli.InteractiveMode() { - askAgain = true - } - - if config.ParsedRegionClusterMap == nil { - config.ParsedRegionClusterMap = make(map[string][]string) - } - - // If there are existing region clusters configured (i.e., from the CLI) display them and ask if they want to add more - if len(config.ParsedRegionClusterMap) > 0 { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{ - Message: fmt.Sprintf( - QuestionEksAuditRegionClusterCurrent, - config.ParsedRegionClusterMap, - ), - }, - Response: &askAgain}); err != nil { - return err - } - } - - // If we already have more than 1 region, don't bother asking the user if it's - // multi region and instead just set MultiRegion to true - if len(config.ParsedRegionClusterMap) > 1 { - extraState.MultiRegion = true - } - - // If only 1 region has been configured and the user wishes to add more clusters, - // ask if they want this be to multi region - if len(config.ParsedRegionClusterMap) <= 1 && askAgain && !extraState.MultiRegion { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{ - Message: QuestionEksAuditMultiRegion, - Default: extraState.MultiRegion, - }, - Checks: []*bool{&askAgain}, - Opts: []survey.AskOpt{}, - Required: true, - Response: &extraState.MultiRegion, - }); err != nil { - return err - } - } - - // For each region to add, collect the list of clusters to integrate with - for askAgain { - var awsEksAuditRegion string - var awsEksAuditRegionClusters string - regionClustersQuestions := []SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionEksAuditRegion}, - Opts: []survey.AskOpt{survey.WithValidator(validateAwsRegion)}, - Required: true, - Response: &awsEksAuditRegion, - }, - { - Prompt: &survey.Input{Message: QuestionEksAuditRegionClusters}, - Opts: []survey.AskOpt{}, - Required: true, - Response: &awsEksAuditRegionClusters, - }, - } - - if err := SurveyMultipleQuestionWithValidation(regionClustersQuestions); err != nil { - return err - } - - // append region clusters in case the user has input a region more than once - config.ParsedRegionClusterMap[awsEksAuditRegion] = append( - config.ParsedRegionClusterMap[awsEksAuditRegion], strings.Split(awsEksAuditRegionClusters, ",")...) - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionEksAuditAdditionalRegion}, - Checks: []*bool{&extraState.MultiRegion}, - Response: &askAgain}); err != nil { - return err - } - - if !extraState.MultiRegion { - askAgain = false - } - } - - return nil -} - -func promptCustomizeEksAuditOutputLocation(extraState *AwsEksAuditGenerateCommandExtraState) error { - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionEksAuditCustomizeOutputLocation, - Default: extraState.Output}, - Response: &extraState.Output, - Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, - Required: true, - }) - - return err -} - -func askAdvancedEksAuditOptions(config *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs, - extraState *AwsEksAuditGenerateCommandExtraState) error { - answer := "" - - // Prompt for options - for answer != AwsAdvancedOptDone { - // Construction of this slice is a bit strange at first look, but the reason for that is because we have - // to do string validation to know which option was selected due to how survey works; and doing it by index - // (also supported) is difficult when the options are dynamic (which they are) - // - // Only ask about more accounts if consolidated cloudtrail is set up (matching scenario's doc) - var options []string - - options = append(options, - EksAuditConfigureBucket, - EksAuditExistingCaIamRole, - EksAuditConfigureFh, - EksAuditExistingCwIamRole, - EksAuditConfigureSns, - EksAuditIntegrationNameOpt, - EksAuditAdvancedOptLocation, - EksAuditAdvancedOptDone) - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: "Which options would you like to configure?", - Options: options, - }, - Response: &answer, - }); err != nil { - return err - } - - // Based on response, prompt for actions - switch answer { - case EksAuditConfigureBucket: - if err := promptAwsEksAuditBucketQuestions(config); err != nil { - return err - } - case EksAuditExistingCaIamRole: - if err := promptAwsEksAuditExistingCrossAccountIamQuestions(config); err != nil { - return err - } - case EksAuditConfigureFh: - if err := promptAwsEksAuditFirehoseQuestions(config); err != nil { - return err - } - case EksAuditExistingCwIamRole: - if err := promptAwsEksAuditExistingCloudwatchIamQuestions(config); err != nil { - return err - } - case EksAuditConfigureSns: - if err := promptAwsEksAuditSnsQuestions(config); err != nil { - return err - } - case EksAuditIntegrationNameOpt: - if err := promptAwsEksAuditCustomIntegrationName(config); err != nil { - return err - } - case EksAuditAdvancedOptLocation: - if err := promptCustomizeEksAuditOutputLocation(extraState); err != nil { - return err - } - } - - // Re-prompt if not done - innerAskAgain := true - if answer == EksAuditAdvancedOptDone { - innerAskAgain = false - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Checks: []*bool{&innerAskAgain}, - Prompt: &survey.Confirm{Message: QuestionEksAuditAnotherAdvancedOpt, Default: false}, - Response: &innerAskAgain, - }); err != nil { - return err - } - - if !innerAskAgain { - answer = AwsAdvancedOptDone - } - } - - return nil -} - -func eksAuditConfigIsEmpty(g *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs) bool { - return g.AwsProfile == "" && - len(g.ParsedRegionClusterMap) == 0 && - g.ExistingCrossAccountIamRole == nil && - g.LaceworkProfile == "" -} - -func writeAwsEksAuditGenerationArgsCache(a *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs) { - if !eksAuditConfigIsEmpty(a) { - // If ExistingIamRole is partially set, don't write this to cache; the values won't work when loaded - if a.ExistingCrossAccountIamRole.IsPartial() { - a.ExistingCrossAccountIamRole = nil - } - cli.WriteAssetToCache(CachedAssetAwsEksAuditIacParams, time.Now().Add(time.Hour*1), a) - } -} - -// entry point for launching a survey to build out the required generation parameters -func promptAwsEksAuditGenerate( - config *aws_eks_audit.GenerateAwsEksAuditTfConfigurationArgs, - existingIam *aws_eks_audit.ExistingCrossAccountIamRoleDetails, - extraState *AwsEksAuditGenerateCommandExtraState, -) error { - - // Cache for later use if generation is abandoned and in interactive mode - if cli.InteractiveMode() { - defer writeAwsEksAuditGenerationArgsCache(config) - defer extraState.writeCache() - } - - // Set ExistingCrossAccountIamRole details, if provided as cli flags; otherwise don't initialize - if existingIam.Arn != "" || - existingIam.ExternalId != "" { - config.ExistingCrossAccountIamRole = existingIam - } - - // These are the core questions that should be asked. - if err := promptAwsEksAuditAdditionalClusterRegionQuestions(config, extraState); err != nil { - return err - } - - // Find out if the customer wants to specify more advanced features - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionEksAuditConfigureAdvanced, - Default: extraState.AskAdvanced}, - Response: &extraState.AskAdvanced, - }); err != nil { - return err - } - - // Keep prompting for advanced options until the say done - if extraState.AskAdvanced { - if err := askAdvancedEksAuditOptions(config, extraState); err != nil { - return err - } - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_azure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_azure.go deleted file mode 100644 index 34724f39d..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_azure.go +++ /dev/null @@ -1,895 +0,0 @@ -package cmd - -import ( - "strconv" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/imdario/mergo" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/lwgenerate/azure" - "github.com/pkg/errors" -) - -var ( - // Define question text here so they can be reused in testing - QuestionAzureEnableConfig = "Enable Azure configuration integration?" - QuestionAzureConfigName = "Specify custom configuration integration name: (optional)" - QuestionEnableActivityLog = "Enable Azure Activity Log Integration?" - QuestionActivityLogName = "Specify custom Activity Log integration name: (optional)" - QuestionEnableEntraIdActivityLog = "Enable Azure Entra ID Activity Log Integration?" - QuestionEntraIdActivityLogName = "Specify custom EntraID Activity Log integration name: (optional)" - QuestionAddAzureSubscriptionID = "Set Azure Subscription ID?" - QuestionAzureSubscriptionID = "Specify the Azure Subscription ID to be used to provision " + - "Lacework resources: (optional)" - - QuestionAzureAnotherAdvancedOpt = "Configure another advanced integration option" - QuestionAzureConfigAdvanced = "Configure advanced integration options?" - QuestionAzureCustomizeOutputLocation = "Provide the location for the output to be written:" - - // EntraID Activity Log - QuestionEventHubLocation = "Specify Azure region where the event hub for logging will reside" - QuestionEventHubPartitionCount = "Specify the number of partitions in the event hub for logging" - - // Active Directory - QuestionEnableAdIntegration = "Create Active Directory Integration?" - QuestionADApplicationPass = "Specify the password of an existing Active Directory application" - QuestionADApplicationId = "Specify the ID of an existing Active Directory application" - QuestionADServicePrincpleId = "Specify the Service Principle ID of an existing Active Directory application" - - // Storage Account - QuestionUseExistingStorageAccount = "Use an existing Storage Account?" - QuestionAzureRegion = "Specify the Azure region to be used by Storage Account logging" - QuestionStorageAccountName = "Specify existing Storage Account name" - QuestionStorageAccountResourceGroup = "Specify existing Storage Account Resource Group" - - QuestionStorageLocation = "Specify Azure region where Storage Account for logging resides " - - // Subscriptions - QuestionEnableAllSubscriptions = "Enable all subscriptions?" - QuestionSubscriptionIds = "Specify list of subscription ids to enable logging" - - // Management Group - QuestionEnableManagementGroup = "Enable Management Group level Integration?" - QuestionManagementGroupId = "Specify Management Group ID" - - // Select options - AzureAdvancedOptDone = "Done" - AdvancedAdIntegration = "Configure Lacework integration with an existing Active Directory (optional)" - AzureExistingStorageAcount = "Configure Storage Account (optional)" - AzureSubscriptions = "Configure Subscriptions (optional)" - AzureManagmentGroup = "Configure Management Group (optional)" - AzureStorageGroup = "Configure Storage Group (optional)" - AzureUserIntegrationNames = "Customize integration name(s)" - AzureAdvancedOptLocation = "Customize output location (optional)" - AzureRegionStorage = "Customize Azure region for Storage Account (optional)" - AzureEntraIdAdvancedOpt = "Configure Entra ID activity log integration advanced options" - - GenerateAzureCommandState = &azure.GenerateAzureTfConfigurationArgs{} - GenerateAzureCommandExtraState = &AzureGenerateCommandExtraState{} - CachedAzureAssetIacParams = "iac-azure-generate-params" - CachedAzureAssetExtraState = "iac-azure-extra-state" - - // List of valid Azure Storage locations - validAzureLocations = map[string]bool{ - "East US": true, - "East US 2": true, - "South Central US": true, - "West US 2": true, - "West US 3": true, - "Australia East": true, - "Southeast Asia": true, - "North Europe": true, - "Sweden Central": true, - "UK South": true, - "West Europe": true, - "Central US": true, - "North Central US": true, - "West US": true, - "South Africa North": true, - "Central India": true, - "East Asia": true, - "Japan East": true, - "Jio India West": true, - "Korea Central": true, - "Canada Central": true, - "France Central": true, - "Germany West Central": true, - "Norway East": true, - "Switzerland North": true, - "UAE North": true, - "Brazil South": true, - "Central US (Stage)": true, - "East US (Stage)": true, - "East US 2 (Stage)": true, - "North Central US (Stage)": true, - "South Central US (Stage)": true, - "West US (Stage)": true, - "West US 2 (Stage)": true, - "Asia": true, - "Asia Pacific": true, - "Australia": true, - "Brazil": true, - "Canada": true, - "Europe": true, - "France": true, - "Germany": true, - "Global": true, - "India": true, - "Japan": true, - "Korea": true, - "Norway": true, - "South Africa": true, - "Switzerland": true, - "United Arab Emirates": true, - "United Kingdom": true, - "United States": true, - "United States EUAP": true, - "East Asia (Stage)": true, - "Southeast Asia (Stage)": true, - "Central US EUAP": true, - "East US 2 EUAP": true, - "West Central US": true, - "South Africa West": true, - "Australia Central": true, - "Australia Central 2": true, - "Australia Southeast": true, - "Japan West": true, - "Jio India Central": true, - "Korea South": true, - "South India": true, - "West India": true, - "Canada East": true, - "France South": true, - "Germany North": true, - "Norway West": true, - "Switzerland West": true, - "UK West": true, - "UAE Central": true, - "Brazil Southeast": true, - } - - // Azure command used to generate TF code for azure - generateAzureTfCommand = &cobra.Command{ - Use: "azure", - Aliases: []string{"az"}, - Short: "Generate and/or execute Terraform code for Azure integration", - Long: `Use this command to generate Terraform code for deploying Lacework into new Azure environment. - -By default, this command will function interactively, prompting for the required information to setup -the new cloud account. In interactive mode, this command will: - -* Prompt for the required information to setup the integration -* Generate new Terraform code using the inputs -* Optionally, run the generated Terraform code: - * If Terraform is already installed, the version will be confirmed suitable for use - * If Terraform is not installed, or the version installed is not suitable, a new version will be - installed into a temporary location - * Once Terraform is detected or installed, Terraform plan will be executed - * The command will prompt with the outcome of the plan and allow to view more details or continue - with Terraform apply - * If confirmed, Terraform apply will be run, completing the setup of the cloud account -`, - RunE: func(cmd *cobra.Command, args []string) error { - // Generate TF Code - cli.StartProgress("Generating Azure Terraform Code...") - - if cli.Profile != "default" { - GenerateAzureCommandState.LaceworkProfile = cli.Profile - } - - // Setup modifiers for NewTerraform constructor - mods := []azure.AzureTerraformModifier{ - azure.WithLaceworkProfile(GenerateAzureCommandState.LaceworkProfile), - azure.WithSubscriptionID(GenerateAzureCommandState.SubscriptionID), - azure.WithAllSubscriptions(GenerateAzureCommandState.AllSubscriptions), - azure.WithManagementGroup(GenerateAzureCommandState.ManagementGroup), - azure.WithExistingStorageAccount(GenerateAzureCommandState.ExistingStorageAccount), - azure.WithStorageAccountName(GenerateAzureCommandState.StorageAccountName), - azure.WithStorageLocation(GenerateAzureCommandState.StorageLocation), - azure.WithActivityLogIntegrationName(GenerateAzureCommandState.ActivityLogIntegrationName), - azure.WithConfigIntegrationName(GenerateAzureCommandState.ConfigIntegrationName), - azure.WithEntraIdActivityLogIntegrationName(GenerateAzureCommandState.EntraIdIntegrationName), - azure.WithEventHubLocation(GenerateAzureCommandState.EventHubLocation), - azure.WithEventHubPartitionCount(GenerateAzureCommandState.EventHubPartitionCount), - } - - // Check if AD Creation is required, need to set values for current integration - if !GenerateAzureCommandState.CreateAdIntegration { - mods = append(mods, azure.WithAdApplicationId(GenerateAzureCommandState.AdApplicationId)) - mods = append(mods, azure.WithAdApplicationPassword(GenerateAzureCommandState.AdApplicationPassword)) - mods = append(mods, azure.WithAdServicePrincipalId(GenerateAzureCommandState.AdServicePrincipalId)) - } - - // Check subscriptions - if !GenerateAzureCommandState.AllSubscriptions { - if len(GenerateAzureCommandState.SubscriptionIds) > 0 { - mods = append(mods, azure.WithSubscriptionIds(GenerateAzureCommandState.SubscriptionIds)) - } - } - - // Check management groups - if GenerateAzureCommandState.ManagementGroup { - mods = append(mods, azure.WithManagementGroupId(GenerateAzureCommandState.ManagementGroupId)) - } - - // Check storage account - if GenerateAzureCommandState.ExistingStorageAccount { - mods = append(mods, - azure.WithStorageAccountResourceGroup(GenerateAzureCommandState.StorageAccountResourceGroup)) - } - - // Create new struct - data := azure.NewTerraform( - GenerateAzureCommandState.Config, - GenerateAzureCommandState.ActivityLog, - GenerateAzureCommandState.EntraIdActivityLog, - GenerateAzureCommandState.CreateAdIntegration, - mods...) - - // Generate HCL for azure deployment - hcl, err := data.Generate() - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "failed to generate terraform code") - } - - // Write-out generated code to location specified - dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "azure") - if err != nil { - return err - } - - // Prompt to execute, if the command line flag has not been set - if !GenerateAzureCommandExtraState.TerraformApply { - err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Default: GenerateAzureCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, - Response: &GenerateAzureCommandExtraState.TerraformApply, - }) - - if err != nil { - return errors.Wrap(err, "failed to prompt for terraform execution") - } - } - - locationDir, _ := determineOutputDirPath(dirname, "azure") - if GenerateAzureCommandExtraState.TerraformApply { - // Execution pre-run check - err := executionPreRunChecks(dirname, locationDir, "azure") - if err != nil { - return err - } - } - - // Output where code was generated - if !GenerateAzureCommandExtraState.TerraformApply { - cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) - } - - return nil - }, - PreRunE: func(cmd *cobra.Command, _ []string) error { - - // Validate output location is OK if supplied - dirname, err := cmd.Flags().GetString("output") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateOutputLocation(dirname); err != nil { - return err - } - - // Validate Storage Location - storageLocation, err := cmd.Flags().GetString("location") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateAzureLocation(storageLocation); storageLocation != "" && err != nil { - return err - } - - // Load any cached inputs if interactive - if cli.InteractiveMode() { - cachedOptions := &azure.GenerateAzureTfConfigurationArgs{} - iacParamsExpired := cli.ReadCachedAsset(CachedAzureAssetIacParams, &cachedOptions) - if iacParamsExpired { - cli.Log.Debug("loaded previously set values for Azure iac generation") - } - - extraState := &AzureGenerateCommandExtraState{} - extraStateParamsExpired := cli.ReadCachedAsset(CachedAzureAssetExtraState, &extraState) - if extraStateParamsExpired { - cli.Log.Debug("loaded previously set values for Azure iac generation (extra state)") - } - - // Determine if previously cached options exists; prompt user if they'd like to continue - answer := false - if !iacParamsExpired || !extraStateParamsExpired { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, - Response: &answer, - }); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - - // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs - // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get - // re-prompted. - if answer { - // Merge cached inputs to current options (current options win) - if err := mergo.Merge(GenerateAzureCommandState, cachedOptions); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - if err := mergo.Merge(GenerateAzureCommandExtraState, extraState); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - } - - // Collect and/or confirm parameters - err = promptAzureGenerate(GenerateAzureCommandState, GenerateAzureCommandExtraState) - if err != nil { - return errors.Wrap(err, "collecting/confirming parameters") - } - - return nil - }, - } -) - -type AzureGenerateCommandExtraState struct { - AskAdvanced bool - Output string - TerraformApply bool -} - -func (a *AzureGenerateCommandExtraState) isEmpty() bool { - return a.Output == "" && !a.TerraformApply -} - -// Flush current state of the struct to disk, provided it's not empty -func (a *AzureGenerateCommandExtraState) writeCache() { - if !a.isEmpty() { - cli.WriteAssetToCache(CachedAzureAssetExtraState, time.Now().Add(time.Hour*1), a) - } -} - -func validateAzureLocation(location string) error { - if !validAzureLocations[location] { - return errors.New("invalid Azure region prvovided") - } - return nil -} - -func initGenerateAzureTfCommandFlags() { - // Azure sub-command flags - generateAzureTfCommand.PersistentFlags().BoolVar( - &GenerateAzureCommandState.ActivityLog, - "activity_log", - false, - "enable activity log integration") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.ActivityLogIntegrationName, - "activity_log_integration_name", - "", - "specify a custom activity log integration name") - - generateAzureTfCommand.PersistentFlags().BoolVar( - &GenerateAzureCommandState.EntraIdActivityLog, - "entra_id_activity_log", - false, - "enable Entra ID activity log integration") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.EntraIdIntegrationName, - "entra_id_activity_log_integration_name", - "", - "specify a custom Entra ID activity log integration name") - - generateAzureTfCommand.PersistentFlags().BoolVar( - &GenerateAzureCommandState.Config, - "configuration", - false, - "enable configuration integration") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.ConfigIntegrationName, - "configuration_name", - "", - "specify a custom configuration integration name") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.SubscriptionID, - "subscription_id", - "", - "specify the Azure Subscription ID to be used to provision Lacework resources") - - generateAzureTfCommand.PersistentFlags().BoolVar( - &GenerateAzureCommandState.CreateAdIntegration, - "ad_create", - true, - "create new active directory integration") - - generateAzureTfCommand.PersistentFlags().BoolVar( - &GenerateAzureCommandState.ManagementGroup, - "management_group", - false, - "management group level integration") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.ManagementGroupId, - "management_group_id", - "", - "specify management group id. Required if mgmt_group provided") - - generateAzureTfCommand.PersistentFlags().BoolVar( - &GenerateAzureCommandState.ExistingStorageAccount, - "existing_storage", - false, - "use existing storage account") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.EventHubLocation, - "event_hub_location", - "", - "specify the location where the Event Hub for logging will reside") - - generateAzureTfCommand.PersistentFlags().IntVar( - &GenerateAzureCommandState.EventHubPartitionCount, - "event_hub_partition_count", - 1, - "specify the number of partitions for the Event Hub") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.StorageAccountName, - "storage_account_name", - "", - "specify storage account name") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.StorageAccountResourceGroup, - "storage_resource_group", - "", - "specify storage resource group") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.StorageLocation, - "location", - "", - "specify azure region where storage account logging resides") - - generateAzureTfCommand.PersistentFlags().BoolVar( - &GenerateAzureCommandState.AllSubscriptions, - "all_subscriptions", - false, - "grant read access to ALL subscriptions within Tenant (overrides `subscription ids`)") - - generateAzureTfCommand.PersistentFlags().StringSliceVar( - &GenerateAzureCommandState.SubscriptionIds, - "subscription_ids", - []string{}, - `list of subscriptions to grant read access; format is id1,id2,id3`) - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.AdApplicationPassword, - "ad_pass", - "", - "existing active directory application password") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.AdApplicationId, - "ad_id", - "", - "existing active directory application id") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandState.AdServicePrincipalId, - "ad_pid", - "", - "existing active directory application service principle id") - - generateAzureTfCommand.PersistentFlags().BoolVar( - &GenerateAzureCommandExtraState.TerraformApply, - "terraform-apply", - false, - "run terraform apply for the generated hcl") - - _ = generateAzureTfCommand.PersistentFlags().MarkHidden("terraform-apply") - - generateAzureTfCommand.PersistentFlags().BoolVar( - &GenerateAzureCommandExtraState.TerraformApply, - "apply", - false, - "run terraform apply for the generated hcl") - - generateAzureTfCommand.PersistentFlags().StringVar( - &GenerateAzureCommandExtraState.Output, - "output", - "", - "location to write generated content (default is ~/lacework/azure)", - ) -} - -func promptAzureIntegrationNameQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionAzureConfigName, Default: config.ConfigIntegrationName}, - Checks: []*bool{&config.Config}, - Response: &config.ConfigIntegrationName, - }, - { - Prompt: &survey.Input{Message: QuestionActivityLogName, Default: config.ActivityLogIntegrationName}, - Checks: []*bool{&config.ActivityLog}, - Response: &config.ActivityLogIntegrationName, - }, - { - Prompt: &survey.Input{Message: QuestionEntraIdActivityLogName, Default: config.EntraIdIntegrationName}, - Checks: []*bool{&config.EntraIdActivityLog}, - Response: &config.EntraIdIntegrationName, - }, - }); err != nil { - return err - } - return nil -} - -func promptAzureStorageAccountQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionUseExistingStorageAccount, Default: config.ExistingStorageAccount}, - Response: &config.ExistingStorageAccount, - }, - { - Prompt: &survey.Input{Message: QuestionStorageAccountName, Default: config.StorageAccountName}, - Required: true, - Response: &config.StorageAccountName, - }, - { - Prompt: &survey.Input{ - Message: QuestionStorageAccountResourceGroup, - Default: config.StorageAccountResourceGroup, - }, - Checks: []*bool{&config.ExistingStorageAccount}, - Required: true, - Response: &config.StorageAccountResourceGroup, - }, - }); err != nil { - return err - } - - return nil -} - -func promptAzureEntraIdActivityLogQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionEventHubLocation, Default: config.EventHubLocation}, - Required: true, - Response: &config.EventHubLocation, - }, - }); err != nil { - return err - } - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionEventHubPartitionCount, - Default: strconv.Itoa(config.EventHubPartitionCount)}, - Response: &config.EventHubPartitionCount, - }, - }); err != nil { - return err - } - - return nil -} - -func promptAzureSubscriptionQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionEnableAllSubscriptions, Default: config.AllSubscriptions}, - Response: &config.AllSubscriptions, - }, - }); err != nil { - return err - } - var idList string - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionSubscriptionIds, Default: strings.Join(config.SubscriptionIds, ",")}, - Checks: []*bool{allSubscriptionsDisabled(config)}, - Required: true, - Response: &idList, - }, - }); err != nil { - return err - } - config.SubscriptionIds = strings.Split(strings.ReplaceAll(idList, " ", ""), ",") - - return nil -} - -func promptAzureManagementGroupQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionEnableManagementGroup, Default: config.ManagementGroup}, - Response: &config.ManagementGroup, - }, - { - Prompt: &survey.Input{Message: QuestionManagementGroupId, Default: config.ManagementGroupId}, - Checks: []*bool{&config.ManagementGroup}, - Required: true, - Response: &config.ManagementGroupId, - }, - }); err != nil { - return err - } - return nil -} - -func promptAzureAdIntegrationQuestions(config *azure.GenerateAzureTfConfigurationArgs) error { - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionADApplicationPass, Default: config.AdApplicationPassword}, - Required: true, - Response: &config.AdApplicationPassword, - }, - { - Prompt: &survey.Input{Message: QuestionADApplicationId, Default: config.AdApplicationId}, - Required: true, - Response: &config.AdApplicationId, - }, - { - Prompt: &survey.Input{Message: QuestionADServicePrincpleId, Default: config.AdServicePrincipalId}, - Required: true, - Response: &config.AdServicePrincipalId, - }, - }); err != nil { - return err - } - return nil -} - -func promptCustomizeAzureOutputLocation(extraState *AzureGenerateCommandExtraState) error { - - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionAzureCustomizeOutputLocation, Default: extraState.Output}, - Response: &extraState.Output, - }, - }); err != nil { - return err - } - - return nil -} - -func promptCustomizeAzureStorageLoggingRegion(config *azure.GenerateAzureTfConfigurationArgs) error { - var region string - if err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionStorageLocation, Default: config.StorageLocation}, - Response: ®ion, - }, - }); err != nil { - return err - } - if err := validateAzureLocation(region); err != nil { - return err - } - config.StorageLocation = region - return nil -} - -func askAzureSubscriptionID(config *azure.GenerateAzureTfConfigurationArgs) error { - var addSubscription bool - - // if subscription has been set by --subscription_id flag do not prompt - if config.SubscriptionID != "" { - return nil - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionAddAzureSubscriptionID, Default: false}, - Response: &addSubscription, - }); err != nil { - return err - } - - if addSubscription { - if err := survey.AskOne(&survey.Input{ - Message: QuestionAzureSubscriptionID, - }, &config.SubscriptionID); err != nil { - return err - } - } - - return nil -} - -func askAdvancedAzureOptions( - config *azure.GenerateAzureTfConfigurationArgs, extraState *AzureGenerateCommandExtraState, -) error { - answer := "" - - // Prompt for options - for answer != AzureAdvancedOptDone { - - // Set the initial options - options := []string{AzureUserIntegrationNames, AzureSubscriptions} - // Only ask about Active Directory information if one was requested to be created - if !config.CreateAdIntegration { - options = append(options, AdvancedAdIntegration) - } - - // Only show Region Storage options in the case of Activity Log integration - if config.ActivityLog { - options = append(options, AzureRegionStorage) - options = append(options, AzureExistingStorageAcount) - } - - // Only show management options in the case of Config integration - if config.Config { - options = append(options, AzureManagmentGroup) - } - - // Only show Entra ID options in the case of Entra ID integration - if config.EntraIdActivityLog { - options = append(options, AzureEntraIdAdvancedOpt) - } - - options = append(options, AzureAdvancedOptLocation, AzureAdvancedOptDone) - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: "Which options would you like to enable?", - Options: options, - }, - Response: &answer, - }); err != nil { - return err - } - - // Based on response, prompt for actions - switch answer { - case AzureUserIntegrationNames: - if err := promptAzureIntegrationNameQuestions(config); err != nil { - return err - } - case AzureExistingStorageAcount: - if err := promptAzureStorageAccountQuestions(config); err != nil { - return err - } - case AzureEntraIdAdvancedOpt: - if err := promptAzureEntraIdActivityLogQuestions(config); err != nil { - return err - } - case AzureSubscriptions: - if err := promptAzureSubscriptionQuestions(config); err != nil { - return err - } - case AzureManagmentGroup: - if err := promptAzureManagementGroupQuestions(config); err != nil { - return err - } - case AdvancedAdIntegration: - if err := promptAzureAdIntegrationQuestions(config); err != nil { - return err - } - case AzureRegionStorage: - if err := promptCustomizeAzureStorageLoggingRegion(config); err != nil { - return err - } - case AzureAdvancedOptLocation: - if err := promptCustomizeAzureOutputLocation(extraState); err != nil { - return err - } - return nil - } - - // Re-prompt if not done - innerAskAgain := true - if answer == AzureAdvancedOptDone { - innerAskAgain = false - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Checks: []*bool{&innerAskAgain}, - Prompt: &survey.Confirm{Message: QuestionAzureAnotherAdvancedOpt, Default: false}, - Response: &innerAskAgain, - }); err != nil { - return err - } - - if !innerAskAgain { - answer = AzureAdvancedOptDone - } - } - - return nil -} - -func azureConfigIsEmpty(config *azure.GenerateAzureTfConfigurationArgs) bool { - return !config.Config && !config.ActivityLog && config.LaceworkProfile == "" -} - -func allSubscriptionsDisabled(config *azure.GenerateAzureTfConfigurationArgs) *bool { - allSubscriptionsDisabled := !config.AllSubscriptions - return &allSubscriptionsDisabled -} - -func writeAzureGenerationArgsCache(config *azure.GenerateAzureTfConfigurationArgs) { - if !azureConfigIsEmpty(config) { - cli.WriteAssetToCache(CachedAzureAssetIacParams, time.Now().Add(time.Hour*1), config) - } -} - -// entry point for launching a survey to build out the required Azure generation parameters -func promptAzureGenerate( - config *azure.GenerateAzureTfConfigurationArgs, extraState *AzureGenerateCommandExtraState, -) error { - - // Cache for later use if generation is abandoned and in interactive mode - if cli.InteractiveMode() { - defer writeAzureGenerationArgsCache(config) - defer extraState.writeCache() - } - // These are the core questions that should be asked. - if err := SurveyMultipleQuestionWithValidation( - []SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionAzureEnableConfig, Default: config.Config}, - Response: &config.Config, - }, - { - Prompt: &survey.Confirm{Message: QuestionEnableActivityLog, Default: config.ActivityLog}, - Response: &config.ActivityLog, - }, - { - Prompt: &survey.Confirm{Message: QuestionEnableAdIntegration, Default: config.CreateAdIntegration}, - Response: &config.CreateAdIntegration, - }, - { - Prompt: &survey.Confirm{Message: QuestionEnableEntraIdActivityLog, Default: config.EntraIdActivityLog}, - Response: &config.EntraIdActivityLog, - }, - }); err != nil { - return err - } - - // Validate one of config or activity log was enabled; otherwise error out - if !config.Config && !config.ActivityLog && !config.EntraIdActivityLog { - return errors.New("must enable activity log or config") - } - - if err := askAzureSubscriptionID(config); err != nil { - return err - } - - // Find out if the customer wants to specify more advanced features - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionAzureConfigAdvanced, Default: extraState.AskAdvanced}, - Response: &extraState.AskAdvanced, - }); err != nil { - return err - } - - // Keep prompting for advanced options until the say done - if extraState.AskAdvanced { - if err := askAdvancedAzureOptions(config, extraState); err != nil { - return err - } - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_cloud_account.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_cloud_account.go deleted file mode 100644 index 999ff9b23..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_cloud_account.go +++ /dev/null @@ -1,31 +0,0 @@ -package cmd - -import "github.com/spf13/cobra" - -var ( - generateCloudAccountCommand = &cobra.Command{ - Use: "cloud-account", - Aliases: []string{"cloud", "ca"}, - Short: "Generate cloud integration IaC", - Long: `Generate cloud-account IaC to deploy Lacework into a cloud environment. - -This command creates Infrastructure as Code (IaC) in the form of Terraform HCL, with the option of running -Terraform and deploying Lacework into AWS, Azure, GCP or OCI. -`, - } -) - -func init() { - generateTfCommand.AddCommand(generateCloudAccountCommand) - - // Uncomment when `cloud-account iac-generate` removed - // initGenerateAwsTfCommandFlags() - // initGenerateGcpTfCommandFlags() - // initGenerateAzureTfCommandFlags() - initGenerateOciTfCommandFlags() - - generateCloudAccountCommand.AddCommand(generateAwsTfCommand) - generateCloudAccountCommand.AddCommand(generateGcpTfCommand) - generateCloudAccountCommand.AddCommand(generateAzureTfCommand) - generateCloudAccountCommand.AddCommand(generateOciTfCommand) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_execute.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_execute.go deleted file mode 100644 index 594eae302..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_execute.go +++ /dev/null @@ -1,477 +0,0 @@ -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/Masterminds/semver" - "github.com/abiosoft/colima/util/terminal" - "github.com/hashicorp/go-version" - "github.com/hashicorp/hc-install/product" - "github.com/hashicorp/hc-install/releases" - "github.com/hashicorp/terraform-exec/tfexec" - tfjson "github.com/hashicorp/terraform-json" - "github.com/pkg/errors" -) - -var ( - requiredTerraformVersion = ">= 0.15.1" - installTerraformVersion = "1.0.11" -) - -type terraformVersion struct { - Version string `json:"terraform_version"` -} - -// helper function to create new *tfexec.Terraform object and return useful error if not found -func newTf(workingDir string, execPath string) (*tfexec.Terraform, error) { - // Create new tf object - tf, err := tfexec.NewTerraform(workingDir, execPath) - if err != nil { - return nil, errors.Wrap(err, "could not locate terraform binary") - } - - return tf, nil -} - -// LocateOrInstallTerraform Determine if terraform is installed, if that version is new enough, -// and if not install a new ephemeral binary of the correct version into tmp location -// -// forceInstall: if set always install ephemeral binary -func LocateOrInstallTerraform(forceInstall bool, workingDir string) (*tfexec.Terraform, error) { - // find existing binary if not force install - execPath, _ := exec.LookPath("terraform") - if execPath != "" { - cli.Log.Debugf("existing terraform path %s", execPath) - } - - existingVersionOk := false - if !forceInstall && execPath != "" { - // Test if it's an OK version - requiredVersion := requiredTerraformVersion - constraint, _ := semver.NewConstraint(requiredVersion) - - // Extract tf version && check for unsupportedExistingVersion - out, err := exec.Command("terraform", "--version", "--json").Output() - if err != nil { - return nil, - errors.Wrap( - err, - fmt.Sprintf("failed to collect version from existing terraform install (%s)", execPath)) - } - - // If this version supports checking the version via --version --json, check if we can use it - var data terraformVersion - unsupportedVersionCheck := false - err = json.Unmarshal(out, &data) - if err != nil { - // If this version does not support checking version via --version --json, report and install new - cli.OutputHuman( - "Existing Terraform version cannot be used, version doesn't meet requirement %s, "+ - "installing short lived version\n", - requiredVersion) - unsupportedVersionCheck = true - } - cli.Log.Debugf("existing terraform version %s", data.Version) - - // Parse into new semver - if !unsupportedVersionCheck { - tfVersion, err := semver.NewVersion(data.Version) - if err != nil { - return nil, - errors.Wrap( - err, - fmt.Sprintf("version from existing terraform install is invalid (%s)", data.Version)) - } - - // Test if it matches - existingVersionOk, _ = constraint.Validate(tfVersion) - if !existingVersionOk { - cli.OutputHuman( - "Existing Terraform version cannot be used, version %s doesn't meet requirement %s, "+ - "installing short lived version\n", - data.Version, - requiredVersion) - } - cli.Log.Debug("using existing terraform install") - } - } - - if !existingVersionOk { - // If forceInstall was true or the existing version couldn't be used, install it - installer := &releases.ExactVersion{ - Product: product.Terraform, - Version: version.Must(version.NewVersion(installTerraformVersion)), - } - - cli.StartProgress("Installing Terraform") - installPath, err := installer.Install(context.Background()) - if err != nil { - return nil, errors.Wrap(err, "error installing terraform") - } - cli.StopProgress() - execPath = installPath - } - - // Return *tfexec.Terraform object - return newTf(workingDir, execPath) -} - -// used to create suitable response messages when showing actions tf will take as a result of a plan execution -func createOrDestroy(create bool, - destroy bool, - update bool, - read bool, - noop bool, - replace bool, - createBeforeDestroy bool, - destroyBeforeCreate bool, -) string { - switch { - case create: - return "created" - case destroy: - return "destroyed" - case update: - return "update" - case read: - return "read" - case noop: - return "(noop)" - case replace: - return "replaced" - case createBeforeDestroy: - return "created before destroy" - case destroyBeforeCreate: - return "destroyed before create" - default: - return "unchanged" - } -} - -type TfPlanChangesSummary struct { - plan *tfjson.Plan - create int - deleted int - update int - replace int -} - -// buildHumanReadablePlannedActions creates a summarized listing of expected changes from Terraform -func buildHumanReadablePlannedActions(workingDir string, execPath string, data []*tfjson.ResourceChange) string { - outputString := strings.Builder{} - outputString.WriteString("Resource details: \n") - - for _, c := range data { - outputString.WriteString(fmt.Sprintf(" %s.%s will be %s\n", c.Type, c.Name, - createOrDestroy( - c.Change.Actions.Create(), - c.Change.Actions.Delete(), - c.Change.Actions.Update(), - c.Change.Actions.Read(), - c.Change.Actions.NoOp(), - c.Change.Actions.Replace(), - c.Change.Actions.CreateBeforeDestroy(), - c.Change.Actions.DestroyBeforeCreate(), - ), - ), - ) - } - outputString.WriteString("\n") - outputString.WriteString(fmt.Sprintf( - "More details can be viewed by running:\n\n cd %s\n %s show tfplan.json\n", - workingDir, execPath, - )) - outputString.WriteString("\n") - return outputString.String() -} - -// DisplayTerraformPlanChanges used to display the results of a plan -// -// returns true if apply should run, false to exit -func DisplayTerraformPlanChanges(tf *tfexec.Terraform, data TfPlanChangesSummary) (bool, error) { - // Prompt for next steps - prompt := true - previewShown := false - var answer int - - // Displaying resources - for prompt { - id, err := promptForTerraformNextSteps(&previewShown, data) - if err != nil { - return false, errors.Wrap(err, "failed to run terraform") - } - - switch { - case id == 1 && !previewShown: - cli.OutputHuman(buildHumanReadablePlannedActions(tf.WorkingDir(), tf.ExecPath(), data.plan.ResourceChanges)) - default: - answer = id - prompt = false - } - - if id == 1 && !previewShown { - previewShown = true - } - } - - // Run apply - if answer == 0 { - return true, nil - } - - // Quit - return false, nil -} - -func parseTfPlanOutput(plan *tfjson.Plan) *TfPlanChangesSummary { - // Build output of changes - resourceCreate := 0 - resourceDelete := 0 - resourceUpdate := 0 - resourceReplace := 0 - - for _, c := range plan.ResourceChanges { - switch { - case c.Change.Actions.Create(): - resourceCreate++ - case c.Change.Actions.Delete(): - resourceDelete++ - case c.Change.Actions.Update(): - resourceUpdate++ - case c.Change.Actions.Replace(): - resourceReplace++ - } - } - - return &TfPlanChangesSummary{ - plan: plan, - create: resourceCreate, - deleted: resourceDelete, - update: resourceDelete, - replace: resourceReplace, - } -} - -func processTfPlanChangesSummary(tf *tfexec.Terraform) (*TfPlanChangesSummary, error) { - // Extract changes from tf plan - cli.StartProgress("Getting terraform plan details") - plan, err := tf.ShowPlanFile(context.Background(), "tfplan.json") - if err != nil { - return nil, errors.Wrap(err, "failed to inspect terraform plan") - } - cli.StopProgress() - - return parseTfPlanOutput(plan), nil -} - -func TerraformInit(tf *tfexec.Terraform) error { - cli.StartProgress("Running terraform init") - err := tf.Init(context.Background(), tfexec.Upgrade(true)) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "failed to execute terraform init") - } - - return nil -} - -// TerraformExecPlan Run terraform plan using the workingDir from *tfexec.Terraform -// -// - Run plan -// - Get plan file details (returned) -func TerraformExecPlan(tf *tfexec.Terraform) (*TfPlanChangesSummary, error) { - // Plan - cli.StartProgress("Running terraform plan") - _, err := tf.Plan(context.Background(), tfexec.Out("tfplan.json")) - cli.StopProgress() - if err != nil { - return nil, err - } - - // Gather changes from plan - return processTfPlanChangesSummary(tf) -} - -// TerraformExecApply Run terraform apply using the workingDir from *tfexec.Terraform -// -// - Run plan -// - Get plan file details (returned) -func TerraformExecApply(tf *tfexec.Terraform) error { - cli.StartProgress("Running terraform apply") - err := tf.Apply(context.Background()) - cli.StopProgress() - if err != nil { - return err - } - - return nil -} - -// Simple helper to prompt for next steps after TF plan -func promptForTerraformNextSteps(previewShown *bool, data TfPlanChangesSummary) (int, error) { - options := []string{ - "Continue with Terraform Apply", - } - - // Omit option to show details if we already have - if !*previewShown { - options = append(options, "Show details") - } - options = append(options, "Quit") - - var answer int - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: fmt.Sprintf( - "Terraform will create %d resources, delete %d resources, update %d resources, and replace %d resources.", - data.create, - data.deleted, - data.update, - data.replace), - Options: options, - }, - Response: &answer, - }) - - return answer, err -} - -// this helper function is called when terraform flow has been completely executed through apply -func provideGuidanceAfterSuccess(workingDir string, laceworkProfile string) string { - out := new(strings.Builder) - fmt.Fprintf(out, "Lacework integration was successful! Terraform code saved in %s\n\n", workingDir) - fmt.Fprintln(out, "To view integration status:") - - laceworkCmd := " lacework cloud-account list\n\n" - if laceworkProfile != "" { - laceworkCmd = fmt.Sprintf(" lacework -p %s cloud-account list\n\n", laceworkProfile) - } - fmt.Fprint(out, laceworkCmd) - - return out.String() -} - -func provideGuidanceAfterFailure(err error, workingDir string, binaryLocation string) string { - out := new(strings.Builder) - fmt.Fprintf(out, "\n\n%s\n\n", err.Error()) - fmt.Fprintln(out, strings.Repeat("-", 80)) - fmt.Fprint(out, "Terraform encountered an error (see above)\n\n") - fmt.Fprintf(out, "The Terraform code, state, and plan output have been saved in %s.\n\n", workingDir) - fmt.Fprintln(out, "Once the issues have been resolved, the integration can be continued using the following commands:") - fmt.Fprintf(out, " cd %s\n", workingDir) - fmt.Fprintf(out, " %s apply\n\n", binaryLocation) - fmt.Fprintln(out, "Should you simply want to clean up the failed deployment, use the following commands:") - fmt.Fprintf(out, " cd %s\n", workingDir) - fmt.Fprintf(out, " %s destroy\n\n", binaryLocation) - - return out.String() -} - -// this helper function is called when the entire generation/apply flow is not completed; it provides -// guidance on how to proceed from the last point of execution -func provideGuidanceAfterExit(initRun bool, planRun bool, workingDir string, binaryLocation string) string { - planNote := " and plan output" - if !planRun { - planNote = "" - } - - out := new(strings.Builder) - fmt.Fprintf(out, "Terraform code%s saved in %s\n\n", planNote, workingDir) - fmt.Fprintln(out, "The generated code can be executed at any point in the future using the following commands:") - fmt.Fprintf(out, " cd %s\n", workingDir) - - if !initRun { - fmt.Fprintf(out, " %s init\n", binaryLocation) - } - - fmt.Fprintf(out, " %s plan\n", binaryLocation) - fmt.Fprintf(out, " %s apply\n\n", binaryLocation) - return out.String() -} - -// Execute a terraform plan & execute -func TerraformPlanAndExecute(workingDir string) error { - // Ensure Terraform is installed - tf, err := LocateOrInstallTerraform(false, workingDir) - if err != nil { - return err - } - - vw := terminal.NewVerboseWriter(10) - tf.SetStdout(vw) - tf.SetStderr(vw) - - // Initialize tf project - if err := TerraformInit(tf); err != nil { - return err - } - - // Write plan - changes, err := TerraformExecPlan(tf) - if err != nil { - return err - } - - vw.Close() - - // Display changes and determine if apply should proceed - proceed, err := DisplayTerraformPlanChanges(tf, *changes) - if err != nil { - return err - } - - // If not proceed; display guidance on how to continue outside of this session - if !proceed { - cli.OutputHuman(provideGuidanceAfterExit(true, true, tf.WorkingDir(), tf.ExecPath())) - return nil - } - - vw = terminal.NewVerboseWriter(10) - tf.SetStdout(vw) - tf.SetStderr(vw) - - // Apply plan - if err := TerraformExecApply(tf); err != nil { - return errors.New(provideGuidanceAfterFailure(err, tf.WorkingDir(), tf.ExecPath())) - } - vw.Close() - - cli.OutputHuman(provideGuidanceAfterSuccess(tf.WorkingDir(), GenerateAwsCommandState.LaceworkProfile)) - return nil -} - -func TerraformExecutePreRunCheck(outputLocation string, cloud string) (bool, error) { - // If noninteractive, continue - if !cli.InteractiveMode() { - return true, nil - } - - dirname, err := determineOutputDirPath(outputLocation, cloud) - if err != nil { - return false, err - } - stateFile := filepath.FromSlash(fmt.Sprintf("%s/terraform.tfstate", dirname)) - - // If the file doesn't exist, carry on - if _, err := os.Stat(stateFile); os.IsNotExist(err) { - return true, nil - } - - // If it does exist; confirm overwrite - answer := false - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: fmt.Sprintf("Terraform state file %s already exists, continue?", stateFile)}, - Response: &answer, - }); err != nil { - return false, err - } - - return answer, nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gcp.go deleted file mode 100644 index e2fecb48a..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gcp.go +++ /dev/null @@ -1,802 +0,0 @@ -package cmd - -import ( - "regexp" - "strings" - "time" - - "github.com/imdario/mergo" - "github.com/spf13/cobra" - - "github.com/AlecAivazis/survey/v2" - "github.com/pkg/errors" - - "github.com/lacework/go-sdk/lwgenerate/gcp" -) - -var ( - // Define question text here to be reused in testing - QuestionGcpEnableAgentless = "Enable Agentless integration?" - QuestionGcpEnableConfiguration = "Enable Configuration integration?" - QuestionGcpEnableAuditLog = "Enable Audit Log integration?" - QuestionGcpOrganizationIntegration = "Organization integration?" - QuestionGcpOrganizationID = "Specify the GCP organization ID:" - QuestionGcpProjectID = "Specify the project ID to be used to provision Lacework resources:" - QuestionGcpServiceAccountCredsPath = "Specify service account credentials JSON path: (optional)" - - QuestionGcpConfigureAdvanced = "Configure advanced integration options?" - GcpAdvancedOptExistingServiceAccount = "Configure & use existing service account" - QuestionExistingServiceAccountName = "Specify an existing service account name:" - QuestionExistingServiceAccountPrivateKey = "Specify an existing service account private key (base64 encoded):" - - GcpAdvancedOptAgentless = "Configure additional Agentless options" - QuestionGcpProjectFilterList = "Specify a comma separated list of Google Cloud projects that " + - "you want to monitor: (optional)" - QuestionGcpRegions = "Specify a comma separated list of regions to deploy Agentless:" - - GcpAdvancedOptAuditLog = "Configure additional Audit Log options" - QuestionGcpUseExistingSink = "Use an existing sink?" - QuestionGcpExistingSinkName = "Specify the existing sink name" - - GcpAdvancedOptIntegrationName = "Customize integration name(s)" - QuestionGcpConfigurationIntegrationName = "Specify a custom configuration integration name: (optional)" - QuestionGcpAuditLogIntegrationName = "Specify a custom Audit Log integration name: (optional)" - - QuestionGcpAnotherAdvancedOpt = "Configure another advanced integration option" - GcpAdvancedOptLocation = "Customize output location" - GcpAdvancedOptProjects = "Configure multiple projects" - QuestionGcpCustomizeOutputLocation = "Provide the location for the output to be written:" - QuestionGcpCustomizeProjects = "Provide comma separated list of project ID" - QuestionGcpCustomFilter = "Specify a custom Audit Log filter which supersedes all other filter options" - GcpAdvancedOptDone = "Done" - - // GcpRegionRegex regex used for validating region input - GcpRegionRegex = `(asia|australia|europe|northamerica|southamerica|us)-(central|(north|south)?(east|west)?)\d` - - GenerateGcpCommandState = &gcp.GenerateGcpTfConfigurationArgs{} - GenerateGcpExistingServiceAccountDetails = &gcp.ExistingServiceAccountDetails{} - GenerateGcpCommandExtraState = &GcpGenerateCommandExtraState{} - CachedGcpAssetIacParams = "iac-gcp-generate-params" - CachedAssetGcpExtraState = "iac-gcp-extra-state" - - InvalidProjectIDMessage = "invalid GCP project ID. " + - "It must be 6 to 30 lowercase ASCII letters, digits, or hyphens. " + - "It must start with a letter. Trailing hyphens are prohibited. Example: tokyo-rain-123" - - // gcp command is used to generate TF code for gcp - generateGcpTfCommand = &cobra.Command{ - Use: "gcp", - Short: "Generate and/or execute Terraform code for GCP integration", - Long: `Use this command to generate Terraform code for deploying Lacework into an GCP environment. - -By default, this command interactively prompts for the required information to setup the new cloud account. -In interactive mode, this command will: - -* Prompt for the required information to setup the integration -* Generate new Terraform code using the inputs -* Optionally, run the generated Terraform code: - * If Terraform is already installed, the version is verified as compatible for use - * If Terraform is not installed, or the version installed is not compatible, a new version will be - installed into a temporary location - * Once Terraform is detected or installed, Terraform plan will be executed - * The command will prompt with the outcome of the plan and allow to view more details or continue with - Terraform apply - * If confirmed, Terraform apply will be run, completing the setup of the cloud account - -This command can also be run in noninteractive mode. -See help output for more details on the parameter value(s) required for Terraform code generation. -`, - RunE: func(cmd *cobra.Command, args []string) error { - // Generate TF Code - cli.StartProgress("Generating Terraform Code...") - - // Explicitly set Lacework profile if it was passed in main args - if cli.Profile != "default" { - GenerateGcpCommandState.LaceworkProfile = cli.Profile - } - - // Setup modifiers for NewTerraform constructor - mods := []gcp.GcpTerraformModifier{ - gcp.WithGcpServiceAccountCredentials(GenerateGcpCommandState.ServiceAccountCredentials), - gcp.WithOrganizationId(GenerateGcpCommandState.GcpOrganizationId), - gcp.WithProjectId(GenerateGcpCommandState.GcpProjectId), - gcp.WithExistingServiceAccount(GenerateGcpCommandState.ExistingServiceAccount), - gcp.WithConfigurationIntegrationName(GenerateGcpCommandState.ConfigurationIntegrationName), - gcp.WithAuditLogLabels(GenerateGcpCommandState.AuditLogLabels), - gcp.WithPubSubSubscriptionLabels(GenerateGcpCommandState.PubSubSubscriptionLabels), - gcp.WithPubSubTopicLabels(GenerateGcpCommandState.PubSubTopicLabels), - gcp.WithExistingLogSinkName(GenerateGcpCommandState.ExistingLogSinkName), - gcp.WithAuditLogIntegrationName(GenerateGcpCommandState.AuditLogIntegrationName), - gcp.WithLaceworkProfile(GenerateGcpCommandState.LaceworkProfile), - gcp.WithFoldersToInclude(GenerateGcpCommandState.FoldersToInclude), - gcp.WithFoldersToExclude(GenerateGcpCommandState.FoldersToExclude), - gcp.WithCustomFilter(GenerateGcpCommandState.CustomFilter), - gcp.WithGoogleWorkspaceFilter(GenerateGcpCommandState.GoogleWorkspaceFilter), - gcp.WithK8sFilter(GenerateGcpCommandState.K8sFilter), - gcp.WithPrefix(GenerateGcpCommandState.Prefix), - gcp.WithWaitTime(GenerateGcpCommandState.WaitTime), - gcp.WithMultipleProject(GenerateGcpCommandState.Projects), - gcp.WithProjectFilterList(GenerateGcpCommandState.ProjectFilterList), - gcp.WithRegions(GenerateGcpCommandState.Regions), - gcp.WithUsePubSubAudit(true), // always set to true, storage based integration deprecated - } - - if GenerateGcpCommandState.OrganizationIntegration { - mods = append(mods, gcp.WithOrganizationIntegration(GenerateGcpCommandState.OrganizationIntegration)) - } - - if len(GenerateGcpCommandState.FoldersToExclude) > 0 { - mods = append(mods, gcp.WithIncludeRootProjects(GenerateGcpCommandState.IncludeRootProjects)) - } - - // Create new struct - data := gcp.NewTerraform( - GenerateGcpCommandState.Agentless, - GenerateGcpCommandState.Configuration, - GenerateGcpCommandState.AuditLog, - GenerateGcpCommandState.UsePubSubAudit, - mods...) - - // Generate - hcl, err := data.Generate() - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "failed to generate terraform code") - } - - // Write-out generated code to location specified - dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "gcp") - if err != nil { - return err - } - - // Prompt to execute - err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Default: GenerateGcpCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, - Response: &GenerateGcpCommandExtraState.TerraformApply, - }) - - if err != nil { - return errors.Wrap(err, "failed to prompt for terraform execution") - } - - locationDir, _ := determineOutputDirPath(dirname, "gcp") - if GenerateGcpCommandExtraState.TerraformApply { - // Execution pre-run check - err := executionPreRunChecks(dirname, locationDir, "gcp") - if err != nil { - return err - } - } - - // Output where code was generated - if !GenerateGcpCommandExtraState.TerraformApply { - cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) - } - - return nil - }, - PreRunE: func(cmd *cobra.Command, _ []string) error { - // Validate output location is OK if supplied - dirname, err := cmd.Flags().GetString("output") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateOutputLocation(dirname); err != nil { - return err - } - - // Validate gcp sa credentials file, if passed - gcpSaCredentials, err := cmd.Flags().GetString("service_account_credentials") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - - if gcpSaCredentials != "" { - if err := gcp.ValidateServiceAccountCredentialsFile(gcpSaCredentials); err != nil { - return err - } - } - - projectId, err := cmd.Flags().GetString("project_id") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if projectId == "" && !cli.InteractiveMode() { - return errors.New("project_id must be provided") - } - - // Load any cached inputs if interactive - if cli.InteractiveMode() { - cachedOptions := &gcp.GenerateGcpTfConfigurationArgs{} - iacParamsExpired := cli.ReadCachedAsset(CachedGcpAssetIacParams, &cachedOptions) - if iacParamsExpired { - cli.Log.Debug("loaded previously set values for GCP iac generation") - } - - extraState := &GcpGenerateCommandExtraState{} - extraStateParamsExpired := cli.ReadCachedAsset(CachedAssetGcpExtraState, &extraState) - if extraStateParamsExpired { - cli.Log.Debug("loaded previously set values for GCP iac generation (extra state)") - } - - // Determine if previously cached options exists; prompt user if they'd like to continue - answer := false - if !iacParamsExpired || !extraStateParamsExpired { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, - Response: &answer, - }); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - - // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs - // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get - // re-prompted. - if answer { - // Merge cached inputs to current options (current options win) - if err := mergo.Merge(GenerateGcpCommandState, cachedOptions); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - if err := mergo.Merge(GenerateGcpCommandExtraState, extraState); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - } - - // Collect and/or confirm parameters - if err := promptGcpGenerate( - GenerateGcpCommandState, - GenerateGcpExistingServiceAccountDetails, - GenerateGcpCommandExtraState, - ); err != nil { - return errors.Wrap(err, "collecting/confirming parameters") - } - - return nil - }, - } -) - -type GcpGenerateCommandExtraState struct { - AskAdvanced bool - Output string - UseExistingServiceAccount bool - UseExistingSink bool - TerraformApply bool -} - -func (gcp *GcpGenerateCommandExtraState) isEmpty() bool { - return gcp.Output == "" && - !gcp.AskAdvanced && - !gcp.UseExistingServiceAccount && - !gcp.UseExistingSink && - !gcp.TerraformApply -} - -// Flush current state of the struct to disk, provided it's not empty -func (gcp *GcpGenerateCommandExtraState) writeCache() { - if !gcp.isEmpty() { - cli.WriteAssetToCache(CachedAssetGcpExtraState, time.Now().Add(time.Hour*1), gcp) - } -} - -func initGenerateGcpTfCommandFlags() { - // add flags to sub commands - // TODO Share the help with the interactive generation - generateGcpTfCommand.PersistentFlags().BoolVar( - &GenerateGcpCommandState.Agentless, - "agentless", - false, - "enable agentless integration") - generateGcpTfCommand.PersistentFlags().BoolVar( - &GenerateGcpCommandState.AuditLog, - "audit_log", - false, - "enable audit log integration") - generateGcpTfCommand.PersistentFlags().BoolVar( - &GenerateGcpCommandState.Configuration, - "configuration", - false, - "enable configuration integration") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandState.ServiceAccountCredentials, - "service_account_credentials", - "", - "specify service account credentials JSON file path (leave blank to make use of google credential ENV vars)") - generateGcpTfCommand.PersistentFlags().BoolVar( - &GenerateGcpCommandState.OrganizationIntegration, - "organization_integration", - false, - "enable organization integration") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandState.GcpOrganizationId, - "organization_id", - "", - "specify the organization id (only set if agentless integration or organization_integration is set)") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandState.GcpProjectId, - "project_id", - "", - "specify the project id to be used to provision lacework resources (required)") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpExistingServiceAccountDetails.Name, - "existing_service_account_name", - "", - "specify existing service account name") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpExistingServiceAccountDetails.PrivateKey, - "existing_service_account_private_key", - "", - "specify existing service account private key (base64 encoded)") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandState.ConfigurationIntegrationName, - "configuration_integration_name", - "", - "specify a custom configuration integration name") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandState.ExistingLogSinkName, - "existing_sink_name", - "", - "specify existing sink name") - generateGcpTfCommand.PersistentFlags().StringSliceVar( - &GenerateGcpCommandState.ProjectFilterList, - "project_filter_list", - []string{}, - "List of GCP project IDs to monitor for Agentless integration") - generateGcpTfCommand.PersistentFlags().StringSliceVar( - &GenerateGcpCommandState.Regions, - "regions", - []string{}, - "List of GCP regions to deploy for Agentless integration") - - // --- - - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandState.CustomFilter, - "custom_filter", - "", - "Audit Log filter which supersedes all other filter options when defined") - generateGcpTfCommand.PersistentFlags().BoolVar( - &GenerateGcpCommandState.GoogleWorkspaceFilter, - "google_workspace_filter", - true, - "filter out Google Workspace login logs from GCP Audit Log sinks") - generateGcpTfCommand.PersistentFlags().BoolVar( - &GenerateGcpCommandState.K8sFilter, - "k8s_filter", - true, - "filter out GKE logs from GCP Audit Log sinks") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandState.Prefix, - "prefix", - "", - "prefix that will be used at the beginning of every generated resource") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandState.WaitTime, - "wait_time", - "", - "amount of time to wait before the next resource is provisioned") - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandState.AuditLogIntegrationName, - "audit_log_integration_name", - "", - "specify a custom audit log integration name") - generateGcpTfCommand.PersistentFlags().StringArrayVarP( - &GenerateGcpCommandState.FoldersToExclude, - "folders_to_exclude", - "e", - []string{}, - "List of root folders to exclude for an organization-level integration") - generateGcpTfCommand.PersistentFlags().BoolVar( - &GenerateGcpCommandState.IncludeRootProjects, - "include_root_projects", - true, - "Disables logic that includes root-level projects if excluding folders") - generateGcpTfCommand.PersistentFlags().StringArrayVarP( - &GenerateGcpCommandState.FoldersToInclude, - "folders_to_include", - "i", - []string{}, - "list of root folders to include for an organization-level integration") - generateGcpTfCommand.PersistentFlags().BoolVar( - &GenerateGcpCommandExtraState.TerraformApply, - "apply", - false, - "run terraform apply without executing plan or prompting", - ) - generateGcpTfCommand.PersistentFlags().StringVar( - &GenerateGcpCommandExtraState.Output, - "output", - "", - "location to write generated content (default is ~/lacework/gcp)", - ) - generateGcpTfCommand.PersistentFlags().BoolVar( - &GenerateGcpCommandState.UsePubSubAudit, - "use_pub_sub", - true, - "deprecated: pub/sub audit log integration is always used and only supported type") - generateGcpTfCommand.PersistentFlags().StringSliceVar( - &GenerateGcpCommandState.Projects, - "projects", - []string{}, - "list of project IDs to integrate with (project-level integrations)") -} - -func promptGcpAgentlessQuestions( - config *gcp.GenerateGcpTfConfigurationArgs, - extraState *GcpGenerateCommandExtraState, -) error { - projectFilterListInput := "" - - err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionGcpProjectFilterList, Default: strings.Join(config.ProjectFilterList, ",")}, - Response: &projectFilterListInput, - }, - }, config.Agentless) - - if projectFilterListInput != "" { - config.ProjectFilterList = strings.Split(projectFilterListInput, ",") - } - - return err -} - -func promptGcpAuditLogQuestions( - config *gcp.GenerateGcpTfConfigurationArgs, - extraState *GcpGenerateCommandExtraState, -) error { - - err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionGcpUseExistingSink, Default: extraState.UseExistingSink}, - Checks: []*bool{&config.AuditLog}, - Required: true, - Response: &extraState.UseExistingSink, - }, - { - Prompt: &survey.Input{Message: QuestionGcpExistingSinkName, Default: config.ExistingLogSinkName}, - Checks: []*bool{&config.AuditLog, &extraState.UseExistingSink}, - Required: true, - Response: &config.ExistingLogSinkName, - }, - { - Prompt: &survey.Input{Message: QuestionGcpCustomFilter, Default: config.CustomFilter}, - Checks: []*bool{&config.AuditLog}, - Response: &config.CustomFilter, - }, - }, config.AuditLog) - - return err -} - -func promptGcpExistingServiceAccountQuestions(config *gcp.GenerateGcpTfConfigurationArgs) error { - // ensure struct is initialized - if config.ExistingServiceAccount == nil { - config.ExistingServiceAccount = &gcp.ExistingServiceAccountDetails{} - } - - err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionExistingServiceAccountName, Default: config.ExistingServiceAccount.Name}, - Response: &config.ExistingServiceAccount.Name, - Opts: []survey.AskOpt{survey.WithValidator(survey.Required)}, - }, - { - Prompt: &survey.Input{ - Message: QuestionExistingServiceAccountPrivateKey, - Default: config.ExistingServiceAccount.PrivateKey, - }, - Response: &config.ExistingServiceAccount.PrivateKey, - Opts: []survey.AskOpt{ - survey.WithValidator(survey.Required), - survey.WithValidator(gcp.ValidateStringIsBase64), - }, - }}) - - return err -} - -func promptGcpIntegrationNameQuestions(config *gcp.GenerateGcpTfConfigurationArgs) error { - err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{ - Message: QuestionGcpConfigurationIntegrationName, - Default: config.ConfigurationIntegrationName, - }, - Checks: []*bool{&config.Configuration}, - Response: &config.ConfigurationIntegrationName, - }, - { - Prompt: &survey.Input{Message: QuestionGcpAuditLogIntegrationName, Default: config.AuditLogIntegrationName}, - Checks: []*bool{&config.AuditLog}, - Response: &config.AuditLogIntegrationName, - }}) - - return err -} - -func promptCustomizeGcpOutputLocation(extraState *GcpGenerateCommandExtraState) error { - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionGcpCustomizeOutputLocation, Default: extraState.Output}, - Response: &extraState.Output, - Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, - Required: true, - }) - - return err -} - -func promptCustomizeGcpProjects(config *gcp.GenerateGcpTfConfigurationArgs) error { - - validation := func(val interface{}) error { - switch value := val.(type) { - case string: - for _, id := range strings.Split(value, ",") { - err := validateGcpProjectId(strings.TrimSpace(id)) - if err != nil { - return err - } - } - default: - return errors.New("value must be a string") - } - - return nil - } - - var projects string - - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionGcpCustomizeProjects}, - Response: &projects, - Opts: []survey.AskOpt{survey.WithValidator(validation)}, - Required: true, - }) - - if err != nil { - return err - } - - for _, id := range strings.Split(projects, ",") { - config.Projects = append(config.Projects, strings.TrimSpace(id)) - } - - return nil -} - -func askAdvancedOptions(config *gcp.GenerateGcpTfConfigurationArgs, extraState *GcpGenerateCommandExtraState) error { - answer := "" - - // Prompt for options - for answer != GcpAdvancedOptDone { - // Construction of this slice is a bit strange at first look, but the reason for that is because we have to do - // string validation to know which option was selected due to how survey works; and doing it by index (also - // supported) is difficult when the options are dynamic (which they are) - var options []string - - // Only show Advanced Agentless options if Agentless integration is set to true - if config.Agentless { - options = append(options, GcpAdvancedOptAgentless) - } - - // Only show Advanced AuditLog options if AuditLog integration is set to true - if config.AuditLog { - options = append(options, GcpAdvancedOptAuditLog) - } - - options = append(options, - GcpAdvancedOptExistingServiceAccount, - GcpAdvancedOptIntegrationName, - GcpAdvancedOptLocation, - GcpAdvancedOptProjects, - GcpAdvancedOptDone) - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: "Which options would you like to configure?", - Options: options, - }, - Response: &answer, - }); err != nil { - return err - } - - // Based on response, prompt for actions - switch answer { - case GcpAdvancedOptAgentless: - if err := promptGcpAgentlessQuestions(config, extraState); err != nil { - return err - } - case GcpAdvancedOptAuditLog: - if err := promptGcpAuditLogQuestions(config, extraState); err != nil { - return err - } - case GcpAdvancedOptExistingServiceAccount: - if err := promptGcpExistingServiceAccountQuestions(config); err != nil { - return err - } - case GcpAdvancedOptIntegrationName: - if err := promptGcpIntegrationNameQuestions(config); err != nil { - return err - } - case GcpAdvancedOptLocation: - if err := promptCustomizeGcpOutputLocation(extraState); err != nil { - return err - } - case GcpAdvancedOptProjects: - if err := promptCustomizeGcpProjects(config); err != nil { - return err - } - } - - // Re-prompt if not done - innerAskAgain := true - if answer == GcpAdvancedOptDone { - innerAskAgain = false - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Checks: []*bool{&innerAskAgain}, - Prompt: &survey.Confirm{Message: QuestionGcpAnotherAdvancedOpt, Default: false}, - Response: &innerAskAgain, - }); err != nil { - return err - } - - if !innerAskAgain { - answer = GcpAdvancedOptDone - } - } - - return nil -} - -func gcpConfigIsEmpty(g *gcp.GenerateGcpTfConfigurationArgs) bool { - return !g.Agentless && - !g.AuditLog && - !g.Configuration && - g.ServiceAccountCredentials == "" && - g.GcpOrganizationId == "" && - g.LaceworkProfile == "" -} - -func writeGcpGenerationArgsCache(a *gcp.GenerateGcpTfConfigurationArgs) { - if !gcpConfigIsEmpty(a) { - // If ExistingServiceAccount is partially set, don't write this to cache; the values won't work when loaded - if a.ExistingServiceAccount.IsPartial() { - a.ExistingServiceAccount = nil - } - cli.WriteAssetToCache(CachedGcpAssetIacParams, time.Now().Add(time.Hour*1), a) - } -} - -// entry point for launching a survey to build out the required generation parameters -func promptGcpGenerate( - config *gcp.GenerateGcpTfConfigurationArgs, - existingServiceAccount *gcp.ExistingServiceAccountDetails, - extraState *GcpGenerateCommandExtraState, -) error { - // Cache for later use if generation is abandon and in interactive mode - if cli.InteractiveMode() { - defer writeGcpGenerationArgsCache(config) - defer extraState.writeCache() - } - - // Set ExistingIamRole details, if provided as cli flags; otherwise don't initialize - if existingServiceAccount.Name != "" || - existingServiceAccount.PrivateKey != "" { - config.ExistingServiceAccount = existingServiceAccount - } - - // These are the core questions that should be asked. - if err := SurveyMultipleQuestionWithValidation( - []SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionGcpEnableAgentless, Default: config.Agentless}, - Response: &config.Agentless, - }, - { - Prompt: &survey.Confirm{Message: QuestionGcpEnableConfiguration, Default: config.Configuration}, - Response: &config.Configuration, - }, - { - Prompt: &survey.Confirm{Message: QuestionGcpEnableAuditLog, Default: config.AuditLog}, - Response: &config.AuditLog, - }, - }); err != nil { - return err - } - - // Validate one of configuration or audit log was enabled; otherwise error out - if !config.Agentless && !config.Configuration && !config.AuditLog { - return errors.New("must enable agentless, audit log or configuration") - } - - configOrAuditLogEnabled := config.Configuration || config.AuditLog - regionsInput := "" - - if err := SurveyMultipleQuestionWithValidation( - []SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionGcpProjectID, Default: config.GcpProjectId}, - Opts: []survey.AskOpt{survey.WithValidator(validateGcpProjectId)}, - Required: true, - Response: &config.GcpProjectId, - }, - { - Prompt: &survey.Input{Message: QuestionGcpRegions, Default: strings.Join(config.Regions, ",")}, - Checks: []*bool{&config.Agentless}, - Response: ®ionsInput, - Required: true, - }, - { - Prompt: &survey.Confirm{Message: QuestionGcpOrganizationIntegration, Default: config.OrganizationIntegration}, - Response: &config.OrganizationIntegration, - }, - }); err != nil { - return err - } - - organizationIdRequired := config.Agentless || config.OrganizationIntegration - - if err := SurveyMultipleQuestionWithValidation( - []SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionGcpOrganizationID, Default: config.GcpOrganizationId}, - Checks: []*bool{&organizationIdRequired}, - Required: true, - Response: &config.GcpOrganizationId, - }, - { - Prompt: &survey.Input{Message: QuestionGcpServiceAccountCredsPath, Default: config.ServiceAccountCredentials}, - Opts: []survey.AskOpt{survey.WithValidator(gcp.ValidateServiceAccountCredentials)}, - Checks: []*bool{&configOrAuditLogEnabled}, - Response: &config.ServiceAccountCredentials, - }, - }); err != nil { - return err - } - - if regionsInput != "" { - config.Regions = strings.Split(regionsInput, ",") - } - - // Find out if the customer wants to specify more advanced features - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionGcpConfigureAdvanced, Default: extraState.AskAdvanced}, - Response: &extraState.AskAdvanced, - }); err != nil { - return err - } - - // Keep prompting for advanced options until the say done - if extraState.AskAdvanced { - if err := askAdvancedOptions(config, extraState); err != nil { - return err - } - } - - return nil -} - -func validateGcpProjectId(val interface{}) error { - switch value := val.(type) { - - case string: - match, err := regexp.MatchString("(^[a-z][a-z0-9-]{4,28}[a-z0-9]$|^$)", value) - if err != nil { - return err - } - - if !match { - return errors.New(InvalidProjectIDMessage) - } - default: - return errors.New("value must be a string") - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gke.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gke.go deleted file mode 100644 index e2cdc0d2d..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_gke.go +++ /dev/null @@ -1,505 +0,0 @@ -package cmd - -import ( - "time" - - "github.com/lacework/go-sdk/lwgenerate/gcp" - - "github.com/AlecAivazis/survey/v2" - "github.com/imdario/mergo" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -type GkeGenerateCommandExtraState struct { - AskAdvanced bool - Output string - ConfigureNewBucketSettings bool - UseExistingServiceAccount bool - UseExistingSink bool - TerraformApply bool -} - -func (g *GkeGenerateCommandExtraState) isEmpty() bool { - return g.Output == "" && - !g.AskAdvanced && - !g.UseExistingServiceAccount && - !g.UseExistingSink && - !g.TerraformApply -} - -func (g *GkeGenerateCommandExtraState) writeCache() { - if !g.isEmpty() { - cli.WriteAssetToCache(CachedGkeAssetExtraState, time.Now().Add(time.Hour*1), g) - } -} - -var ( - QuestionGkeOrganizationIntegration = "Organization integration?" - QuestionGkeOrganizationID = "Specify the GCP organization ID:" - QuestionGkeProjectID = "Specify the project ID to be used to provision Lacework resources:" - QuestionGkeServiceAccountCredsPath = "Specify service account credentials JSON path: (optional)" - - QuestionGkeConfigureAdvanced = "Configure advanced integration options?" - GkeAdvancedOpt = "Configure additional options" - QuestionGkeUseExistingSink = "Use an existing sink?" - QuestionGkeExistingSinkName = "Specify the existing sink name" - GkeAdvancedOptIntegrationName = "Customize integration name(s)" - QuestionGkeIntegrationName = "Specify a custom integration name: (optional)" - - GkeAdvancedOptExistingServiceAccount = "Configure & use existing service account" - QuestionGkeExistingServiceAccountName = "Specify an existing service account name:" - QuestionGkeExistingServiceAccountPrivateKey = "Specify an existing service account private key" + - " (base64 encoded):" // guardrails-disable-line - - GkeAdvancedOptLocation = "Customize output location" - QuestionGkeCustomizeOutputLocation = "Provide the location for the output to be written:" - QuestionGkeAnotherAdvancedOpt = "Configure another advanced integration option" - GkeAdvancedOptDone = "Done" - - GenerateGkeCommandState = &gcp.GenerateGkeTfConfigurationArgs{} - GenerateGkeExistingServiceAccount = &gcp.ServiceAccount{} - GenerateGkeCommandExtraState = &GkeGenerateCommandExtraState{} - CachedGkeAssetIacParams = "iac-gke-generate-params" - CachedGkeAssetExtraState = "iac-gke-extra-state" - - generateGkeTfCommand = &cobra.Command{ - Use: "gke", - Short: "Generate and/or execute Terraform code for GKE integration", - Long: `Use this command to generate Terraform code for deploying Lacework into a GKE environment. - -By default, this command interactively prompts for the required information to setup the new cloud account. -In interactive mode, this command will: - -* Prompt for the required information to setup the integration -* Generate new Terraform code using the inputs -* Optionally, run the generated Terraform code: - * If Terraform is already installed, the version is verified as compatible for use - * If Terraform is not installed, or the version installed is not compatible, a new - version will be installed into a temporary location - * Once Terraform is detected or installed, Terraform plan will be executed - * The command will prompt with the outcome of the plan and allow to view more details - or continue with Terraform apply - * If confirmed, Terraform apply will be run, completing the setup of the cloud account - -This command can also be run in noninteractive mode. -See help output for more details on the parameter value(s) required for Terraform code generation. -`, - RunE: func(cmd *cobra.Command, args []string) error { - cli.StartProgress("Generating Terraform Code...") - - if cli.Profile != "default" { - GenerateGkeCommandState.LaceworkProfile = cli.Profile - } - - mods := []gcp.Modifier{ - gcp.WithGkeExistingServiceAccount(GenerateGkeCommandState.ExistingServiceAccount), - gcp.WithGkeExistingSinkName(GenerateGkeCommandState.ExistingSinkName), - gcp.WithGkeIntegrationName(GenerateGkeCommandState.IntegrationName), - gcp.WithGkeLabels(GenerateGkeCommandState.Labels), - gcp.WithGkeLaceworkProfile(GenerateGkeCommandState.LaceworkProfile), - gcp.WithGkeOrganizationId(GenerateGkeCommandState.OrganizationId), - gcp.WithGkeOrganizationIntegration(GenerateGkeCommandState.OrganizationIntegration), - gcp.WithGkePrefix(GenerateGkeCommandState.Prefix), - gcp.WithGkeProjectId(GenerateGkeCommandState.ProjectId), - gcp.WithGkePubSubSubscriptionLabels(GenerateGkeCommandState.PubSubSubscriptionLabels), - gcp.WithGkePubSubTopicLabels(GenerateGkeCommandState.PubSubTopicLabels), - gcp.WithGkeServiceAccountCredentials(GenerateGkeCommandState.ServiceAccountCredentials), - gcp.WithGkeWaitTime(GenerateGkeCommandState.WaitTime), - } - - hcl, err := gcp.NewGkeTerraform(mods...).Generate() - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "failed to generate terraform code") - } - - dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "gke") - if err != nil { - return err - } - - err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Default: GenerateGkeCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, - Response: &GenerateGkeCommandExtraState.TerraformApply, - }) - - if err != nil { - return errors.Wrap(err, "failed to prompt for terraform execution") - } - - locationDir, _ := determineOutputDirPath(dirname, "gke") - if GenerateGkeCommandExtraState.TerraformApply { - err := executionPreRunChecks(dirname, locationDir, "gke") - if err != nil { - return err - } - } - - if !GenerateGkeCommandExtraState.TerraformApply { - cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) - } - - return nil - }, - PreRunE: func(cmd *cobra.Command, args []string) error { - var err error - - if err = gkeValidation(cmd); err != nil { - return err - } - - if cli.InteractiveMode() { - if err = gkeCaching(); err != nil { - return err - } - } - - err = promptGkeGenerate(GenerateGkeCommandState, GenerateGkeExistingServiceAccount, GenerateGkeCommandExtraState) - if err != nil { - return errors.Wrap(err, "collecting/confirming parameters") - } - - return nil - }, - } -) - -func gkeValidation(cmd *cobra.Command) error { - dirname, err := cmd.Flags().GetString("output") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - - if err := validateOutputLocation(dirname); err != nil { - return err - } - - gcpSaCredentials, err := cmd.Flags().GetString("service_account_credentials") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - - if gcpSaCredentials != "" { - if err := gcp.ValidateServiceAccountCredentialsFile(gcpSaCredentials); err != nil { - return err - } - } - - projectId, err := cmd.Flags().GetString("project_id") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - - if projectId == "" && !cli.InteractiveMode() { - return errors.New("project_id must be provided") - } - - return nil -} - -func gkeCaching() error { - cachedOptions := &gcp.GenerateGkeTfConfigurationArgs{} - iacParamsExpired := cli.ReadCachedAsset(CachedGkeAssetIacParams, &cachedOptions) - if iacParamsExpired { - cli.Log.Debug("loaded previously set values for GCP iac generation") - } - - extraState := &GkeGenerateCommandExtraState{} - extraStateParamsExpired := cli.ReadCachedAsset(CachedGkeAssetExtraState, &extraState) - if extraStateParamsExpired { - cli.Log.Debug("loaded previously set values for GCP iac generation (extra state)") - } - - answer := false - if !iacParamsExpired || !extraStateParamsExpired { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, - Response: &answer, - }); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - - if answer { - if err := mergo.Merge(GenerateGkeCommandState, cachedOptions); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - if err := mergo.Merge(GenerateGkeCommandExtraState, extraState); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - - return nil -} - -func initGenerateGkeTfCommandFlags() { - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeExistingServiceAccount.Name, - "existing_service_account_name", - "", - "specify existing service account name") - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeExistingServiceAccount.PrivateKey, - "existing_service_account_private_key", - "", - "specify existing service account private key (base64 encoded)") - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeCommandState.ExistingSinkName, - "existing_sink_name", - "", - "specify existing sink name") - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeCommandState.IntegrationName, - "integration_name", - "", - "specify a custom integration name") - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeCommandState.OrganizationId, - "organization_id", - "", - "specify the organization id (only set if organization_integration is set)") - generateGkeTfCommand.PersistentFlags().BoolVar( - &GenerateGkeCommandState.OrganizationIntegration, - "organization_integration", - false, - "enable organization integration") - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeCommandState.Prefix, - "prefix", - "", - "prefix that will be used at the beginning of every generated resource") - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeCommandState.ProjectId, - "project_id", - "", - "specify the project id to be used to provision lacework resources (required)") - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeCommandState.ServiceAccountCredentials, - "service_account_credentials", - "", - "specify service account credentials JSON file path (leave blank to make use of google credential ENV vars)") - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeCommandState.WaitTime, - "wait_time", - "", - "amount of time to wait before the next resource is provisioned") - generateGkeTfCommand.PersistentFlags().BoolVar( - &GenerateGkeCommandExtraState.TerraformApply, - "apply", - false, - "run terraform apply without executing plan or prompting", - ) - generateGkeTfCommand.PersistentFlags().StringVar( - &GenerateGkeCommandExtraState.Output, - "output", - "", - "location to write generated content (default is ~/lacework/gcp)", - ) -} - -func gkeConfigIsEmpty(g *gcp.GenerateGkeTfConfigurationArgs) bool { - return g.ServiceAccountCredentials == "" && - g.ProjectId == "" && - g.OrganizationId == "" && - g.LaceworkProfile == "" -} - -func writeGkeGenerationArgsCache(a *gcp.GenerateGkeTfConfigurationArgs) { - if !gkeConfigIsEmpty(a) { - if a.ExistingServiceAccount.IsPartial() { - a.ExistingServiceAccount = nil - } - cli.WriteAssetToCache(CachedGkeAssetIacParams, time.Now().Add(time.Hour*1), a) - } -} - -func promptGkeGenerate( - config *gcp.GenerateGkeTfConfigurationArgs, - existingServiceAccount *gcp.ServiceAccount, - extraState *GkeGenerateCommandExtraState, -) error { - if cli.InteractiveMode() { - defer writeGkeGenerationArgsCache(config) - defer extraState.writeCache() - } - - if existingServiceAccount.Name != "" || - existingServiceAccount.PrivateKey != "" { - config.ExistingServiceAccount = existingServiceAccount - } - - if err := SurveyMultipleQuestionWithValidation( - []SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionGkeProjectID, Default: config.ProjectId}, - Required: true, - Opts: []survey.AskOpt{survey.WithValidator(validateGcpProjectId)}, - Response: &config.ProjectId, - }, - { - Prompt: &survey.Confirm{Message: QuestionGkeOrganizationIntegration, Default: config.OrganizationIntegration}, - Response: &config.OrganizationIntegration, - }, - { - Prompt: &survey.Input{Message: QuestionGkeOrganizationID, Default: config.OrganizationId}, - Checks: []*bool{&config.OrganizationIntegration}, - Required: true, - Response: &config.OrganizationId, - }, - { - Prompt: &survey.Input{Message: QuestionGkeServiceAccountCredsPath, Default: config.ServiceAccountCredentials}, - Opts: []survey.AskOpt{survey.WithValidator(gcp.ValidateServiceAccountCredentials)}, - Response: &config.ServiceAccountCredentials, - }, - }); err != nil { - return err - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionGkeConfigureAdvanced, Default: extraState.AskAdvanced}, - Response: &extraState.AskAdvanced, - }); err != nil { - return err - } - - if extraState.AskAdvanced { - if err := promptGkeAdvancedOptions(config, extraState); err != nil { - return err - } - } - - return nil -} - -func promptGkeAdvancedOptions( - config *gcp.GenerateGkeTfConfigurationArgs, extraState *GkeGenerateCommandExtraState, -) error { - answer := "" - options := []string{ - GkeAdvancedOpt, - GkeAdvancedOptExistingServiceAccount, - GkeAdvancedOptIntegrationName, - GkeAdvancedOptLocation, - GkeAdvancedOptDone, - } - - for answer != GkeAdvancedOptDone { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: "Which options would you like to configure?", - Options: options, - }, - Response: &answer, - }); err != nil { - return err - } - - switch answer { - case GkeAdvancedOpt: - if err := promptGkeQuestions(config, extraState); err != nil { - return err - } - case GkeAdvancedOptExistingServiceAccount: - if err := promptGkeExistingServiceAccountQuestions(config); err != nil { - return err - } - case GkeAdvancedOptIntegrationName: - if err := promptGkeIntegrationNameQuestions(config); err != nil { - return err - } - case GkeAdvancedOptLocation: - if err := promptCustomizeGkeOutputLocation(extraState); err != nil { - return err - } - } - - innerAskAgain := true - if answer == GkeAdvancedOptDone { - innerAskAgain = false - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Checks: []*bool{&innerAskAgain}, - Prompt: &survey.Confirm{Message: QuestionGkeAnotherAdvancedOpt, Default: false}, - Response: &innerAskAgain, - }); err != nil { - return err - } - - if !innerAskAgain { - answer = GkeAdvancedOptDone - } - } - - return nil -} - -func promptGkeQuestions(config *gcp.GenerateGkeTfConfigurationArgs, extraState *GkeGenerateCommandExtraState) error { - err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionGkeUseExistingSink, Default: extraState.UseExistingSink}, - Required: true, - Response: &extraState.UseExistingSink, - }, - { - Prompt: &survey.Input{Message: QuestionGkeExistingSinkName, Default: config.ExistingSinkName}, - Checks: []*bool{&extraState.UseExistingSink}, - Required: true, - Response: &config.ExistingSinkName, - }, - }) - - return err -} - -func promptGkeExistingServiceAccountQuestions(config *gcp.GenerateGkeTfConfigurationArgs) error { - if config.ExistingServiceAccount == nil { - config.ExistingServiceAccount = &gcp.ServiceAccount{} - } - - err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{ - Message: QuestionGkeExistingServiceAccountName, - Default: config.ExistingServiceAccount.Name, - }, - Response: &config.ExistingServiceAccount.Name, - Opts: []survey.AskOpt{survey.WithValidator(survey.Required)}, - }, - { - Prompt: &survey.Input{ - Message: QuestionGkeExistingServiceAccountPrivateKey, - Default: config.ExistingServiceAccount.PrivateKey, - }, - Response: &config.ExistingServiceAccount.PrivateKey, - Opts: []survey.AskOpt{ - survey.WithValidator(survey.Required), - survey.WithValidator(gcp.ValidateStringIsBase64), - }, - }}) - - return err -} - -func promptGkeIntegrationNameQuestions(config *gcp.GenerateGkeTfConfigurationArgs) error { - err := SurveyMultipleQuestionWithValidation([]SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Input{Message: QuestionGkeIntegrationName, Default: config.IntegrationName}, - Response: &config.IntegrationName, - }, - }) - - return err -} - -func promptCustomizeGkeOutputLocation(extraState *GkeGenerateCommandExtraState) error { - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionGkeCustomizeOutputLocation, Default: extraState.Output}, - Response: &extraState.Output, - Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, - Required: true, - }) - - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_k8s.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_k8s.go deleted file mode 100644 index 6660b88ec..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_k8s.go +++ /dev/null @@ -1,24 +0,0 @@ -package cmd - -import "github.com/spf13/cobra" - -var ( - generateK8sCommand = &cobra.Command{ - Use: "k8s", - Short: "Generate Kubernetes integration IaC", - Long: `Generate IaC to deploy Lacework into a Kubernetes platform. - -This command creates Infrastructure as Code (IaC) in the form of Terraform HCL, with the option of running -Terraform and deploying Lacework into GKE. -`, - } -) - -func init() { - generateTfCommand.AddCommand(generateK8sCommand) - - initGenerateGkeTfCommandFlags() - initGenerateAwsEksAuditTfCommandFlags() - generateK8sCommand.AddCommand(generateGkeTfCommand) - generateK8sCommand.AddCommand(generateAwsEksAuditTfCommand) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_oci.go b/vendor/github.com/lacework/go-sdk/cli/cmd/generate_oci.go deleted file mode 100644 index 534064e5a..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/generate_oci.go +++ /dev/null @@ -1,438 +0,0 @@ -package cmd - -import ( - "time" - - "github.com/imdario/mergo" - "github.com/spf13/cobra" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/internal/validate" - "github.com/lacework/go-sdk/lwgenerate/oci" - "github.com/pkg/errors" -) - -type OciGenerateCommandExtraState struct { - AskAdvanced bool - Output string - TerraformApply bool -} - -var ( - // questions - QuestionOciEnableConfig = "Enable configuration integration?" - QuestionOciTenantOcid = "Specify the OCID of the tenant to be integrated" - QuestionOciUserEmail = "Specify the email address to associate with the integration OCI user" - QuestionOciConfigAdvanced = "Configure advanced integration options?" - QuestionOciConfigName = "Specify name of configuration integration (optional)" - QuestionOciCustomizeOutputLocation = "Provide the location for the output to be written:" - QuestionOciAnotherAdvancedOpt = "Configure another advanced integration option" - - // options - OciAdvancedOptDone = "Done" - OciAdvancedOptLocation = "Customize output location" - OciAdvancedOptIntegrationName = "Customize integration name" - - // state - GenerateOciCommandState = &oci.GenerateOciTfConfigurationArgs{} - GenerateOciCommandExtraState = &OciGenerateCommandExtraState{} - - // cache keys - CachedOciAssetIacParams = "iac-oci-generate-params" - CachedAssetOciExtraState = "iac-oci-extra-state" - - // oci command is used to generate TF code for OCI - generateOciTfCommand = &cobra.Command{ - Use: "oci", - Short: "Generate and/or execute Terraform code for OCI integration", - Long: `Use this command to generate Terraform code for deploying Lacework into an OCI tenant. - -By default, this command interactively prompts for the required information to setup the new cloud account. -In interactive mode, this command will: - -* Prompt for the required information to setup the integration -* Generate new Terraform code using the inputs -* Optionally, run the generated Terraform code: - * If Terraform is already installed, the version is verified as compatible for use - * If Terraform is not installed, or the version installed is not compatible, a new - version will be installed into a temporary location - * Once Terraform is detected or installed, Terraform plan will be executed - * The command will prompt with the outcome of the plan and allow to view more details - or continue with Terraform apply - * If confirmed, Terraform apply will be run, completing the setup of the cloud account - -This command can also be run in noninteractive mode. -See help output for more details on the parameter value(s) required for Terraform code generation. -`, - RunE: runGenerateOci, - PreRunE: preRunGenerateOci, - } -) - -func runGenerateOci(cmd *cobra.Command, args []string) error { - // Generate TF Code - cli.StartProgress("Generating Terraform Code...") - - // Explicitly set Lacework profile if it was passed in main args - if cli.Profile != "default" { - GenerateOciCommandState.LaceworkProfile = cli.Profile - } - - // Setup modifiers for NewTerraform constructor - mods := []oci.OciTerraformModifier{ - oci.WithLaceworkProfile(GenerateOciCommandState.LaceworkProfile), - oci.WithConfigName(GenerateOciCommandState.ConfigName), - oci.WithTenantOcid(GenerateOciCommandState.TenantOcid), - oci.WithUserEmail(GenerateOciCommandState.OciUserEmail), - } - - // Create new struct - data := oci.NewTerraform( - GenerateOciCommandState.Config, - mods...) - - // Generate - hcl, err := data.Generate() - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "failed to generate terraform code") - } - - // Write-out generated code to location specified - dirname, _, err := writeGeneratedCodeToLocation(cmd, hcl, "oci") - if err != nil { - return err - } - - // Prompt to execute - err = SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Default: GenerateOciCommandExtraState.TerraformApply, Message: QuestionRunTfPlan}, - Response: &GenerateOciCommandExtraState.TerraformApply, - }) - - if err != nil { - return errors.Wrap(err, "failed to prompt for terraform execution") - } - - locationDir, _ := determineOutputDirPath(dirname, "oci") - if GenerateOciCommandExtraState.TerraformApply { - // Execution pre-run check - err := executionPreRunChecks(dirname, locationDir, "oci") - if err != nil { - return err - } - } - - // Output where code was generated - if !GenerateOciCommandExtraState.TerraformApply { - cli.OutputHuman(provideGuidanceAfterExit(false, false, locationDir, "terraform")) - } - - return nil -} - -func preRunGenerateOci(cmd *cobra.Command, _ []string) error { - // Validate output location is OK if supplied - dirname, err := cmd.Flags().GetString("output") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateOutputLocation(dirname); err != nil { - return err - } - - // Validate tenant OCID - tenantOcid, err := cmd.Flags().GetString("tenant_ocid") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateOciTenantOcid(tenantOcid); tenantOcid != "" && err != nil { - return err - } - - // Validate user email - ociUserEmail, err := cmd.Flags().GetString("oci_user_email") - if err != nil { - return errors.Wrap(err, "failed to load command flags") - } - if err := validateOciUserEmail(ociUserEmail); ociUserEmail != "" && err != nil { - return err - } - - // Load any cached inputs if interactive - if cli.InteractiveMode() { - cachedOptions := &oci.GenerateOciTfConfigurationArgs{} - iacParamsExpired := cli.ReadCachedAsset(CachedOciAssetIacParams, &cachedOptions) - if iacParamsExpired { - cli.Log.Debug("loaded previously set values for OCI iac generation") - } - - extraState := &OciGenerateCommandExtraState{} - extraStateParamsExpired := cli.ReadCachedAsset(CachedAssetOciExtraState, &extraState) - if extraStateParamsExpired { - cli.Log.Debug("loaded previously set values for OCI iac generation (extra state)") - } - - // Determine if previously cached options exists; prompt user if they'd like to continue - answer := false - if !iacParamsExpired || !extraStateParamsExpired { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionUsePreviousCache, Default: false}, - Response: &answer, - }); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - - // If the user decides NOT to use the previous values; we won't load them. However, every time the command runs - // we are going to write out new cached values, so if they run it - bail out - and run it again they'll get - // re-prompted. - if answer { - // Merge cached inputs to current options (current options win) - if err := mergo.Merge(GenerateOciCommandState, cachedOptions); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - if err := mergo.Merge(GenerateOciCommandExtraState, extraState); err != nil { - return errors.Wrap(err, "failed to load saved options") - } - } - } - - // Collect and/or confirm parameters - err = promptOciGenerate(GenerateOciCommandState, GenerateOciCommandExtraState) - if err != nil { - return errors.Wrap(err, "collecting/confirming parameters") - } - - return nil -} - -func initGenerateOciTfCommandFlags() { - // add flags to sub commands - generateOciTfCommand.PersistentFlags().BoolVar( - &GenerateOciCommandState.Config, - "config", - false, - "enable configuration integration") - generateOciTfCommand.PersistentFlags().StringVar( - &GenerateOciCommandState.ConfigName, - "config_name", - "", - "specify name of configuration integration") - generateOciTfCommand.PersistentFlags().StringVar( - &GenerateOciCommandState.TenantOcid, - "tenant_ocid", - "", - "specify the OCID of the tenant to integrate") - generateOciTfCommand.PersistentFlags().StringVar( - &GenerateOciCommandState.OciUserEmail, - "oci_user_email", - "", - "specify the email address to associate with the integration OCI user") - generateOciTfCommand.PersistentFlags().BoolVar( - &GenerateOciCommandExtraState.TerraformApply, - "apply", - false, - "run terraform apply without executing plan or prompting", - ) - generateOciTfCommand.PersistentFlags().StringVar( - &GenerateOciCommandExtraState.Output, - "output", - "", - "location to write generated content (default is ~/lacework/oci)", - ) -} - -// basic validation of Tenant OCID format -func validateOciTenantOcid(val interface{}) error { - return validateStringWithRegex( - val, - // https://docs.oracle.com/en-us/iaas/Content/General/Concepts/identifiers.htm - `ocid1\.tenancy\.[^\.\s]*\.[^\.\s]*(\.[^\.\s]+)?\.[^\.\s]+`, - "invalid tenant OCID supplied", - ) -} - -// basic validation of email -func validateOciUserEmail(val interface{}) error { - return validate.EmailAddress(val) -} - -func promptCustomizeOciOutputLocation(extraState *OciGenerateCommandExtraState) error { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionOciCustomizeOutputLocation, Default: extraState.Output}, - Response: &extraState.Output, - Opts: []survey.AskOpt{survey.WithValidator(validPathExists)}, - Required: true, - }); err != nil { - return err - } - - return nil -} - -func promptCustomizeOciConfigOptions(config *oci.GenerateOciTfConfigurationArgs) error { - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionOciConfigName, Default: config.ConfigName}, - Checks: []*bool{&config.Config}, - Response: &config.ConfigName, - }); err != nil { - return err - } - - return nil -} - -func askAdvancedOciOptions(config *oci.GenerateOciTfConfigurationArgs, extraState *OciGenerateCommandExtraState) error { - answer := "" - - // Prompt for options - for answer != OciAdvancedOptDone { - var options []string - - // Determine if user specified name for Config is potentially required - if config.Config { - options = append(options, OciAdvancedOptIntegrationName) - } - - options = append(options, OciAdvancedOptLocation) - - options = append(options, OciAdvancedOptDone) - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: "Which options would you like to configure?", - Options: options, - }, - Response: &answer, - }); err != nil { - return err - } - - // Based on response, prompt for actions - switch answer { - case OciAdvancedOptLocation: - if err := promptCustomizeOciOutputLocation(extraState); err != nil { - return err - } - case OciAdvancedOptIntegrationName: - if err := promptCustomizeOciConfigOptions(config); err != nil { - return err - } - } - - // Re-prompt if not done - innerAskAgain := true - if answer == OciAdvancedOptDone { - innerAskAgain = false - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Checks: []*bool{&innerAskAgain}, - Prompt: &survey.Confirm{Message: QuestionOciAnotherAdvancedOpt, Default: false}, - Response: &innerAskAgain, - }); err != nil { - return err - } - - if !innerAskAgain { - answer = OciAdvancedOptDone - } - } - - return nil -} - -func configEnabled(config *oci.GenerateOciTfConfigurationArgs) *bool { - return &config.Config -} - -func (a *OciGenerateCommandExtraState) isEmpty() bool { - return a.Output == "" && - !a.TerraformApply && - !a.AskAdvanced -} - -// Flush current state of the struct to disk, provided it's not empty -func (a *OciGenerateCommandExtraState) writeCache() { - if !a.isEmpty() { - cli.WriteAssetToCache(CachedAssetOciExtraState, time.Now().Add(time.Hour*1), a) - } -} - -func ociConfigIsEmpty(g *oci.GenerateOciTfConfigurationArgs) bool { - return !g.Config && - g.ConfigName == "" && - g.LaceworkProfile == "" && - g.TenantOcid == "" && - g.OciUserEmail == "" -} - -func writeOciGenerationArgsCache(a *oci.GenerateOciTfConfigurationArgs) { - if !ociConfigIsEmpty(a) { - cli.WriteAssetToCache(CachedOciAssetIacParams, time.Now().Add(time.Hour*1), a) - } -} - -// entry point for launching a survey to build out the required generation parameters -func promptOciGenerate( - config *oci.GenerateOciTfConfigurationArgs, - extraState *OciGenerateCommandExtraState, -) error { - // Cache for later use if generation is abandon and in interactive mode - if cli.InteractiveMode() { - defer writeOciGenerationArgsCache(config) - defer extraState.writeCache() - } - - // These are the core questions that should be asked. - if err := SurveyMultipleQuestionWithValidation( - []SurveyQuestionWithValidationArgs{ - { - Prompt: &survey.Confirm{Message: QuestionOciEnableConfig, Default: config.Config}, - Response: &config.Config, - }, - }); err != nil { - return err - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionOciTenantOcid, Default: config.TenantOcid}, - Response: &config.TenantOcid, - Opts: []survey.AskOpt{survey.WithValidator(survey.Required), survey.WithValidator(validateOciTenantOcid)}, - Checks: []*bool{configEnabled(config)}, - }); err != nil { - return err - } - - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Input{Message: QuestionOciUserEmail, Default: config.OciUserEmail}, - Response: &config.OciUserEmail, - Opts: []survey.AskOpt{survey.WithValidator(survey.Required), survey.WithValidator(validateOciUserEmail)}, - Checks: []*bool{configEnabled(config)}, - }); err != nil { - return err - } - - // Validate that config was enabled. Otherwise throw error. - if !config.Config { - return errors.New("must enable configuration integration to continue") - } - - // Find out if the customer wants to specify more advanced features - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Confirm{Message: QuestionOciConfigAdvanced, Default: extraState.AskAdvanced}, - Response: &extraState.AskAdvanced, - }); err != nil { - return err - } - - // Keep prompting for advanced options until the say done - if extraState.AskAdvanced { - if err := askAdvancedOciOptions(config, extraState); err != nil { - return err - } - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/grpc.go b/vendor/github.com/lacework/go-sdk/cli/cmd/grpc.go deleted file mode 100644 index 4a2ead21d..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/grpc.go +++ /dev/null @@ -1,50 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2024, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strings" - - "github.com/spf13/cobra" -) - -var ( - // grpcCmd is a hidden command that developers use to debug CDK components - grpcCmd = &cobra.Command{ - Use: "grpc", - Hidden: true, - Short: "Starts a CDK gRPC server (developer mode)", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - cli.OutputHuman("\nDevelopment mode for CDK components") - cli.OutputHuman("\n===================================\n\n") - cli.OutputHuman("When debugging a component, it might expect some environment variables and a\n") - cli.OutputHuman("running gRPC server, this command starts the CDK server and shows the variables\n") - cli.OutputHuman("that your component might need:\n\n") - vars := cli.envs() - cli.OutputHuman("export %s", strings.Join(vars, " \\\n ")) - cli.OutputHuman("\n\n'Ctrl+c' to stop the server. ") - return cli.Serve() - }, - } -) - -func init() { - rootCmd.AddCommand(grpcCmd) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/honeyvent.go b/vendor/github.com/lacework/go-sdk/cli/cmd/honeyvent.go deleted file mode 100644 index 407efcb7a..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/honeyvent.go +++ /dev/null @@ -1,225 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "os" - "regexp" - "runtime" - "strings" - "sync" - - "github.com/honeycombio/libhoney-go" - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/lwdomain" -) - -var ( - // HoneyDataset is the dataset in Honeycomb that we send tracing - // data this variable will be set depending on the environment we - // are running on. During development, we send all events and - // tracing data to a default dataset. - HoneyDataset = "lacework-cli-dev" -) - -const ( - // DisableTelemetry is an environment variable that can be used to - // disable telemetry sent to Honeycomb - DisableTelemetry = "LW_TELEMETRY_DISABLE" - - // HomebrewInstall is an environment variable that denotes the - // install method was via homebrew package manager - HomebrewInstall = "LW_HOMEBREW_INSTALL" - - // ChocolateyInstall is an environment variable that denotes the - // install method was via chocolatey package manager - ChocolateyInstall = "LW_CHOCOLATEY_INSTALL" - - // List of Features - // - // A feature within the Lacework CLI is any functionality that - // can't be traced or tracked by the default event sent to Honeycomb, - // it is a behavior that we, Lacework engineers, would like to - // trace and understand its usage and adoption. - // - // By default the Feature field within the Honeyvent is empty, - // define a new feature below and set it before sending a new - // Honeyvent. Additionally, there is a FeatureData field that - // any feature can use to inject any specific information - // related to that feature. - // - // Example: - // - // ```go - // cli.Event.Feature = featPollCtrScan - // cli.Event.AddFeatureField("key", "value") - // cli.SendHoneyvent() - // ``` - // - // Polling mechanism feature - featPollCtrScan = "poll_ctr_scan" - - // Daily version check feature - featDailyVerCheck = "daily_check" - - // Daily Component version check feature - featDailyCompVerCheck = "daily_comp_check" - - // Generate package manifest feature - featGenPkgManifest = "gen_pkg_manifest" - - // Split package manifest feature - featSplitPkgManifest = "split_pkg_manifest" - - // Migration API v1 -> v2 feature - featMigrateConfigV2 = "migrate_config_v2" -) - -// InitHoneyvent initialize honeycomb library and main Honeyvent, such event -// could be modified during a command execution to add extra parameters such -// as error message, feature data, etc. -func (c *cliState) InitHoneyvent() { - c.Event = &api.Honeyvent{ - Os: runtime.GOOS, - Arch: runtime.GOARCH, - Version: Version, - Profile: c.Profile, - Account: c.Account, - Subaccount: c.Subaccount, - ApiKey: c.KeyID, - CfgVersion: c.CfgVersion, - TraceID: newID(), - InstallMethod: installMethod(), - Dataset: HoneyDataset, - } -} - -// Wait should be called before finishing the execution of any CLI command, -// it waits for pending workers (a.k.a. honeyvents) to be transmitted -func (c *cliState) Wait() { - // wait for any missing worker - c.workers.Wait() - - // flush any pending calls to Honeycomb - libhoney.Close() - - // stop gRPC server gracefully - c.Stop() -} - -// SendHoneyvent is used throughout the CLI to send Honeyvents, these events -// have tracing data to understand how the commands are being executed, what -// features are used and the overall command flow. This function sends the -// events via goroutines so that we don't block the execution of the main process -// -// NOTE: the CLI will send at least one event per command execution -func (c *cliState) SendHoneyvent() { - if disabled := os.Getenv(DisableTelemetry); disabled != "" { - return - } - - if c.LwApi == nil { - c.Log.Debug("unable to send honeyvent", "error") - return - } - - if c.Event.SpanID == "" { - // root span of a trace which is defined by having its parent_id omitted - c.Event.SpanID = c.id - } else { - // parent_id is set always to the root span since this is a command-line - c.Event.ParentID = c.id - c.Event.SpanID = newID() - } - - if c.Event.ContextID == "" { - c.Event.ContextID = os.Getenv("LACEWORK_CONTEXT_ID") - } - - // Lacework accounts are NOT case-sensitive but some users configure them - // in uppercase and others in lowercase, therefore we will normalize all - // account to be lowercase so that we don't see different accounts in - // Honeycomb. - c.Event.Account = strings.ToLower(c.Event.Account) - - // Detect if the account has the full domain, if so, subtract the account - if match, _ := regexp.MatchString(".lacework.net", c.Account); match { - d, err := lwdomain.New(c.Account) - if err == nil { - c.Event.Account = strings.ToLower(d.String()) - } - } - - c.Log.Debugw("new honeyvent", "dataset", HoneyDataset, - "trace_id", c.Event.TraceID, - "span_id", c.Event.SpanID, - "parent_id", c.Event.ParentID, - "context_id", c.Event.ContextID, - ) - honeyvent := libhoney.NewEvent() - _ = honeyvent.Add(c.Event) - honeycombEvent := *c.Event - honeycombEvent.Dataset = c.Event.Dataset - - c.workers.Add(1) - go func(wg *sync.WaitGroup, event *libhoney.Event, honeycombEvent api.Honeyvent) { - defer wg.Done() - - c.Log.Debugw("sending honeyvent", "dataset", HoneyDataset) - - _, err := c.LwApi.V2.Metrics.Send(honeycombEvent) - if err != nil { - c.Log.Debugw("unable to send honeyvent", "error", err) - } - - }(&c.workers, honeyvent, honeycombEvent) - - // after adding a worker to submit a honeyvent, we remove - // all temporal fields such as feature, feature.data, error - c.Event.DurationMs = 0 - c.Event.Error = "" - c.Event.Feature = "" - c.Event.FeatureData = nil -} - -func installMethod() string { - if os.Getenv(HomebrewInstall) != "" { - return "HOMEBREW" - } - - if os.Getenv(ChocolateyInstall) != "" { - return "CHOCOLATEY" - } - return "" -} - -// parseFlags is a helper used to parse all the flags that the user provided -func parseFlags(args []string) (flags []string) { - for len(args) > 0 { - arg := args[0] - args = args[1:] - if len(arg) <= 1 || arg[0] != '-' { - // not a flag - continue - } - - flags = append(flags, strings.Split(arg, "=")[0]) - } - return -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws.go deleted file mode 100644 index 8abde774c..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws.go +++ /dev/null @@ -1,168 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createAwsConfigIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "role_arn", - Prompt: &survey.Input{Message: "Role ARN: "}, - Validate: survey.Required, - }, - { - Name: "external_id", - Prompt: &survey.Input{Message: "External ID: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - RoleArn string `survey:"role_arn"` - ExternalID string `survey:"external_id"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - awsCfg := api.NewCloudAccount(answers.Name, - api.AwsCfgCloudAccount, - api.AwsCfgData{ - Credentials: api.AwsCfgCredentials{ - RoleArn: answers.RoleArn, - ExternalID: answers.ExternalID, - }, - }, - ) - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(awsCfg) - cli.StopProgress() - return err -} - -func createAwsCloudTrailIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "role_arn", - Prompt: &survey.Input{Message: "Role ARN:"}, - Validate: survey.Required, - }, - { - Name: "external_id", - Prompt: &survey.Input{Message: "External ID:"}, - Validate: survey.Required, - }, - { - Name: "queue_url", - Prompt: &survey.Input{Message: "SQS Queue URL:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - RoleArn string `survey:"role_arn"` - ExternalID string `survey:"external_id"` - QueueUrl string `survey:"queue_url"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - awsCtSqsData := api.AwsCtSqsData{ - QueueUrl: answers.QueueUrl, - Credentials: api.AwsCtSqsCredentials{ - RoleArn: answers.RoleArn, - ExternalID: answers.ExternalID, - }, - } - // ask the user if they would like to configure an Account Mapping - mapping := false - err = survey.AskOne(&survey.Confirm{ - Message: "Configure an Account Mapping File? (org admins only)", - }, &mapping) - - if err != nil { - return err - } - - if mapping { - var content string - - err = survey.AskOne(&survey.Editor{ - Message: "Provide the Account Mapping File in JSON format", - FileName: "*.json", - }, &content) - - if err != nil { - return err - } - - awsCtSqsData.EncodeAccountMappingFile([]byte(content)) - } - - awsCtSqs := api.NewCloudAccount(answers.Name, api.AwsCtSqsCloudAccount, awsCtSqsData) - - cli.StartProgress(" Creating integration...") - defer cli.StopProgress() - - // if the user didn't provide an account mapping file, - // we just create the integration with a regular request - if !mapping { - _, err = cli.LwApi.V2.CloudAccounts.Create(awsCtSqs) - return err - } - - // but if it did provide one, then we need to elevate - // the user to the Organization level because Account - // Mappings are only allowed at that level, so we - // copy the client to make it an org client - orgLwClient, err := api.CopyClient(cli.LwApi, - api.WithOrgAccess(), - ) - if err != nil { - return err - } - _, err = orgLwClient.V2.CloudAccounts.Create(awsCtSqs) - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_cloudwatch.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_cloudwatch.go deleted file mode 100644 index 114932c32..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_cloudwatch.go +++ /dev/null @@ -1,64 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createAwsCloudWatchAlertChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "arn", - Prompt: &survey.Input{Message: "Event Bus ARN: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Arn string - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - cloudwatch := api.NewAlertChannel(answers.Name, - api.CloudwatchEbAlertChannelType, - api.CloudwatchEbDataV2{ - EventBusArn: answers.Arn, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(cloudwatch) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_govcloud.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_govcloud.go deleted file mode 100644 index 82dda964f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_govcloud.go +++ /dev/null @@ -1,140 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createAwsGovCloudConfigIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "account_id", - Prompt: &survey.Input{Message: "AWS Account ID: "}, - Validate: survey.Required, - }, - { - Name: "access_key_id", - Prompt: &survey.Input{Message: "Access Key ID: "}, - Validate: survey.Required, - }, - { - Name: "secret_access_key", - Prompt: &survey.Password{Message: "Secret Access Key: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - AccountID string `survey:"account_id"` - AccessKeyID string `survey:"access_key_id"` - SecretAccessKey string `survey:"secret_access_key"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - awsCfg := api.NewCloudAccount(answers.Name, - api.AwsUsGovCfgCloudAccount, - api.AwsUsGovCfgData{ - Credentials: api.AwsUsGovCfgCredentials{ - AwsAccountID: answers.AccountID, - AccessKeyID: answers.AccessKeyID, - SecretAccessKey: answers.SecretAccessKey, - }, - }, - ) - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(awsCfg) - cli.StopProgress() - return err -} - -func createAwsGovCloudCTIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "account_id", - Prompt: &survey.Input{Message: "AWS Account ID: "}, - Validate: survey.Required, - }, - { - Name: "access_key_id", - Prompt: &survey.Input{Message: "Access Key ID: "}, - Validate: survey.Required, - }, - { - Name: "secret_access_key", - Prompt: &survey.Password{Message: "Secret Access Key: "}, - Validate: survey.Required, - }, - { - Name: "queue_url", - Prompt: &survey.Input{Message: "SQS Queue URL:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - AccountID string `survey:"account_id"` - AccessKeyID string `survey:"access_key_id"` - SecretAccessKey string `survey:"secret_access_key"` - QueueUrl string `survey:"queue_url"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - awsCfg := api.NewCloudAccount(answers.Name, - api.AwsUsGovCtSqsCloudAccount, - api.AwsUsGovCtSqsData{ - QueueUrl: answers.QueueUrl, - Credentials: api.AwsUsGovCtSqsCredentials{ - AwsAccountID: answers.AccountID, - AccessKeyID: answers.AccessKeyID, - SecretAccessKey: answers.SecretAccessKey, - }, - }, - ) - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(awsCfg) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_s3_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_s3_channel.go deleted file mode 100644 index cc7585cf4..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_aws_s3_channel.go +++ /dev/null @@ -1,80 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createAwsS3ChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "role_arn", - Prompt: &survey.Input{Message: "Role ARN:"}, - Validate: survey.Required, - }, - { - Name: "bucket_arn", - Prompt: &survey.Input{Message: "Bucket ARN:"}, - Validate: survey.Required, - }, - { - Name: "external_id", - Prompt: &survey.Input{Message: "External ID:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - RoleArn string `survey:"role_arn"` - BucketArn string `survey:"bucket_arn"` - ExternalID string `survey:"external_id"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - s3 := api.NewAlertChannel(answers.Name, - api.AwsS3AlertChannelType, - api.AwsS3DataV2{ - Credentials: api.AwsS3Credentials{ - RoleArn: answers.RoleArn, - BucketArn: answers.BucketArn, - ExternalID: answers.ExternalID, - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(s3) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_azure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_azure.go deleted file mode 100644 index 6fa67c40b..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_azure.go +++ /dev/null @@ -1,211 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createAzureConfigIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "client_id", - Prompt: &survey.Input{Message: "Client ID:"}, - Validate: survey.Required, - }, - { - Name: "client_secret", - Prompt: &survey.Input{Message: "Client Secret:"}, - Validate: survey.Required, - }, - { - Name: "tenant_id", - Prompt: &survey.Input{Message: "Tenant ID:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - ClientID string `survey:"client_id"` - ClientSecret string `survey:"client_secret"` - TenantID string `survey:"tenant_id"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - azure := api.NewCloudAccount(answers.Name, - api.AzureCfgCloudAccount, - api.AzureCfgData{ - TenantID: answers.TenantID, - Credentials: api.AzureCfgCredentials{ - ClientID: answers.ClientID, - ClientSecret: answers.ClientSecret, - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(azure) - cli.StopProgress() - return err -} - -func createAzureActivityLogIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "client_id", - Prompt: &survey.Input{Message: "Client ID:"}, - Validate: survey.Required, - }, - { - Name: "client_secret", - Prompt: &survey.Input{Message: "Client Secret:"}, - Validate: survey.Required, - }, - { - Name: "tenant_id", - Prompt: &survey.Input{Message: "Tenant ID:"}, - Validate: survey.Required, - }, - { - Name: "queue_url", - Prompt: &survey.Input{Message: "Queue URL:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - ClientID string `survey:"client_id"` - ClientSecret string `survey:"client_secret"` - TenantID string `survey:"tenant_id"` - QueueUrl string `survey:"queue_url"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - azure := api.NewCloudAccount(answers.Name, - api.AzureAlSeqCloudAccount, - api.AzureAlSeqData{ - QueueUrl: answers.QueueUrl, - TenantID: answers.TenantID, - Credentials: api.AzureAlSeqCredentials{ - ClientID: answers.ClientID, - ClientSecret: answers.ClientSecret, - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(azure) - cli.StopProgress() - return err -} - -func createAzureAdAlIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "client_id", - Prompt: &survey.Input{Message: "Client ID:"}, - Validate: survey.Required, - }, - { - Name: "client_secret", - Prompt: &survey.Input{Message: "Client Secret:"}, - Validate: survey.Required, - }, - { - Name: "tenant_id", - Prompt: &survey.Input{Message: "Tenant ID:"}, - Validate: survey.Required, - }, - { - Name: "event_hub_namespace", - Prompt: &survey.Input{Message: "Event Hub Fully Qualified Namespace:"}, - Validate: survey.Required, - }, - { - Name: "event_hub_name", - Prompt: &survey.Input{Message: "Event Hub Name:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - ClientID string `survey:"client_id"` - ClientSecret string `survey:"client_secret"` - TenantID string `survey:"tenant_id"` - EventHubNamespace string `survey:"event_hub_namespace"` - EventHubName string `survey:"event_hub_name"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - azure := api.NewCloudAccount(answers.Name, - api.AzureAdAlCloudAccount, - api.AzureAdAlData{ - TenantID: answers.TenantID, - EventHubNamespace: answers.EventHubNamespace, - EventHubName: answers.EventHubName, - Credentials: api.AzureAdAlCredentials{ - ClientID: answers.ClientID, - ClientSecret: answers.ClientSecret, - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(azure) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_cisco_webex.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_cisco_webex.go deleted file mode 100644 index 36759f6cc..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_cisco_webex.go +++ /dev/null @@ -1,64 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createCiscoWebexChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "url", - Prompt: &survey.Input{Message: "Webhook URL: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Url string - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - webex := api.NewAlertChannel(answers.Name, - api.CiscoSparkWebhookAlertChannelType, - api.CiscoSparkWebhookDataV2{ - Webhook: answers.Url, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(webex) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ctr_reg_limits.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ctr_reg_limits.go deleted file mode 100644 index 1fc78862e..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ctr_reg_limits.go +++ /dev/null @@ -1,125 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strings" - - "github.com/AlecAivazis/survey/v2" -) - -func castStringToLimitByLabel(labels string) []map[string]string { - out := make([]map[string]string, 0) - - for _, label := range strings.Split(labels, "\n") { - kv := strings.Split(label, ":") - if len(kv) != 2 { - cli.Log.Warnw("malformed limit_by_label entry, ignoring", - "label", label, - "expected_format", "key:value", - ) - continue - } - out = append(out, map[string]string{kv[0]: kv[1]}) - } - - return out -} - -func askV2LimitByTags(answers interface{}) error { - custom := false - if err := survey.AskOne(&survey.Confirm{ - Message: "Configure limit of scans by tags?", - }, &custom); err != nil { - return err - } - - if custom { - questions := []*survey.Question{{ - Name: "limit_tags", - Prompt: &survey.Multiline{Message: "List of tags to scan:"}, - }} - - if err := survey.Ask(questions, answers, - survey.WithIcons(promptIconsFunc), - ); err != nil { - return err - } - } - - return nil -} - -func askV2LimitByLabels(answers interface{}) error { - custom := false - if err := survey.AskOne(&survey.Confirm{ - Message: "Configure limit of scans by labels?", - }, &custom); err != nil { - return err - } - - if custom { - questions := []*survey.Question{{ - Name: "limit_labels", - Prompt: &survey.Multiline{Message: "List of 'key:value' labels to scan:"}, - }} - - if err := survey.Ask(questions, answers, - survey.WithIcons(promptIconsFunc), - ); err != nil { - return err - } - } - - return nil -} - -func askV2LimitByRepositories(answers interface{}) error { - custom := false - if err := survey.AskOne(&survey.Confirm{ - Message: "Configure limit of scans by repositories?", - }, &custom); err != nil { - return err - } - - if custom { - questions := []*survey.Question{{ - Name: "limit_repos", - Prompt: &survey.Multiline{Message: "List of repositories to scan:"}, - }} - - if err := survey.Ask(questions, answers, - survey.WithIcons(promptIconsFunc), - ); err != nil { - return err - } - } - - return nil -} - -func askV2Limits(answers interface{}) error { - if err := askV2LimitByTags(answers); err != nil { - return err - } - if err := askV2LimitByLabels(answers); err != nil { - return err - } - return askV2LimitByRepositories(answers) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_datadog.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_datadog.go deleted file mode 100644 index 7105f3867..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_datadog.go +++ /dev/null @@ -1,96 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createDatadogIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "datadog_site", - Prompt: &survey.Select{Message: "Datadog Site: ", - Options: []string{string(api.DatadogSiteEu), string(api.DatadogSiteCom)}, - Default: string(api.DatadogSiteCom), - }, - }, - { - Name: "datadog_type", - Prompt: &survey.Select{Message: "Datadog Type: ", - Options: []string{ - string(api.DatadogServiceLogsDetails), - string(api.DatadogServiceEventsSummary), - string(api.DatadogServiceLogsSummary), - }, - Default: string(api.DatadogServiceLogsDetails), - }, - }, - { - Name: "api_key", - Prompt: &survey.Input{Message: "Api Key: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - DatadogSite string `survey:"datadog_site"` - DatadogService string `survey:"datadog_type"` - ApiKey string `survey:"api_key"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - site, err := api.DatadogSite(answers.DatadogSite) - if err != nil { - return err - } - - service, err := api.DatadogService(answers.DatadogService) - if err != nil { - return err - } - - datadog := api.NewAlertChannel(answers.Name, - api.DatadogAlertChannelType, - api.DatadogDataV2{ - DatadogSite: site, - DatadogType: service, - ApiKey: answers.ApiKey, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(datadog) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_hub.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_hub.go deleted file mode 100644 index b927386a1..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_hub.go +++ /dev/null @@ -1,128 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createDockerHubIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "username", - Prompt: &survey.Input{Message: "Username: "}, - Validate: survey.Required, - }, - { - Name: "password", - Prompt: &survey.Password{Message: "Password: "}, - Validate: survey.Required, - }, - { - Name: "non_os_package_support", - Prompt: &survey.Confirm{ - Message: "Enable scanning for Non-OS packages: "}, - }, - { - Name: "limit_tag", - Prompt: &survey.Input{ - Message: "Limit by Tag: ", - Default: "*", - }, - }, - { - Name: "limit_label", - Prompt: &survey.Input{ - Message: "Limit by Label: ", - Default: "*", - }, - }, - { - Name: "limit_repos", - Prompt: &survey.Input{Message: "Limit by Repository: "}, - }, - { - Name: "limit_max_images", - Prompt: &survey.Select{ - Message: "Limit Number of Images per Repo: ", - Options: []string{"5", "10", "15"}, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Username string - Password string - LimitTag string `survey:"limit_tag"` - LimitLabel string `survey:"limit_label"` - LimitRepos string `survey:"limit_repos"` - LimitMaxImages string `survey:"limit_max_images"` - NonOSPackageSupport bool `survey:"non_os_package_support"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) - if err != nil { - cli.Log.Warnw("unable to convert limit_max_images, using default", - "error", err, - "input", answers.LimitMaxImages, - "default", "5", - ) - limitMaxImages = 5 - } - - docker := api.NewContainerRegistry(answers.Name, - api.DockerhubContainerRegistry, - api.DockerhubData{ - Credentials: api.DockerhubCredentials{ - Username: answers.Username, - Password: answers.Password, - }, - NonOSPackageEval: answers.NonOSPackageSupport, - - LimitByTag: strings.Split(answers.LimitTag, "\n"), - LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), - LimitByRep: strings.Split(answers.LimitRepos, "\n"), - LimitNumImg: limitMaxImages, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.ContainerRegistries.Create(docker) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_v2.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_v2.go deleted file mode 100644 index ba5ba8259..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_docker_v2.go +++ /dev/null @@ -1,112 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createDockerV2Integration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "domain", - Prompt: &survey.Input{Message: "Registry Domain: "}, - Validate: survey.Required, - }, - { - Name: "username", - Prompt: &survey.Input{Message: "Username: "}, - Validate: survey.Required, - }, - { - Name: "password", - Prompt: &survey.Password{Message: "Password: "}, - Validate: survey.Required, - }, - { - Name: "ssl", - Prompt: &survey.Confirm{Message: "Enable SSL?"}, - }, - { - Name: "non_os_package_support", - Prompt: &survey.Confirm{Message: "Enable scanning for Non-OS packages: "}, - }, - { - Name: "limit_tag", - Prompt: &survey.Input{ - Message: "Limit by Tag: ", - Default: "*", - }, - }, - { - Name: "limit_label", - Prompt: &survey.Input{ - Message: "Limit by Label: ", - Default: "*", - }, - }, - } - - answers := struct { - Name string - Domain string - Username string - Password string - SSL bool - NonOSPackageSupport bool `survey:"non_os_package_support"` - LimitTag string `survey:"limit_tag"` - LimitLabel string `survey:"limit_label"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - docker := api.NewContainerRegistry(answers.Name, - api.DockerhubV2ContainerRegistry, - api.DockerhubV2Data{ - Credentials: api.DockerhubV2Credentials{ - Username: answers.Username, - Password: answers.Password, - SSL: answers.SSL, - }, - RegistryDomain: answers.Domain, - NonOSPackageEval: answers.NonOSPackageSupport, - LimitByTag: strings.Split(answers.LimitTag, "\n"), - LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.ContainerRegistries.Create(docker) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ecr.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ecr.go deleted file mode 100644 index b6ce42ce3..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ecr.go +++ /dev/null @@ -1,212 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/pkg/errors" - - "github.com/lacework/go-sdk/api" -) - -func createAwsEcrIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "domain", - Prompt: &survey.Input{Message: "Registry Domain: "}, - Validate: survey.Required, - }, - { - Name: "non_os_package_support", - Prompt: &survey.Confirm{ - Message: "Enable scanning for Non-OS packages: "}, - }, - { - Name: "limit_tag", - Prompt: &survey.Input{ - Message: "Limit by Tag: ", - Default: "*", - }, - }, - { - Name: "limit_label", - Prompt: &survey.Input{ - Message: "Limit by Label: ", - Default: "*", - }, - }, - { - Name: "limit_repos", - Prompt: &survey.Input{Message: "Limit by Repository: "}, - }, - { - Name: "limit_max_images", - Prompt: &survey.Select{ - Message: "Limit Number of Images per Repo: ", - Options: []string{"5", "10", "15"}, - }, - Validate: survey.Required, - }, - { - Name: "aws_auth_type", - Prompt: &survey.Select{ - Message: "Authentication Type: ", - Options: []string{"AWS IAM Role", "AWS Access Key"}, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Domain string - AccessKeyID string `survey:"access_key_id"` - SecretAccessKey string `survey:"secret_access_key"` - LimitTag string `survey:"limit_tag"` - LimitLabel string `survey:"limit_label"` - LimitRepos string `survey:"limit_repos"` - LimitMaxImages string `survey:"limit_max_images"` - AwsAuthType string `survey:"aws_auth_type"` - NonOSPackageSupport bool `survey:"non_os_package_support"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) - if err != nil { - cli.Log.Warnw("unable to convert limit_max_images, using default", - "error", err, - "input", answers.LimitMaxImages, - "default", "5", - ) - limitMaxImages = 5 - } - - switch answers.AwsAuthType { - - case "AWS IAM Role": - ecrAuthAnswers := struct { - RoleArn string `survey:"role_arn"` - ExternalID string `survey:"external_id"` - }{} - - questionsAuth := []*survey.Question{ - { - Name: "role_arn", - Prompt: &survey.Input{Message: "Role ARN:"}, - Validate: survey.Required, - }, - { - Name: "external_id", - Prompt: &survey.Input{Message: "External ID:"}, - Validate: survey.Required, - }, - } - - err := survey.Ask(questionsAuth, &ecrAuthAnswers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - ecr := api.NewContainerRegistry(answers.Name, - api.AwsEcrContainerRegistry, - api.AwsEcrIamRoleData{ - CrossAccountCredentials: api.AwsEcrCrossAccountCredentials{ - RoleArn: ecrAuthAnswers.RoleArn, - ExternalID: ecrAuthAnswers.ExternalID, - }, - RegistryDomain: answers.Domain, - NonOSPackageEval: answers.NonOSPackageSupport, - LimitByTag: strings.Split(answers.LimitTag, "\n"), - LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), - LimitByRep: strings.Split(answers.LimitRepos, "\n"), - LimitNumImg: limitMaxImages, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.ContainerRegistries.Create(ecr) - cli.StopProgress() - return err - - case "AWS Access Key": - ecrAuthAnswers := struct { - AccessKeyID string `survey:"access_key_id"` - SecretAccessKey string `survey:"secret_access_key"` - }{} - - questionsAuth := []*survey.Question{ - { - Name: "access_key_id", - Prompt: &survey.Input{Message: "Access Key ID: "}, - Validate: survey.Required, - }, - { - Name: "secret_access_key", - Prompt: &survey.Password{Message: "Secret Access Key: "}, - Validate: survey.Required, - }, - } - - err := survey.Ask(questionsAuth, &ecrAuthAnswers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - ecr := api.NewContainerRegistry(answers.Name, - api.AwsEcrContainerRegistry, - api.AwsEcrAccessKeyData{ - AccessKeyCredentials: api.AwsEcrAccessKeyCredentials{ - AccessKeyID: ecrAuthAnswers.AccessKeyID, - SecretAccessKey: ecrAuthAnswers.SecretAccessKey, - }, - RegistryDomain: answers.Domain, - LimitByTag: strings.Split(answers.LimitTag, "\n"), - LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), - LimitByRep: strings.Split(answers.LimitRepos, "\n"), - LimitNumImg: limitMaxImages, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.ContainerRegistries.Create(ecr) - cli.StopProgress() - return err - - default: - return errors.New("unknown ECR authentication method") - } -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_email.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_email.go deleted file mode 100644 index ee2273c27..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_email.go +++ /dev/null @@ -1,68 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createEmailAlertChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "recipients", - Prompt: &survey.Multiline{Message: "List of Recipients: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Recipients string - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - emailAlertChan := api.NewAlertChannel(answers.Name, - api.EmailUserAlertChannelType, - api.EmailUserData{ - ChannelProps: api.EmailUserChannelProps{ - Recipients: strings.Split(answers.Recipients, "\n"), - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(emailAlertChan) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gar.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gar.go deleted file mode 100644 index 7ee1b93fb..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gar.go +++ /dev/null @@ -1,166 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createGarIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "domain", - Prompt: &survey.Select{ - Message: "Registry Domain:", - Options: []string{ - "northamerica-northeast1-docker.pkg.dev", - "us-central1-docker.pkg.dev", - "us-east1-docker.pkg.dev", - "us-east4-docker.pkg.dev", - "us-west1-docker.pkg.dev", - "us-west2-docker.pkg.dev", - "us-west3-docker.pkg.dev", - "us-west4-docker.pkg.dev", - "southamerica-east1-docker.pkg.dev", - "europe-north1-docker.pkg.dev", - "europe-west1-docker.pkg.dev", - "europe-west2-docker.pkg.dev", - "europe-west3-docker.pkg.dev", - "europe-west4-docker.pkg.dev", - "europe-west6-docker.pkg.dev", - "asia-east1-docker.pkg.dev", - "asia-east2-docker.pkg.dev", - "asia-northeast1-docker.pkg.dev", - "asia-northeast2-docker.pkg.dev", - "asia-northeast3-docker.pkg.dev", - "asia-south1-docker.pkg.dev", - "asia-southeast1-docker.pkg.dev", - "asia-southeast2-docker.pkg.dev", - "australia-southeast1-docker.pkg.dev", - "asia-docker.pkg.dev", - "europe-docker.pkg.dev", - "us-docker.pkg.dev", - }, - Default: "us-west1-docker.pkg.dev", - }, - Validate: survey.Required, - }, - { - Name: "client_id", - Prompt: &survey.Input{Message: "Client ID:"}, - Validate: survey.Required, - }, - { - Name: "private_key_id", - Prompt: &survey.Input{Message: "Private Key ID:"}, - Validate: survey.Required, - }, - { - Name: "client_email", - Prompt: &survey.Input{Message: "Client Email:"}, - Validate: survey.Required, - }, - { - Name: "private_key", - Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, - Validate: survey.Required, - }, - { - Name: "non_os_package_support", - Prompt: &survey.Confirm{ - Message: "Enable scanning for Non-OS packages: "}, - }, - { - Name: "limit_max_images", - Prompt: &survey.Select{ - Message: "Limit number of images per repository: ", - Options: []string{"5", "10", "15"}, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Domain string - ClientID string `survey:"client_id"` - PrivateKeyID string `survey:"private_key_id"` - ClientEmail string `survey:"client_email"` - PrivateKey string `survey:"private_key"` - NonOSPackageSupport bool `survey:"non_os_package_support"` - LimitTags string `survey:"limit_tags"` - LimitLabels string `survey:"limit_labels"` - LimitRepos string `survey:"limit_repos"` - LimitMaxImages string `survey:"limit_max_images"` - }{} - - if err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ); err != nil { - return err - } - - limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) - if err != nil { - cli.Log.Warnw("unable to convert limit_max_images, using default", - "error", err, - "input", answers.LimitMaxImages, - "default", "5", - ) - limitMaxImages = 5 - } - - // @afiune these are the new API v2 limits - if err := askV2Limits(&answers); err != nil { - return err - } - - gar := api.NewContainerRegistry(answers.Name, - api.GcpGarContainerRegistry, - api.GcpGarData{ - Credentials: api.GcpCredentialsV2{ - ClientEmail: answers.ClientEmail, - ClientID: answers.ClientID, - PrivateKey: answers.PrivateKey, - PrivateKeyID: answers.PrivateKeyID, - }, - RegistryDomain: answers.Domain, - NonOSPackageEval: answers.NonOSPackageSupport, - LimitByTag: strings.Split(answers.LimitTags, "\n"), - LimitByLabel: castStringToLimitByLabel(answers.LimitLabels), - LimitByRep: strings.Split(answers.LimitRepos, "\n"), - LimitNumImg: limitMaxImages, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.ContainerRegistries.Create(gar) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp.go deleted file mode 100644 index 4936f84f3..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp.go +++ /dev/null @@ -1,196 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createGcpConfigIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "client_id", - Prompt: &survey.Input{Message: "Client ID:"}, - Validate: survey.Required, - }, - { - Name: "private_key_id", - Prompt: &survey.Input{Message: "Private Key ID:"}, - Validate: survey.Required, - }, - { - Name: "client_email", - Prompt: &survey.Input{Message: "Client Email:"}, - Validate: survey.Required, - }, - { - Name: "private_key", - Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, - Validate: survey.Required, - }, - { - Name: "integration_level", - Prompt: &survey.Select{ - Message: "Integration Level:", - Options: []string{ - api.GcpProjectIntegration.String(), - api.GcpOrganizationIntegration.String(), - }, - }, - Validate: survey.Required, - }, - { - Name: "org_project_id", - Prompt: &survey.Input{Message: "Organization/Project ID:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - ClientID string `survey:"client_id"` - PrivateKeyID string `survey:"private_key_id"` - ClientEmail string `survey:"client_email"` - PrivateKey string `survey:"private_key"` - IntegrationLevel string `survey:"integration_level"` - OrgProjectID string `survey:"org_project_id"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - gcp := api.NewCloudAccount(answers.Name, - api.GcpCfgCloudAccount, - api.GcpCfgData{ - ID: answers.OrgProjectID, - IDType: answers.IntegrationLevel, - Credentials: api.GcpCfgCredentials{ - ClientID: answers.ClientID, - ClientEmail: answers.ClientEmail, - PrivateKeyID: answers.PrivateKeyID, - PrivateKey: answers.PrivateKey, - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(gcp) - cli.StopProgress() - return err -} - -func createGcpAuditLogIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "client_id", - Prompt: &survey.Input{Message: "Client ID:"}, - Validate: survey.Required, - }, - { - Name: "private_key_id", - Prompt: &survey.Input{Message: "Private Key ID:"}, - Validate: survey.Required, - }, - { - Name: "client_email", - Prompt: &survey.Input{Message: "Client Email:"}, - Validate: survey.Required, - }, - { - Name: "private_key", - Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, - Validate: survey.Required, - }, - { - Name: "integration_level", - Prompt: &survey.Select{ - Message: "Integration Level:", - Options: []string{ - api.GcpProjectIntegration.String(), - api.GcpOrganizationIntegration.String(), - }, - }, - Validate: survey.Required, - }, - { - Name: "org_project_id", - Prompt: &survey.Input{Message: "Organization/Project ID:"}, - Validate: survey.Required, - }, - { - Name: "subscription_name", - Prompt: &survey.Input{Message: "Subscription Name:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - ClientID string `survey:"client_id"` - PrivateKeyID string `survey:"private_key_id"` - ClientEmail string `survey:"client_email"` - PrivateKey string `survey:"private_key"` - IntegrationLevel string `survey:"integration_level"` - OrgProjectID string `survey:"org_project_id"` - SubscriptionName string `survey:"subscription_name"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - gcp := api.NewCloudAccount(answers.Name, - api.GcpAtSesCloudAccount, - api.GcpAtSesData{ - ID: answers.OrgProjectID, - IDType: answers.IntegrationLevel, - SubscriptionName: answers.SubscriptionName, - Credentials: api.GcpAtSesCredentials{ - ClientID: answers.ClientID, - ClientEmail: answers.ClientEmail, - PrivateKeyID: answers.PrivateKeyID, - PrivateKey: answers.PrivateKey, - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(gcp) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_audit.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_audit.go deleted file mode 100644 index 49414aade..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_audit.go +++ /dev/null @@ -1,121 +0,0 @@ -// -// Author:: David McTavish () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createGcpPubSubAuditLogIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "client_id", - Prompt: &survey.Input{Message: "Client ID:"}, - Validate: survey.Required, - }, - { - Name: "private_key_id", - Prompt: &survey.Input{Message: "Private Key ID:"}, - Validate: survey.Required, - }, - { - Name: "client_email", - Prompt: &survey.Input{Message: "Client Email:"}, - Validate: survey.Required, - }, - { - Name: "private_key", - Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, - Validate: survey.Required, - }, - { - Name: "integration_level", - Prompt: &survey.Select{ - Message: "Integration Level:", - Options: []string{ - api.GcpProjectIntegration.String(), - api.GcpOrganizationIntegration.String(), - }, - }, - Validate: survey.Required, - }, - { - Name: "org_project_id", - Prompt: &survey.Input{Message: "Organization/Project ID:"}, - Validate: survey.Required, - }, - { - Name: "subscription_id", - Prompt: &survey.Input{Message: "Subscription ID:"}, - Validate: survey.Required, - }, - { - Name: "topic_id", - Prompt: &survey.Input{Message: "Topic ID:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - ClientID string `survey:"client_id"` - PrivateKeyID string `survey:"private_key_id"` - ClientEmail string `survey:"client_email"` - PrivateKey string `survey:"private_key"` - IntegrationLevel string `survey:"integration_level"` - OrgProjectID string `survey:"org_project_id"` - SubscriptionName string `survey:"subscription_id"` - TopicID string `survey:"topic_id"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - gcp := api.NewCloudAccount(answers.Name, - api.GcpAlPubSubCloudAccount, - api.GcpAlPubSubSesData{ - ProjectID: answers.OrgProjectID, - IntegrationType: answers.IntegrationLevel, - SubscriptionName: answers.SubscriptionName, - TopicID: answers.TopicID, - Credentials: api.GcpAlPubSubCredentials{ - ClientID: answers.ClientID, - ClientEmail: answers.ClientEmail, - PrivateKeyID: answers.PrivateKeyID, - PrivateKey: answers.PrivateKey, - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(gcp) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_channel.go deleted file mode 100644 index 4936407e5..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcp_pub_sub_channel.go +++ /dev/null @@ -1,109 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createGcpPubSubChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "topic_id", - Prompt: &survey.Input{Message: "Topic ID:"}, - Validate: survey.Required, - }, - { - Name: "project_id", - Prompt: &survey.Input{Message: "Project ID:"}, - Validate: survey.Required, - }, - { - Name: "client_id", - Prompt: &survey.Input{Message: "Client ID:"}, - Validate: survey.Required, - }, - { - Name: "client_email", - Prompt: &survey.Input{Message: "Client Email:"}, - Validate: survey.Required, - }, - { - Name: "private_key_id", - Prompt: &survey.Input{Message: "Private Key ID:"}, - Validate: survey.Required, - }, - { - Name: "private_key", - Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, - Validate: survey.Required, - }, - { - Name: "issue_grouping", - Prompt: &survey.Select{Message: "Issue Grouping:", - Options: []string{"Events", "Resources"}, - }, - }, - } - - answers := struct { - Name string - ClientID string `survey:"client_id"` - PrivateKeyID string `survey:"private_key_id"` - ClientEmail string `survey:"client_email"` - PrivateKey string `survey:"private_key"` - ProjectID string `survey:"project_id"` - TopicID string `survey:"topic_id"` - IssueGrouping string `survey:"issue_grouping"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - gcp := api.NewAlertChannel(answers.Name, - api.GcpPubSubAlertChannelType, - api.GcpPubSubDataV2{ - ProjectID: answers.ProjectID, - TopicID: answers.TopicID, - IssueGrouping: answers.IssueGrouping, - Credentials: api.GcpPubSubCredentials{ - ClientID: answers.ClientID, - ClientEmail: answers.ClientEmail, - PrivateKeyID: answers.PrivateKeyID, - PrivateKey: answers.PrivateKey, - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(gcp) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcr.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcr.go deleted file mode 100644 index d412f42cc..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_gcr.go +++ /dev/null @@ -1,156 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createGcrIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "domain", - Prompt: &survey.Select{ - Message: "Registry Domain:", - Options: []string{ - "gcr.io", - "us.gcr.io", - "eu.gcr.io", - "asia.gcr.io", - }, - }, - Validate: survey.Required, - }, - { - Name: "client_id", - Prompt: &survey.Input{Message: "Client ID:"}, - Validate: survey.Required, - }, - { - Name: "private_key_id", - Prompt: &survey.Input{Message: "Private Key ID:"}, - Validate: survey.Required, - }, - { - Name: "client_email", - Prompt: &survey.Input{Message: "Client Email:"}, - Validate: survey.Required, - }, - { - Name: "private_key", - Prompt: &survey.Editor{Message: "Enter properly formatted Private Key:"}, - Validate: survey.Required, - }, - { - Name: "non_os_package_support", - Prompt: &survey.Confirm{ - Message: "Enable scanning for Non-OS packages: "}, - }, - { - Name: "limit_tag", - Prompt: &survey.Input{ - Message: "Limit by Tag: ", - Default: "*", - }, - }, - { - Name: "limit_label", - Prompt: &survey.Input{ - Message: "Limit by Label: ", - Default: "*", - }, - }, - { - Name: "limit_repos", - Prompt: &survey.Input{Message: "Limit by Repository: "}, - }, - { - Name: "limit_max_images", - Prompt: &survey.Select{ - Message: "Limit Number of Images per Repo: ", - Options: []string{"5", "10", "15"}, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Domain string - ClientID string `survey:"client_id"` - PrivateKeyID string `survey:"private_key_id"` - ClientEmail string `survey:"client_email"` - PrivateKey string `survey:"private_key"` - NonOSPackageSupport bool `survey:"non_os_package_support"` - LimitTag string `survey:"limit_tag"` - LimitLabel string `survey:"limit_label"` - LimitRepos string `survey:"limit_repos"` - LimitMaxImages string `survey:"limit_max_images"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) - if err != nil { - cli.Log.Warnw("unable to convert limit_max_images, using default", - "error", err, - "input", answers.LimitMaxImages, - "default", "5", - ) - limitMaxImages = 5 - } - - gcr := api.NewContainerRegistry(answers.Name, - api.GcpGcrContainerRegistry, - api.GcpGcrData{ - Credentials: api.GcpCredentialsV2{ - ClientEmail: answers.ClientEmail, - ClientID: answers.ClientID, - PrivateKey: answers.PrivateKey, - PrivateKeyID: answers.PrivateKeyID, - }, - RegistryDomain: answers.Domain, - NonOSPackageEval: answers.NonOSPackageSupport, - LimitByTag: strings.Split(answers.LimitTag, "\n"), - LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), - LimitByRep: strings.Split(answers.LimitRepos, "\n"), - LimitNumImg: limitMaxImages, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.ContainerRegistries.Create(gcr) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ghcr.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ghcr.go deleted file mode 100644 index ea48a5566..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_ghcr.go +++ /dev/null @@ -1,124 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createGhcrIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "username", - Prompt: &survey.Input{Message: "Username:"}, - Validate: survey.Required, - }, - { - Name: "password", - Prompt: &survey.Password{Message: "Password:"}, - Validate: survey.Required, - }, - { - Name: "ssl", - Prompt: &survey.Confirm{Message: "Enable SSL?"}, - }, - { - Name: "notifications", - Prompt: &survey.Confirm{Message: "Subscribe to Registry Notifications?"}, - }, - { - Name: "non_os_package_support", - Prompt: &survey.Confirm{ - Message: "Enable scanning for Non-OS packages: "}, - }, - { - Name: "limit_max_images", - Prompt: &survey.Select{ - Message: "Limit number of images per repository: ", - Options: []string{"5", "10", "15"}, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Username string - Password string - SSL bool - Notifications bool - NonOSPackageSupport bool `survey:"non_os_package_support"` - LimitTags string `survey:"limit_tags"` - LimitLabels string `survey:"limit_labels"` - LimitRepos string `survey:"limit_repos"` - LimitMaxImages string `survey:"limit_max_images"` - }{} - - if err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ); err != nil { - return err - } - - limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) - if err != nil { - cli.Log.Warnw("unable to convert limit_max_images, using default", - "error", err, - "input", answers.LimitMaxImages, - "default", "5", - ) - limitMaxImages = 5 - } - - // @afiune these are the new API v2 limits - if err := askV2Limits(&answers); err != nil { - return err - } - - ghcr := api.NewContainerRegistry(answers.Name, - api.GhcrContainerRegistry, - api.GhcrData{ - Credentials: api.GhcrCredentials{ - Username: answers.Username, - Password: answers.Password, - Ssl: answers.SSL, - }, - NonOSPackageEval: answers.NonOSPackageSupport, - LimitByTag: strings.Split(answers.LimitTags, "\n"), - LimitByLabel: castStringToLimitByLabel(answers.LimitLabels), - LimitByRep: strings.Split(answers.LimitRepos, "\n"), - LimitNumImg: limitMaxImages, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.ContainerRegistries.Create(ghcr) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_inline_scanner.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_inline_scanner.go deleted file mode 100644 index 7647a7d9d..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_inline_scanner.go +++ /dev/null @@ -1,93 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strconv" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createInlineScannerIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "identifier_tag", - Prompt: &survey.Multiline{ - Message: "List of 'key:value' tags:", - Default: "*", - }, - }, - { - Name: "limit_num_scan", - Prompt: &survey.Select{ - Message: "Limit number of scans: ", - Default: "60", - Options: []string{ - "5", "10", "15", - "20", "25", "30", - "35", "40", "45", - "50", "55", "60", - }, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - IdentifierTag string `survey:"identifier_tag"` - LimitNumScan string `survey:"limit_num_scan"` - }{} - - if err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ); err != nil { - return err - } - - limitNumScan, err := strconv.Atoi(answers.LimitNumScan) - if err != nil { - cli.Log.Warnw("unable to convert limit_num_scan, using default", - "error", err, - "input", answers.LimitNumScan, - "default", "60", - ) - limitNumScan = 60 - } - - inline := api.NewContainerRegistry(answers.Name, - api.InlineScannerContainerRegistry, - api.InlineScannerData{ - IdentifierTag: castStringToLimitByLabel(answers.IdentifierTag), - LimitNumScan: strconv.Itoa(limitNumScan), - }, - ) - - cli.StartProgress("Creating integration...") - _, err = cli.LwApi.V2.ContainerRegistries.Create(inline) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_jira.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_jira.go deleted file mode 100644 index d84c8326d..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_jira.go +++ /dev/null @@ -1,171 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "sort" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -type jiraAlertChannelIntegrationSurvey struct { - Name string - Url string - Issue string - Project string - Username string - Token string - Password string - Grouping string - Bidirectional bool -} - -func getJiraGroupingOptions() []string { - options := make([]string, 0, len(api.JiraIssueGroupingsSurvey)) - - for option := range api.JiraIssueGroupingsSurvey { - options = append(options, option) - } - - sort.SliceStable(options, func(i, j int) bool { - return api.JiraIssueGroupingsSurvey[options[i]] < api.JiraIssueGroupingsSurvey[options[j]] - }) - - return options -} - -func createJiraAlertChannelIntegration(jiraType string) error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "bidirectional", - Prompt: &survey.Confirm{ - Message: "Would you like a bidirectional integration?", - Default: false, - Help: "See https://docs.lacework.com/onboarding/jira#bidirectional-integration for more detail.", - }, - Validate: survey.Required, - }, - { - Name: "grouping", - Prompt: &survey.Select{ - Message: "Group Issues by:", - Options: getJiraGroupingOptions(), - }, - Validate: survey.Required, - }, - { - Name: "url", - Prompt: &survey.Input{Message: "Jira URL: "}, - Validate: survey.Required, - }, - { - Name: "issue", - Prompt: &survey.Input{Message: "Issue Type: "}, - Validate: survey.Required, - }, - { - Name: "project", - Prompt: &survey.Input{Message: "Project Key: "}, - Validate: survey.Required, - }, - { - Name: "username", - Prompt: &survey.Input{Message: "Username: "}, - Validate: survey.Required, - }, - } - - switch jiraType { - case api.JiraCloudAlertType, "": - jiraType = api.JiraCloudAlertType - questions = append(questions, &survey.Question{ - Name: "token", - Prompt: &survey.Password{Message: "API Token: "}, - Validate: survey.Required, - }) - case api.JiraServerAlertType: - questions = append(questions, &survey.Question{ - Name: "password", - Prompt: &survey.Password{Message: "Password: "}, - Validate: survey.Required, - }) - } - - var answers jiraAlertChannelIntegrationSurvey - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - grouping := api.JiraIssueGroupingsSurvey[answers.Grouping] - jira := api.JiraDataV2{ - ApiToken: answers.Token, - IssueGrouping: grouping.String(), - IssueType: answers.Issue, - JiraType: jiraType, - JiraUrl: answers.Url, - ProjectID: answers.Project, - Username: answers.Username, - Password: answers.Password, - } - if answers.Bidirectional { - jira.Configuration = api.BidirectionalJiraConfiguration - } - - // ask the user if they would like to configure a Custom Template - custom := false - err = survey.AskOne(&survey.Confirm{ - Message: "Configure a Custom Template File?", - }, &custom) - - if err != nil { - return err - } - - if custom { - var content string - - err = survey.AskOne(&survey.Editor{ - Message: "Provide the Custom Template File in JSON format", - FileName: "*.json", - }, &content) - - if err != nil { - return err - } - - jira.EncodeCustomTemplateFile(content) - } - - jiraCloudAlertChan := api.NewAlertChannel(answers.Name, api.JiraAlertChannelType, jira) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(jiraCloudAlertChan) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_microsoft_teams.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_microsoft_teams.go deleted file mode 100644 index 3c1a3a9c3..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_microsoft_teams.go +++ /dev/null @@ -1,64 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createMicrosoftTeamsChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "webhook_url", - Prompt: &survey.Input{Message: "Webhook URL: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - WebhookUrl string `survey:"webhook_url"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - teams := api.NewAlertChannel(answers.Name, - api.MicrosoftTeamsAlertChannelType, - api.MicrosoftTeamsData{ - TeamsURL: answers.WebhookUrl, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(teams) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_new_relic_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_new_relic_channel.go deleted file mode 100644 index 5e26d6705..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_new_relic_channel.go +++ /dev/null @@ -1,71 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createNewRelicAlertChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "account_id", - Prompt: &survey.Input{Message: "Account ID:"}, - Validate: survey.Required, - }, - { - Name: "insert_key", - Prompt: &survey.Input{Message: "Insert API Key:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - AccountID int `survey:"account_id"` - InsertKey string `survey:"insert_key"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - relic := api.NewAlertChannel(answers.Name, - api.NewRelicInsightsAlertChannelType, - api.NewRelicInsightsDataV2{ - AccountID: answers.AccountID, - InsertKey: answers.InsertKey, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(relic) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_oci.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_oci.go deleted file mode 100644 index abf6872cf..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_oci.go +++ /dev/null @@ -1,107 +0,0 @@ -// -// Author:: Kolbeinn Karlsson () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "os" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" -) - -func createOciConfigIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "tenant_id", - Prompt: &survey.Input{Message: "Tenant ID:"}, - Validate: survey.Required, - }, - { - Name: "tenant_name", - Prompt: &survey.Input{Message: "Tenant Name:"}, - Validate: survey.Required, - }, - { - Name: "home_region", - Prompt: &survey.Input{Message: "Home Region:"}, - Validate: survey.Required, - }, - { - Name: "user_ocid", - Prompt: &survey.Input{Message: "User OCID:"}, - Validate: survey.Required, - }, - { - Name: "fingerprint", - Prompt: &survey.Input{Message: "Public Key Fingerprint:"}, - Validate: survey.Required, - }, - { - Name: "private_key_file", - Prompt: &survey.Input{Message: "Path to private key file:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - TenantID string `survey:"tenant_id"` - TenantName string `survey:"tenant_name"` - HomeRegion string `survey:"home_region"` - UserOCID string `survey:"user_ocid"` - Fingerprint string `survey:"fingerprint"` - PrivateKeyFile string `survey:"private_key_file"` - }{} - - err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)) - if err != nil { - return err - } - - privateKeyBytes, err := os.ReadFile(answers.PrivateKeyFile) - if err != nil { - return errors.Wrap(err, "error reading private key file") - } - - oci := api.NewCloudAccount( - answers.Name, - api.OciCfgCloudAccount, - api.OciCfgData{ - TenantID: answers.TenantID, - TenantName: answers.TenantName, - HomeRegion: answers.HomeRegion, - UserOCID: answers.UserOCID, - Credentials: api.OciCfgCredentials{ - Fingerprint: answers.Fingerprint, - PrivateKey: string(privateKeyBytes), - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.CloudAccounts.Create(oci) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_pagerduty.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_pagerduty.go deleted file mode 100644 index 95bbcca7c..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_pagerduty.go +++ /dev/null @@ -1,64 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createPagerDutyAlertChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "key", - Prompt: &survey.Input{Message: "Integration Key: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Key string - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - alert := api.NewAlertChannel(answers.Name, - api.PagerDutyApiAlertChannelType, - api.PagerDutyApiDataV2{ - IntegrationKey: answers.Key, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(alert) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_proxy_scanner.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_proxy_scanner.go deleted file mode 100644 index 3509e0cf1..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_proxy_scanner.go +++ /dev/null @@ -1,110 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createProxyScannerIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "limit_tag", - Prompt: &survey.Input{ - Message: "Limit by Tag: ", - Default: "*", - }, - }, - { - Name: "limit_label", - Prompt: &survey.Multiline{ - Message: "List of 'key:value' labels:", - Default: "*", - }, - }, - { - Name: "limit_repos", - Prompt: &survey.Input{ - Message: "Limit by Repository: ", - Default: "*", - }, - }, - { - Name: "limit_max_images", - Prompt: &survey.Select{ - Message: "Limit Number of Images per Repo: ", - Options: []string{ - "5", "10", "15", - }, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - LimitTag string `survey:"limit_tag"` - LimitLabel string `survey:"limit_label"` - LimitRepos string `survey:"limit_repos"` - LimitMaxImages string `survey:"limit_max_images"` - }{} - - if err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ); err != nil { - return err - } - - limitMaxImages, err := strconv.Atoi(answers.LimitMaxImages) - if err != nil { - cli.Log.Warnw("unable to convert limit_max_images, using default", - "error", err, - "input", answers.LimitMaxImages, - "default", "5", - ) - limitMaxImages = 5 - } - - proxy := api.NewContainerRegistry( - answers.Name, - api.ProxyScannerContainerRegistry, - api.ProxyScannerData{ - RegistryType: api.ProxyScannerContainerRegistry.String(), - LimitByTag: strings.Split(answers.LimitTag, "\n"), - LimitByLabel: castStringToLimitByLabel(answers.LimitLabel), - LimitByRep: strings.Split(answers.LimitRepos, "\n"), - LimitNumImg: limitMaxImages, - }, - ) - - cli.StartProgress("Creating integration...") - _, err = cli.LwApi.V2.ContainerRegistries.Create(proxy) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_qradar_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_qradar_channel.go deleted file mode 100644 index 475c018ca..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_qradar_channel.go +++ /dev/null @@ -1,85 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createQRadarAlertChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "host_url", - Prompt: &survey.Input{Message: "Host Url:"}, - Validate: survey.Required, - }, - { - Name: "host_port", - Prompt: &survey.Input{Message: "Host Port:"}, - Validate: survey.Required, - }, - { - Name: "communication_type", - Prompt: &survey.Select{Message: "Communication Type:", - Options: []string{string(api.QRadarCommHttps), string(api.QRadarCommHttpsSelfSigned)}, - Default: string(api.QRadarCommHttps), - }, - }, - } - - answers := struct { - Name string - HostURL string `survey:"host_url"` - HostPort int `survey:"host_port"` - CommunicationType string `survey:"communication_type"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - commType, err := api.QRadarComm(answers.CommunicationType) - if err != nil { - return err - } - - qradar := api.NewAlertChannel(answers.Name, - api.IbmQRadarAlertChannelType, - api.IbmQRadarDataV2{ - HostURL: answers.HostURL, - HostPort: answers.HostPort, - QRadarCommType: commType, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(qradar) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_service_now_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_service_now_channel.go deleted file mode 100644 index 7f3db8cde..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_service_now_channel.go +++ /dev/null @@ -1,112 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createServiceNowAlertChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name:"}, - Validate: survey.Required, - }, - { - Name: "instance_url", - Prompt: &survey.Input{Message: "InstanceURL:"}, - Validate: survey.Required, - }, - { - Name: "username", - Prompt: &survey.Input{Message: "Username:"}, - Validate: survey.Required, - }, - { - Name: "password", - Prompt: &survey.Password{Message: "Password:"}, - Validate: survey.Required, - }, - { - Name: "issue_grouping", - Prompt: &survey.Select{Message: "Issue Grouping:", - Options: []string{"Events", "Resources"}, - }, - }, - } - - answers := struct { - Name string - InstanceURL string `survey:"instance_url"` - Username string `survey:"username"` - Password string `survey:"password"` - IssueGrouping string `survey:"issue_grouping"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - snow := api.ServiceNowRestDataV2{ - InstanceURL: answers.InstanceURL, - Username: answers.Username, - Password: answers.Password, - IssueGrouping: answers.IssueGrouping, - } - - // ask the user if they would like to configure a Custom Template - custom := false - err = survey.AskOne(&survey.Confirm{ - Message: "Configure a Custom Template File?", - }, &custom) - - if err != nil { - return err - } - - if custom { - var content string - - err = survey.AskOne(&survey.Editor{ - Message: "Provide the Custom Template File in JSON format", - FileName: "*.json", - }, &content) - - if err != nil { - return err - } - - snow.EncodeCustomTemplateFile(content) - } - - snowAlert := api.NewAlertChannel(answers.Name, - api.ServiceNowRestAlertChannelType, - snow) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(snowAlert) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_slack_channel.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_slack_channel.go deleted file mode 100644 index 64a3f4a44..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_slack_channel.go +++ /dev/null @@ -1,64 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createSlackAlertChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "url", - Prompt: &survey.Input{Message: "Slack URL: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Url string - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - slack := api.NewAlertChannel(answers.Name, - api.SlackChannelAlertChannelType, - api.SlackChannelDataV2{ - SlackUrl: answers.Url, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(slack) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_splunk.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_splunk.go deleted file mode 100644 index c0eca6cea..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_splunk.go +++ /dev/null @@ -1,106 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createSplunkIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "channel", - Prompt: &survey.Input{Message: "Channel: "}, - }, - { - Name: "hec_token", - Prompt: &survey.Input{Message: "Hec Token: "}, - Validate: survey.Required, - }, - { - Name: "host", - Prompt: &survey.Input{Message: "Host: "}, - Validate: survey.Required, - }, - { - Name: "port", - Prompt: &survey.Input{Message: "Port: "}, - Validate: survey.Required, - }, - { - Name: "source", - Prompt: &survey.Input{Message: "Source: "}, - Validate: survey.Required, - }, - { - Name: "index", - Prompt: &survey.Input{Message: "Index: "}, - Validate: survey.Required, - }, - { - Name: "ssl", - Prompt: &survey.Confirm{Message: "Enable SSL?"}, - }, - } - - answers := struct { - Name string - Channel string - HecToken string `survey:"hec_token"` - Host string - Port int - Source string - Index string - Ssl bool - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - splunk := api.NewAlertChannel(answers.Name, - api.SplunkHecAlertChannelType, - api.SplunkHecDataV2{ - Channel: answers.Channel, - HecToken: answers.HecToken, - Host: answers.Host, - Port: answers.Port, - Ssl: answers.Ssl, - EventData: api.SplunkHecEventDataV2{ - Index: answers.Index, - Source: answers.Source, - }, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(splunk) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_victorops.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_victorops.go deleted file mode 100644 index 01c54f295..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_victorops.go +++ /dev/null @@ -1,64 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createVictorOpsChannelIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "url", - Prompt: &survey.Input{Message: "VictorOps URL: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Url string - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - victorops := api.NewAlertChannel(answers.Name, - api.VictorOpsAlertChannelType, - api.VictorOpsDataV2{ - Url: answers.Url, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(victorops) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_webhook.go b/vendor/github.com/lacework/go-sdk/cli/cmd/integration_webhook.go deleted file mode 100644 index 0a27b97a0..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/integration_webhook.go +++ /dev/null @@ -1,64 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createWebhookIntegration() error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "url", - Prompt: &survey.Input{Message: "Webhook URL: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string - Url string - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return err - } - - webhook := api.NewAlertChannel(answers.Name, - api.WebhookAlertChannelType, - api.WebhookDataV2{ - WebhookUrl: answers.Url, - }, - ) - - cli.StartProgress(" Creating integration...") - _, err = cli.LwApi.V2.AlertChannels.Create(webhook) - cli.StopProgress() - return err -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql.go deleted file mode 100644 index e46f9a45f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql.go +++ /dev/null @@ -1,590 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "io" - "net/http" - "os" - "strconv" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/failon" - "github.com/lacework/go-sdk/lwtime" -) - -var ( - queryCmdState = struct { - End string - File string - Limit int - Range string - Start string - URL string - ValidateOnly bool - FailOnCount string - EmptyTemplate bool - // create, update validate from library - CURVFromLibrary string - }{} - - // queryCmd represents the lql parent command - queryCmd = &cobra.Command{ - Use: "query", - Aliases: []string{"lql", "queries"}, - Short: "Run and manage queries", - Long: `Run and manage Lacework Query Language (LQL) queries. - -LQL is a SQL-like query language for specifying the selection, filtering, and -manipulation of data. Queries let you interactively request information from -specified curated datasources. - -Lacework ships a set of default LQL queries that are available in your account. - -For more information about LQL, visit: - - https://docs.lacework.com/lql-overview - -To view all LQL queries in your Lacework account. - - lacework query ls - -To show a query. - - lacework query show - -To execute a query. - - lacework query run - -**Note: LQL syntax may change.** -`, - } - - // queryRunCmd represents the lql run command - queryRunCmd = &cobra.Command{ - Aliases: []string{"execute"}, - Use: "run [query_id]", - Short: "Run a query", - Long: `Run an LQL query via editor: - - lacework query run --range today - -Run a query via ID (uses active profile): - - lacework query run MyQuery --start "-1w@w" --end "@w" - -Start and end times are required to run a query: - -1. Specify start and end times in one of the following formats: - - A. A relative time specifier - B. RFC3339 date and time - C. Epoch time in milliseconds - -2. Specify start and end times in one of the following ways: - - A. As StartTimeRange and EndTimeRange in the ParamInfo block within the query - B. As start_time_range and end_time_range if specifying JSON - C. As --start and --end CLI flags - -3. Start and End time precedence: - - A. CLI flags take precedence over JSON specifications`, - Args: cobra.MaximumNArgs(1), - PreRunE: func(_ *cobra.Command, _ []string) error { - // default is 0 hence the '< 0' comparison - if queryCmdState.Limit < 0 { - return errors.New("limit must be at least 1") - } - - if queryCmdState.FailOnCount != "" { - var co failon.CountOperation - if err := co.Parse(queryCmdState.FailOnCount); err != nil { - return err - } - - if _, err := co.IsFail(0); err != nil { - return err - } - } - return nil - }, - RunE: runQuery, - } -) - -func init() { - // add the lql command - rootCmd.AddCommand(queryCmd) - - // add sub-commands to the lql command - queryCmd.AddCommand(queryRunCmd) - - if cli.isLCLInstalled() { - queryRunCmd.Flags().StringVarP( - &queryCmdState.CURVFromLibrary, - "library", "l", "", - "run query from Lacework Content Library", - ) - } - - // run specific flags - setQuerySourceFlags(queryRunCmd) - - // limit flag - queryRunCmd.Flags().IntVar( - &queryCmdState.Limit, - "limit", 0, - "result limit for query (default 0)", - ) - - // range time flag - queryRunCmd.Flags().StringVar( - &queryCmdState.Range, - "range", "", - "natural time range for query", - ) - - // start time flag - queryRunCmd.Flags().StringVar( - &queryCmdState.Start, - "start", "-24h", - "start time for query", - ) - // end time flag - queryRunCmd.Flags().StringVar( - &queryCmdState.End, - "end", "now", - "end time for query", - ) - queryRunCmd.Flags().BoolVar( - &queryCmdState.ValidateOnly, - "validate_only", false, - "validate query only (do not run)", - ) - // fail on count - queryRunCmd.Flags().StringVar( - &queryCmdState.FailOnCount, - "fail_on_count", "", - "fail if the results from a query match the provided expression (e.g. '>0')", - ) - // empty template flag - queryRunCmd.Flags().BoolVar( - &queryCmdState.EmptyTemplate, - "empty", false, - "start $EDITOR with empty file", - ) -} - -func setQuerySourceFlags(cmds ...*cobra.Command) { - for _, cmd := range cmds { - if cmd != nil { - action := strings.Split(cmd.Use, " ")[0] - - // file flag to specify a query from disk - cmd.Flags().StringVarP( - &queryCmdState.File, - "file", "f", "", - fmt.Sprintf("path to a query to %s", action), - ) - // url flag to specify a query from url - cmd.Flags().StringVarP( - &queryCmdState.URL, - "url", "u", "", - fmt.Sprintf("url to a query to %s", action), - ) - } - } -} - -// for commands that take a query as input -func inputQuery(cmd *cobra.Command) (string, error) { - // if running via library (CUV) - if queryCmdState.CURVFromLibrary != "" { - return inputQueryFromLibrary(queryCmdState.CURVFromLibrary) - } - // if running via file - if queryCmdState.File != "" { - return inputQueryFromFile(queryCmdState.File) - } - // if running via URL - if queryCmdState.URL != "" { - return inputQueryFromURL(queryCmdState.URL) - } - // if running via stdin - stat, err := os.Stdin.Stat() - if err != nil { - cli.Log.Debugw("error retrieving stdin mode", "error", err.Error()) - } else if (stat.Mode() & os.ModeCharDevice) == 0 { - bytes, err := io.ReadAll(os.Stdin) - return string(bytes), err - } - // if running via editor - action := "validate" - if !queryCmdState.ValidateOnly { - action = strings.Split(cmd.Use, " ")[0] - } - return inputQueryFromEditor(action) -} - -func inputQueryFromLibrary(id string) (string, error) { - var ( - lcl *LaceworkContentLibrary - err error - ) - if lcl, err = cli.LoadLCL(); err != nil { - return "", err - } - return lcl.GetQuery(id) -} - -func inputQueryFromFile(filePath string) (string, error) { - fileData, err := os.ReadFile(filePath) - - if err != nil { - return "", errors.Wrap(err, "unable to read file") - } - - return string(fileData), nil -} - -func inputQueryFromURL(url string) (query string, err error) { - msg := "unable to access URL" - - response, err := http.Get(url) - if err != nil { - err = errors.Wrap(err, msg) - return - } - defer response.Body.Close() - - if response.StatusCode != 200 { - err = errors.Wrap(errors.New(response.Status), msg) - return - } - - body, err := io.ReadAll(response.Body) - if err != nil { - err = errors.Wrap(err, msg) - return - } - query = string(body) - return -} - -func inputQueryFromEditor(action string) (query string, err error) { - prompt := &survey.Editor{ - Message: fmt.Sprintf("Type a query to %s", action), - FileName: "query*.yaml", - } - - if (action == "create" || action == "run") && !queryCmdState.EmptyTemplate { - prompt.Default = `queryId: YourQueryID -queryText: |- - { - source { - --- Select a datasource. To list all available datasources, use 'lacework query sources'. - } - filter { - --- Add query filter(s), if any. If not, remove this block. - } - return { - --- List fields to return from the selected source. Use 'lacework query describe '. - } - }` - prompt.HideDefault = true - prompt.AppendDefault = true - } else if (action == "create" || action == "run") && queryCmdState.EmptyTemplate { - prompt.Default = `` - prompt.HideDefault = true - prompt.AppendDefault = true - } - - err = survey.AskOne(prompt, &query) - return -} - -func parseQueryTime(s string) (time.Time, error) { - // empty - if s == "" { - return time.Time{}, errors.New(fmt.Sprintf("unable to parse time (%s)", s)) - } - // parse time as relative - if t, err := lwtime.ParseRelative(s); err == nil { - return t, err - } - // parse time as RFC3339 - if t, err := time.Parse(time.RFC3339, s); err == nil { - return t, err - } - // parse time as millis - if i, err := strconv.ParseInt(s, 10, 64); err == nil { - return time.Unix(0, i*int64(time.Millisecond)), err - } - return time.Time{}, errors.New(fmt.Sprintf("unable to parse time (%s)", s)) -} - -func queryErrorCrumbs(q string) error { - // smells like json - q = strings.TrimSpace(q) - if strings.HasPrefix(q, "[") || - strings.HasPrefix(q, "{") { - - return errors.New(`invalid query - -It looks like you attempted to submit a query in JSON format. -Verify that the JSON is formatted properly and adheres to the following schema: - -{ - "queryId": "MyLQL", - "queryText": "{ source { CloudTrailRawEvents } filter { EVENT_SOURCE = 's3.amazonaws.com' } return { INSERT_ID } }" -} -`) - } - // smells like plain text - return errors.New(`invalid query - -It looks like you attempted to submit a query in YAML format. -Verify that the text adheres to the following schema: - -queryId: MyLQL -queryText: |- - { - source { - CloudTrailRawEvents - } - filter { - EVENT_SOURCE = 's3.amazonaws.com' - } - return { - INSERT_ID - } - } -`) -} - -func runQuery(cmd *cobra.Command, args []string) error { - var ( - err error - start time.Time - end time.Time - response api.ExecuteQueryResponse - msg string = "unable to run query" - hasCmdArgs bool = len(args) != 0 && args[0] != "" - ) - - // check use of with other flags - if hasCmdArgs { - var naFlag string - - if queryCmdState.File != "" { - naFlag = "file" - } - if queryCmdState.CURVFromLibrary != "" { - naFlag = "library" - } - if queryCmdState.URL != "" { - naFlag = "url" - } - if queryCmdState.ValidateOnly { - naFlag = "validate_only" - } - if queryCmdState.EmptyTemplate { - naFlag = "empty" - } - if naFlag != "" { - return errors.New( - fmt.Sprintf( - "flag --%s not applicable when specifying query_id argument", - naFlag, - ), - ) - } - } - - // validate_only - if queryCmdState.ValidateOnly { - return validateQuery(cmd, args) - } - - // use of if/else intentional here based on logic paths for determining start and end time.Time values - // if cli user has specified a range we use ParseNatural which gives us start and end time.Time values - // otherwise we need to convert queryCmdState start and end strings to time.Time values using parseQueryTime - if queryCmdState.Range != "" { - cli.Log.Debugw("retrieving natural time range") - - start, end, err = lwtime.ParseNatural(queryCmdState.Range) - if err != nil { - return errors.Wrap(err, msg) - } - } else { - // parse start - start, err = parseQueryTime(queryCmdState.Start) - if err != nil { - return errors.Wrap(err, msg) - } - // parse end - end, err = parseQueryTime(queryCmdState.End) - if err != nil { - return errors.Wrap(err, msg) - } - } - - queryArgs := []api.ExecuteQueryArgument{ - { - Name: api.QueryStartTimeRange, - Value: start.UTC().Format(lwtime.RFC3339Milli), - }, - { - Name: api.QueryEndTimeRange, - Value: end.UTC().Format(lwtime.RFC3339Milli), - }, - } - - if hasCmdArgs { - // query by id - response, err = runQueryByID(args[0], queryArgs) - } else { - // adhoc query - response, err = runAdhocQuery(cmd, queryArgs) - } - - if err != nil { - return errors.Wrap(err, "unable to run query") - } - - // output - if err = cli.OutputJSON(response.Data); err != nil { - return err - } - - // fail_on_count post - if queryCmdState.FailOnCount != "" { - cli.Log.Infow("enforce failure flag(s)", - "fail_on_count", queryCmdState.FailOnCount, - ) - - queryFailonError := NewQueryFailonError( - queryCmdState.FailOnCount, - len(response.Data), - ) - if queryFailonError.NonCompliant() { - cmd.SilenceUsage = true - return queryFailonError - } - } - return nil -} - -func runQueryByID(id string, args []api.ExecuteQueryArgument) ( - api.ExecuteQueryResponse, - error, -) { - cli.Log.Debugw("running query", "query", id) - - cli.StartProgress(getRunStartProgressMessage(args)) - defer cli.StopProgress() - - opts := api.ExecuteQueryOptions{} - // only add limit if > 0 - if queryCmdState.Limit > 0 { - opts.Limit = &queryCmdState.Limit - } - - request := api.ExecuteQueryByIDRequest{ - QueryID: id, - Options: opts, - Arguments: args, - } - return cli.LwApi.V2.Query.ExecuteByID(request) -} - -func runAdhocQuery(cmd *cobra.Command, args []api.ExecuteQueryArgument) ( - response api.ExecuteQueryResponse, - err error, -) { - // input query - queryString, err := inputQuery(cmd) - if err != nil { - return - } - // parse query - newQuery, err := api.ParseNewQuery(queryString) - if err != nil { - err = queryErrorCrumbs(queryString) - return - } - - opts := api.ExecuteQueryOptions{} - // only add limit if > 0 - if queryCmdState.Limit > 0 { - opts.Limit = &queryCmdState.Limit - } - - cli.StartProgress(getRunStartProgressMessage(args)) - defer cli.StopProgress() - - // execute query - executeQuery := api.ExecuteQueryRequest{ - Query: api.ExecuteQuery{ - QueryText: newQuery.QueryText, - }, - Options: opts, - Arguments: args, - } - - cli.Log.Debugw("running query", "query", queryString) - response, err = cli.LwApi.V2.Query.Execute(executeQuery) - return -} - -func getRunStartProgressMessage(args []api.ExecuteQueryArgument) string { - var ( - startTime, endTime time.Time - startErr error = errors.New("StartTimeRange not present in ExecuteQueryArgument list") - endErr error = errors.New("EndTimeRange not present in ExecuteQueryArgument list") - ) - for _, arg := range args { - switch arg.Name { - case api.QueryStartTimeRange: - startTime, startErr = time.Parse(time.RFC3339, arg.Value) - case api.QueryEndTimeRange: - endTime, endErr = time.Parse(time.RFC3339, arg.Value) - } - } - - msg := "Executing query" - if startErr == nil && endErr == nil { - msg = fmt.Sprintf( - "%s in the time range %s - %s", - msg, - startTime.Format("2006-Jan-2 15:04:05 MST"), - endTime.Format("2006-Jan-2 15:04:05 MST"), - ) - } - return msg -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_create.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_create.go deleted file mode 100644 index 1f6f00d04..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_create.go +++ /dev/null @@ -1,156 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - // queryCreateCmd represents the lql create command - queryCreateCmd = &cobra.Command{ - Use: "create", - Short: "Create a query", - Long: ` -There are multiple ways you can create a query: - - * Typing the query into your default editor (via $EDITOR) - * Piping a query to the Lacework CLI command (via $STDIN) - * From a local file on disk using the flag '--file' - * From a URL using the flag '--url' - -There are also multiple formats you can use to define a query: - - * Javascript Object Notation (JSON) - * YAML Ain't Markup Language (YAML) - -To launch your default editor and create a new query. - - lacework lql create - -The following example checks for unrestricted ingress to TCP port 445: - - --- - queryId: LW_Custom_UnrestrictedIngressToTCP445 - queryText: |- - { - source { - LW_CFG_AWS_EC2_SECURITY_GROUPS a, - array_to_rows(a.RESOURCE_CONFIG:IpPermissions) as (ip_permissions), - array_to_rows(ip_permissions:IpRanges) as (ip_ranges) - } - filter { - ip_permissions:IpProtocol = 'tcp' - and ip_permissions:FromPort = 445 - and ip_permissions:ToPort = 445 - and ip_ranges:CidrIp = '0.0.0.0/0' - } - return distinct { - ACCOUNT_ALIAS, - ACCOUNT_ID, - ARN as RESOURCE_KEY, - RESOURCE_REGION, - RESOURCE_TYPE, - SERVICE - } - } - -A query is represented using JSON or YAML markup and must specify both 'queryId' -and 'queryText' keys. The above query uses YAML, specifies an identifier of -'LW_Custom_UnrestrictedIngressToTCP445', and identifies AWS EC2 security groups with -unrestricted access to TCP port 445. The queryText is expressed in Lacework Query -Language (LQL) syntax which is delimited by '{ }' and contains three sections: - - * Source data is specified in the 'source' clause. The source of data is the - 'LW_CFG_AWS_EC2_SECURITY_GROUPS' datasource. LQL queries generally refer to other - datasources, and customizable policies always target a suitable datasource. - - * Records of interest are specified by the 'filter' clause. In the example, the - records available in 'LW_CFG_AWS_EC2_SECURITY_GROUPS' are filtered for those whose IP - protocol is 'tcp', whose from and to port is '445', and CidrIP is '0.0.0.0/0'. - The syntax for this filtering expression strongly resembles SQL. - - * The fields this query exposes are listed in the 'return' clause. Because there - may be unwanted duplicates among result records when Lacework composes them from - just these four columns, the distinct modifier is added. This behaves like a SQL - 'SELECT DISTINCT'. Each returned column in this case is just a field that is present - in 'LW_CFG_AWS_EC2_SECURITY_GROUPS', but you can compose results by manipulating strings, - dates, JSON and numbers as well. - -The resulting dataset is shaped like a table. The table's columns are named with the -names of the columns selected. If desired, you could alias them to other names as well. - -For more information about LQL, visit: - - https://docs.lacework.com/lql-overview -`, - Args: cobra.NoArgs, - RunE: createQuery, - } -) - -func init() { - // add sub-commands to the lql command - queryCmd.AddCommand(queryCreateCmd) - - setQuerySourceFlags(queryCreateCmd) - - if cli.isLCLInstalled() { - queryCreateCmd.Flags().StringVarP( - &queryCmdState.CURVFromLibrary, - "library", "l", "", - "create query from Lacework Content Library", - ) - } -} - -func createQuery(cmd *cobra.Command, args []string) error { - msg := "unable to create query" - - // input query - queryString, err := inputQuery(cmd) - if err != nil { - return errors.Wrap(err, msg) - } - - // parse query - newQuery, err := api.ParseNewQuery(queryString) - if err != nil { - return errors.Wrap(queryErrorCrumbs(queryString), msg) - } - - // create query - cli.Log.Debugw("creating query", "query", queryString) - cli.StartProgress(" Creating query...") - create, err := cli.LwApi.V2.Query.Create(newQuery) - cli.StopProgress() - - // output - if err != nil { - return errors.Wrap(err, msg) - } - if cli.JSONOutput() { - return cli.OutputJSON(create.Data) - } - cli.OutputHuman("The query %s was created.\n", create.Data.QueryID) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_delete.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_delete.go deleted file mode 100644 index 2d66b3f78..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_delete.go +++ /dev/null @@ -1,57 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // queryDeleteCmd represents the lql delete command - queryDeleteCmd = &cobra.Command{ - Use: "delete ", - Short: "Delete a query", - Long: `Delete a single LQL query by providing the query ID. - -Use the command 'lacework query list' to list the available queries in -your Lacework account.`, - Args: cobra.ExactArgs(1), - RunE: deleteQuery, - } -) - -func init() { - // add sub-commands to the lql command - queryCmd.AddCommand(queryDeleteCmd) -} - -func deleteQuery(_ *cobra.Command, args []string) error { - cli.Log.Debugw("deleting query", "id", args[0]) - - cli.StartProgress(" Deleting query...") - _, err := cli.LwApi.V2.Query.Delete(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to delete query") - } - - cli.OutputHuman("The query %s was deleted.\n", args[0]) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_library.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_library.go deleted file mode 100644 index 238f3b72d..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_library.go +++ /dev/null @@ -1,117 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "sort" - - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - queryListLibraryCmd = &cobra.Command{ - Use: "list-library", - Short: "List queries from library", - Long: `List all LQL queries in your Lacework Content Library.`, - Args: cobra.NoArgs, - RunE: listQueryLibrary, - } - queryShowLibraryCmd = &cobra.Command{ - Use: "show-library ", - Short: "Show a query from library", - Long: `Show a query in your Lacework Content Library.`, - Args: cobra.ExactArgs(1), - RunE: showQueryLibrary, - } -) - -func init() { - if cli.isLCLInstalled() { - queryCmd.AddCommand(queryListLibraryCmd) - queryCmd.AddCommand(queryShowLibraryCmd) - } -} - -func getListQueryLibraryTable(queries map[string]LCLQuery) (out [][]string) { - for id := range queries { - out = append(out, []string{id}) - } - // order by ID - sort.Slice(out, func(i, j int) bool { - return out[i][0] < out[j][0] - }) - return -} - -func listQueryLibrary(_ *cobra.Command, args []string) error { - cli.Log.Debugw("listing queries from library") - - cli.StartProgress(" Retrieving queries...") - lcl, err := cli.LoadLCL() - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "unable to list queries") - } - if cli.JSONOutput() { - return cli.OutputJSON(lcl.Queries) - } - if len(lcl.Queries) == 0 { - cli.OutputHuman("There were no queries found.") - return nil - } - cli.OutputHuman( - renderSimpleTable( - []string{"Query ID"}, - getListQueryLibraryTable(lcl.Queries), - ), - ) - return nil -} - -func showQueryLibrary(_ *cobra.Command, args []string) error { - var ( - msg string = "unable to show query" - queryString string - newQuery api.NewQuery - err error - ) - cli.Log.Debugw("retrieving query", "id", args[0]) - - cli.StartProgress(" Retrieving query...") - // input query - if queryString, err = inputQueryFromLibrary(args[0]); err != nil { - cli.StopProgress() - return errors.Wrap(err, msg) - } - // parse query - newQuery, err = api.ParseNewQuery(queryString) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, msg) - } - if cli.JSONOutput() { - return cli.OutputJSON(newQuery) - } - cli.OutputHuman(newQuery.QueryText) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_list.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_list.go deleted file mode 100644 index 013d63674..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_list.go +++ /dev/null @@ -1,88 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "sort" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - // queryListCmd represents the lql list command - queryListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List queries", - Long: `List all LQL queries in your Lacework account.`, - Args: cobra.NoArgs, - RunE: listQueries, - } -) - -func init() { - queryCmd.AddCommand(queryListCmd) -} - -func queryTable(queryData []api.Query) (out [][]string) { - for _, query := range queryData { - out = append(out, []string{ - query.QueryID, - query.Owner, - query.LastUpdateTime, - query.LastUpdateUser, - }) - } - - // order by ID - sort.Slice(out, func(i, j int) bool { - return out[i][0] < out[j][0] - }) - - return -} - -func listQueries(_ *cobra.Command, args []string) error { - cli.Log.Debugw("listing queries") - - cli.StartProgress(" Retrieving queries...") - queryResponse, err := cli.LwApi.V2.Query.List() - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to list queries") - } - - if cli.JSONOutput() { - return cli.OutputJSON(queryResponse.Data) - } - if len(queryResponse.Data) == 0 { - cli.OutputHuman("There were no queries found.") - return nil - } - cli.OutputHuman( - renderSimpleTable( - []string{"Query ID", "Owner", "Last Update Time", "Last Update User"}, - queryTable(queryResponse.Data), - ), - ) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_preview.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_preview.go deleted file mode 100644 index 1f7309904..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_preview.go +++ /dev/null @@ -1,117 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/lwtime" -) - -var ( - queryPreviewSourceCmd = &cobra.Command{ - Use: "preview-source ", - Short: "Preview Lacework query datasource", - Long: `Preview Lacework query datasource.`, - Args: cobra.ExactArgs(1), - RunE: previewQuerySource, - } - queryPreviewSourceTemplate = `CLIAdhocPreview { source { %s } return distinct { %s } }` -) - -func init() { - queryCmd.AddCommand(queryPreviewSourceCmd) -} - -func previewQuerySource(_ *cobra.Command, args []string) error { - cli.Log.Debugw("retrieving datasource", "id", args[0]) - - cli.StartProgress(" Retrieving datasource...") - datasourceResponse, err := cli.LwApi.V2.Datasources.Get(args[0]) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "unable to retrieve datasource") - } - - // build returns list from datasource fields - var returns []string - for _, ret := range datasourceResponse.Data.ResultSchema { - returns = append(returns, ret.Name) - } - if len(returns) == 0 { - return errors.New("unable to parse datasource schema") - } - - // initialize limit - limit := 1 - - // initialize query - executeQuery := api.ExecuteQueryRequest{ - Query: api.ExecuteQuery{ - QueryText: fmt.Sprintf( - queryPreviewSourceTemplate, args[0], strings.Join(returns, ",")), - }, - Options: api.ExecuteQueryOptions{Limit: &limit}, - } - - // initialize time attempts - timeAttempts := []map[string]string{ - {"start": "-24h", "end": "now"}, - {"start": "-7d", "end": "-24h"}, - {"start": "-30d", "end": "-7d"}, - } - - for _, timeAttempt := range timeAttempts { - start, _ := lwtime.ParseRelative(timeAttempt["start"]) - end, _ := lwtime.ParseRelative(timeAttempt["end"]) - - executeQuery.Arguments = []api.ExecuteQueryArgument{ - { - Name: api.QueryStartTimeRange, - Value: start.UTC().Format(lwtime.RFC3339Milli), - }, - { - Name: api.QueryEndTimeRange, - Value: end.UTC().Format(lwtime.RFC3339Milli), - }, - } - - // execute query - cli.Log.Debugw("running query", "query", executeQuery.Query.QueryText) - cli.StartProgress(" Executing preview query...") - response, err := cli.LwApi.V2.Query.Execute(executeQuery) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to preview datasource") - } - - if len(response.Data) == 0 { - continue - } - return cli.OutputJSON(response.Data[0]) - } - cli.OutputHuman("No results found for datasource\n") - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_show.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_show.go deleted file mode 100644 index 7528002df..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_show.go +++ /dev/null @@ -1,81 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - // queryShowCmd represents the lql show command - queryShowCmd = &cobra.Command{ - Use: "show ", - Short: "Show a query", - Long: `Show a query in your Lacework account.`, - Args: cobra.ExactArgs(1), - PreRunE: func(cmd *cobra.Command, _ []string) error { - b, err := cmd.Flags().GetBool("yaml") - if err != nil { - return errors.Wrap(err, "unable to parse --yaml flag") - } - if b { - cli.EnableYAMLOutput() - } - return nil - }, - RunE: showQuery, - } -) - -func init() { - queryCmd.AddCommand(queryShowCmd) - - queryShowCmd.Flags().Bool( - "yaml", false, "output query in YAML format", - ) -} - -func showQuery(_ *cobra.Command, args []string) error { - cli.Log.Debugw("retrieving query", "id", args[0]) - - cli.StartProgress("Retrieving query...") - queryResponse, err := cli.LwApi.V2.Query.Get(args[0]) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "unable to show query") - } - - if cli.JSONOutput() { - return cli.OutputJSON(queryResponse.Data) - } - - if cli.YAMLOutput() { - return cli.OutputYAML(&api.NewQuery{ - QueryID: queryResponse.Data.QueryID, - QueryText: queryResponse.Data.QueryText, - }) - } - - cli.OutputHuman(queryResponse.Data.QueryText) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_sources.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_sources.go deleted file mode 100644 index 5f7c2070b..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_sources.go +++ /dev/null @@ -1,195 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sort" - - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - queryListSourcesCmd = &cobra.Command{ - Aliases: []string{"sources"}, - Use: "list-sources", - Short: "List Lacework query datasources", - Long: `List Lacework query datasources.`, - Args: cobra.NoArgs, - RunE: listQuerySources, - } - - queryShowSourceCmd = &cobra.Command{ - Aliases: []string{"describe"}, - Use: "show-source ", - Short: "Show Lacework query datasource", - Long: `Show Lacework query datasource.`, - Args: cobra.ExactArgs(1), - RunE: showQuerySource, - } -) - -func init() { - queryCmd.AddCommand(queryListSourcesCmd) - queryCmd.AddCommand(queryShowSourceCmd) -} - -func querySourcesTable(datasources []api.Datasource) (out [][]string) { - var preOut [][]string - for _, source := range datasources { - preOut = append(preOut, []string{source.Name, source.Description}) - } - - // order by Name - sort.Slice(preOut, func(i, j int) bool { - return preOut[i][0] < preOut[j][0] - }) - - // condence output since datasources can be really long, - // how long? you ask, as of today, we have over 150 characters - for _, source := range preOut { - out = append(out, []string{ - fmt.Sprintf("%s\n%s", source[0], source[1]), - }) - } - return -} - -func listQuerySources(_ *cobra.Command, args []string) error { - cli.Log.Debugw("retrieving LQL datasources") - lqlSourcesUnableMsg := "unable to retrieve LQL datasources" - datasourcesResponse, err := cli.LwApi.V2.Datasources.List() - - if err != nil { - return errors.Wrap(err, lqlSourcesUnableMsg) - } - if cli.JSONOutput() { - return cli.OutputJSON(datasourcesResponse.Data) - } - if len(datasourcesResponse.Data) == 0 { - return yikes(lqlSourcesUnableMsg) - } - - cli.OutputHuman( - renderCustomTable([]string{"Datasource"}, - querySourcesTable(datasourcesResponse.Data), - tableFunc(func(t *tablewriter.Table) { - t.SetAlignment(tablewriter.ALIGN_LEFT) - t.SetColWidth(120) - t.SetAutoWrapText(true) - t.SetRowLine(true) - t.SetBorder(false) - t.SetReflowDuringAutoWrap(false) - }), - ), - ) - - cli.OutputHuman( - "\nUse 'lacework query show-source ' to show details about the datasource.\n", - ) - return nil -} - -func getShowQuerySourceTable(resultSchema []api.DatasourceSchema) (out [][]string) { - for _, schemaItem := range resultSchema { - out = append(out, []string{ - schemaItem.Name, - schemaItem.DataType, - schemaItem.Description, - }) - } - return -} - -func getShowQuerySourceRelationshipsTable(relationships []api.DatasourceRelationship) (out [][]string) { - for _, relationship := range relationships { - out = append(out, []string{ - relationship.Name, - relationship.From, - relationship.To, - relationship.ToCardinality, - relationship.Description, - }) - } - return -} - -func showQuerySource(_ *cobra.Command, args []string) error { - cli.Log.Infow("retrieving datasource", "id", args[0]) - - cli.StartProgress(" Retrieving datasource...") - datasourceResponse, err := cli.LwApi.V2.Datasources.Get(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to show datasource") - } - - if cli.JSONOutput() { - return cli.OutputJSON(datasourceResponse.Data) - } - cli.OutputHuman( - renderOneLineCustomTable("Datasource", - datasourceResponse.Data.Name, - tableFunc(func(t *tablewriter.Table) { - t.SetAlignment(tablewriter.ALIGN_LEFT) - t.SetColWidth(120) - t.SetBorder(false) - }), - ), - ) - cli.OutputHuman("\n") - cli.OutputHuman(renderOneLineCustomTable("DESCRIPTION", - datasourceResponse.Data.Description, - tableFunc(func(t *tablewriter.Table) { - t.SetAlignment(tablewriter.ALIGN_LEFT) - t.SetColWidth(120) - t.SetBorder(false) - t.SetAutoWrapText(true) - }), - )) - cli.OutputHuman("\n") - cli.OutputHuman( - renderSimpleTable( - []string{"Field Name", "Data Type", "Description"}, - getShowQuerySourceTable(datasourceResponse.Data.ResultSchema), - ), - ) - // if source relationships exist - if len(datasourceResponse.Data.SourceRelationships) > 0 { - cli.OutputHuman("\n") - cli.OutputHuman( - renderSimpleTable( - []string{"Relationship Name", "From", "To", "Cardinality", "Description"}, - getShowQuerySourceRelationshipsTable(datasourceResponse.Data.SourceRelationships), - ), - ) - } - // breadcrumb - cli.OutputHuman( - fmt.Sprintf( - "\nUse 'lacework query preview-source %s' to see an actual result from the datasource.\n", - datasourceResponse.Data.Name, - ), - ) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_update.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_update.go deleted file mode 100644 index e593776ae..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_update.go +++ /dev/null @@ -1,149 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - - "github.com/AlecAivazis/survey/v2" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "gopkg.in/yaml.v2" - - "github.com/lacework/go-sdk/api" -) - -var ( - // queryUpdateCmd represents the lql update command - queryUpdateCmd = &cobra.Command{ - Use: "update [query_id]", - Short: "Update a query", - Args: cobra.RangeArgs(0, 1), - Long: ` -There are multiple ways you can update a query: - - * Typing the query into your default editor (via $EDITOR) - * Passing a query ID to load it into your default editor - * From a local file on disk using the flag '--file' - * From a URL using the flag '--url' - -There are also multiple formats you can use to define a query: - - * Javascript Object Notation (JSON) - * YAML Ain't Markup Language (YAML) - -To launch your default editor and update a query. - - lacework query update -`, - RunE: updateQuery, - } -) - -func init() { - // add sub-commands to the lql command - queryCmd.AddCommand(queryUpdateCmd) - - setQuerySourceFlags(queryUpdateCmd) - - if cli.isLCLInstalled() { - queryUpdateCmd.Flags().StringVarP( - &queryCmdState.CURVFromLibrary, - "library", "l", "", - "update query from Lacework Content Library", - ) - } -} - -func updateQuery(cmd *cobra.Command, args []string) error { - msg := "unable to update query" - - var ( - queryString string - err error - ) - - if len(args) != 0 { - // query id via argument - cli.StartProgress("Retrieving query...") - queryRes, err := cli.LwApi.V2.Query.Get(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to load query from your account") - } - - queryYaml, err := yaml.Marshal(&api.NewQuery{ - QueryID: queryRes.Data.QueryID, - QueryText: queryRes.Data.QueryText, - }) - if err != nil { - return errors.Wrap(err, msg) - } - - prompt := &survey.Editor{ - Message: fmt.Sprintf("Update query %s", args[0]), - Default: string(queryYaml), - HideDefault: true, - AppendDefault: true, - FileName: "query*.yaml", - } - var queryStr string - err = survey.AskOne(prompt, &queryStr) - if err != nil { - return errors.Wrap(err, msg) - } - - queryString = queryStr - } else { - // input query - queryString, err = inputQuery(cmd) - if err != nil { - return errors.Wrap(err, msg) - } - } - - // parse query - newQuery, err := api.ParseNewQuery(queryString) - if err != nil { - return errors.Wrap(queryErrorCrumbs(queryString), msg) - } - - // avoid letting the user change the query id - if len(args) != 0 && newQuery.QueryID != args[0] { - return errors.New("changes to query ID not supported") - } - - // update query - cli.Log.Debugw("updating query", "query", queryString) - cli.StartProgress(" Updating query...") - update, err := cli.LwApi.V2.Query.Update(newQuery.QueryID, api.UpdateQuery{ - QueryText: newQuery.QueryText, - }) - cli.StopProgress() - - // output - if err != nil { - return errors.Wrap(err, msg) - } - if cli.JSONOutput() { - return cli.OutputJSON(update.Data) - } - cli.OutputHuman("The query %s was updated.\n", update.Data.QueryID) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_validate.go b/vendor/github.com/lacework/go-sdk/cli/cmd/lql_validate.go deleted file mode 100644 index 1b5a8000e..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/lql_validate.go +++ /dev/null @@ -1,108 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -const ( - lqlValidateUnableMsg string = "unable to validate query" -) - -var ( - // queryValidateCmd represents the lql validate command - queryValidateCmd = &cobra.Command{ - Use: "validate", - Short: "Validate a query", - Long: `Use this command to validate a single LQL query before creating it. - -There are multiple ways you can validate a query: - - * Typing the query into your default editor (via $EDITOR) - * From a local file on disk using the flag '--file' - * From a URL using the flag '--url' - -There are also multiple formats you can use to define a query: - - * Javascript Object Notation (JSON) - * YAML Ain't Markup Language (YAML) - -To launch your default editor and validate a query. - - lacework query validate -`, - Args: cobra.NoArgs, - RunE: validateQuery, - } -) - -func init() { - queryCmd.AddCommand(queryValidateCmd) - - setQuerySourceFlags(queryValidateCmd) - - if cli.isLCLInstalled() { - queryValidateCmd.Flags().StringVarP( - &queryCmdState.CURVFromLibrary, - "library", "l", "", - "validate query from Lacework Content Library", - ) - } -} - -func validateQuery(cmd *cobra.Command, args []string) error { - // input query - queryString, err := inputQuery(cmd) - if err != nil { - return errors.Wrap(err, lqlValidateUnableMsg) - } - // parse query - newQuery, err := api.ParseNewQuery(queryString) - if err != nil { - return errors.Wrap(queryErrorCrumbs(queryString), lqlValidateUnableMsg) - } - - cli.Log.Debugw("validating query", "query", queryString) - - return validateQueryAndOutput(newQuery) -} - -func validateQueryAndOutput(nq api.NewQuery) error { - vq := api.ValidateQuery{ - QueryText: nq.QueryText, - } - - cli.StartProgress(" Validating query...") - validate, err := cli.LwApi.V2.Query.Validate(vq) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, lqlValidateUnableMsg) - } - - if cli.JSONOutput() { - return cli.OutputJSON(validate.Data) - } - - cli.OutputHuman("Query validated successfully. Time for %s\n", randomEmoji()) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/migration.go b/vendor/github.com/lacework/go-sdk/cli/cmd/migration.go deleted file mode 100644 index e158b3607..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/migration.go +++ /dev/null @@ -1,179 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "os" - "path" - "strings" - "time" - - "github.com/pkg/errors" - "github.com/spf13/viper" - - "github.com/lacework/go-sdk/internal/cache" - "github.com/lacework/go-sdk/internal/file" - "github.com/lacework/go-sdk/lwconfig" -) - -// The name of the directory we will store backups of configuration files before migrating them -const ConfigBackupDir = "cfg_backups" - -// Migrations executes automatic configuration migrations, -// if a configuration file does not exist, it will only update -// the CLI state to the appropriate parameters -func (c *cliState) Migrations() (err error) { - c.Log.Debugw("executing v2 migration") - c.Event.Feature = featMigrateConfigV2 - c.CfgVersion = 0 - - defer func() { - if err == nil { - c.SendHoneyvent() - } else { - err = errors.Wrap(err, "during v2 migration") - } - - // update global honeyvent with updated state - c.Event.Account = c.Account - c.Event.Subaccount = c.Subaccount - c.Event.CfgVersion = c.CfgVersion - }() - - err = c.NewClient() - if err != nil { - return - } - - err = c.VerifySettings() - if err != nil { - return err - } - - resp, err := c.LwApi.V2.OrganizationInfo.Get() - if err != nil { - return err - } - orgInfo := resp.Data[0] - - // set new v2 config version and notify our feature event - c.CfgVersion = 2 - c.Event.AddFeatureField("config_version", c.CfgVersion) - c.Event.AddFeatureField("org_account", orgInfo.OrgAccount) - // NOTE: @afiune this will be a constant pattern below where - // we will update settings and notify the feature event - - if orgInfo.OrgAccount { - // we only need to update the account/sub-account - // if the user has an organizational account - c.Log.Debugw("organizational account detected") - c.Event.AddFeatureField("org_account_url", orgInfo.OrgAccountURL) - - primaryAccount := strings.ToLower(orgInfo.AccountName()) - - // if the user is accessing a sub-account, that is, if the current - // account is different from the primary account name, set it as - // what it is, the sub-account - if primaryAccount != c.Account { - c.Log.Debugw("updating account settings for APIv2", - "old_account", c.Account, - "new_account", primaryAccount, - ) - - // ALLY-541: Frankfurt accounts will have the account as 'account.fra', - // we need to remove the .fra domain to use it as a subaccount - if strings.Contains(c.Account, ".") { - c.Log.Debugw("subaccount needs cleanup", "subaccount", c.Account) - accSplit := strings.Split(c.Account, ".") - c.Account = accSplit[0] - } - - c.Subaccount = c.Account - c.Account = primaryAccount - - c.Event.AddFeatureField("account", c.Account) - c.Event.AddFeatureField("subaccount", c.Subaccount) - - c.Log.Debugw("generating new API client") - err = c.NewClient() - if err != nil { - return err - } - } - } - - // if the configuration file does not exist, most likely the user - // is executing the CLI via env variables or flags, update feature - // field and exit migration - if !file.FileExists(viper.ConfigFileUsed()) { - c.Log.Debugw("config file not found, skipping profile migration") - c.Event.AddFeatureField("config_file", "not_found") - return nil - } - - c.Log.Debugw("config found, migrating profile", "profile", c.Profile) - migratedProfile := lwconfig.Profile{ - Account: c.Account, - Subaccount: c.Subaccount, - ApiKey: c.KeyID, - ApiSecret: c.Secret, - Version: c.CfgVersion, - } - - // create a backup before modifying the user's configuration - bkpPath, err := createConfigurationBackup() - if err != nil { - return err - } - c.Log.Debugw("configuration backup", "path", bkpPath) - c.Event.AddFeatureField("backup_file", path.Base(bkpPath)) - - // store the migrated profile - err = lwconfig.StoreProfileAt(viper.ConfigFileUsed(), c.Profile, migratedProfile) - if err != nil { - return errors.Wrap(err, "unable to store migrated profile") - } - - c.Log.Debugw("configuration migrated successfully") - return nil -} - -func createConfigurationBackup() (string, error) { - profiles, err := lwconfig.LoadProfilesFrom(viper.ConfigFileUsed()) - if err != nil { - return "", err - } - - cacheDir, err := cache.CacheDir() - if err != nil { - return "", err - } - - backupDir := path.Join(cacheDir, ConfigBackupDir) - if err := os.MkdirAll(backupDir, 0755); err != nil { - return "", err - } - - backupCfgPath := path.Join(backupDir, - fmt.Sprintf(".lacework.toml.%s.%s.bkp", - time.Now().Format("20060102150405"), newID()), - ) - return backupCfgPath, lwconfig.StoreAt(backupCfgPath, profiles) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/outputs.go b/vendor/github.com/lacework/go-sdk/cli/cmd/outputs.go deleted file mode 100644 index 73b435f89..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/outputs.go +++ /dev/null @@ -1,150 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/csv" - "fmt" - "os" - "strings" - - "github.com/fatih/color" - "github.com/pkg/errors" - "gopkg.in/yaml.v2" -) - -// OutputJSON will print out the JSON representation of the provided data -func (c *cliState) OutputJSON(v interface{}) error { - pretty, err := c.JsonF.Marshal(v) - if err != nil { - c.Log.Debugw("unable to pretty print JSON object", "raw", v) - return err - } - fmt.Fprintln(color.Output, string(pretty)) - return nil -} - -// OutputChecklist prints out a message with an icon formatted as a checklist, -// note that all strings should come already colorized -func (c *cliState) OutputChecklist(icon, message string, a ...interface{}) { - c.OutputHuman(fmt.Sprintf(" [%s] %s", icon, message), a...) -} - -// OutputHuman will print out the provided message if the cli state is -// configured to talk to humans, to switch to json format use --json -func (c *cliState) OutputHuman(format string, a ...interface{}) { - if c.HumanOutput() { - if len(a) == 0 { - fmt.Fprint(color.Output, format) - } else { - fmt.Fprintf(color.Output, format, a...) - } - } -} - -// OutputHumanErr will print the msg if human output is enabled to stderr -func (c *cliState) OutputHumanErr(format string, a ...interface{}) { - if c.HumanOutput() { - if len(a) == 0 { - fmt.Fprint(color.Error, format) - } else { - fmt.Fprintf(color.Error, format, a...) - } - } -} - -// OutputJSONString is just like OutputJSON but from a JSON string -func (c *cliState) OutputJSONString(s string) error { - pretty, err := c.FormatJSONString(s) - if err != nil { - return err - } - fmt.Fprintln(color.Output, string(pretty)) - return nil -} - -// FormatJSONString formats a JSON string into a pretty JSON format -func (c *cliState) FormatJSONString(s string) (string, error) { - pretty, err := c.JsonF.Format([]byte(strings.Trim(s, "'"))) - if err != nil { - c.Log.Debugw("unable to pretty print JSON string", "raw", s, "error", err.Error()) - return "", err - } - return string(pretty), nil -} - -// Used to clean CSV inputs prior to rendering -func csvCleanData(input []string) []string { - var data []string - for _, h := range input { - data = append(data, strings.Replace(h, "\n", "", -1)) - } - return data -} - -// Used to produce CSV output -func renderAsCSV(headers []string, data [][]string) (string, error) { - csvOut := &strings.Builder{} - csv := csv.NewWriter(csvOut) - - if len(headers) > 0 { - if err := csv.Write(csvCleanData(headers)); err != nil { - return "", errors.Wrap(err, "Failed to build csv output") - } - } - - for _, record := range data { - if err := csv.Write(csvCleanData(record)); err != nil { - return "", errors.Wrap(err, "Failed to build csv output") - } - } - - // Write any buffered data to the underlying writer (standard output). - csv.Flush() - return csvOut.String(), csv.Error() -} - -// OutputCSV will print out the provided headers/data in CSV format -func (c *cliState) OutputCSV(headers []string, data [][]string) error { - csv, err := renderAsCSV(headers, data) - if err != nil { - return err - } - - fmt.Fprint(os.Stdout, csv) - return nil -} - -func (c *cliState) OutputNonDefaultProfileFlag() string { - if c.Profile != "default" { - return fmt.Sprintf(" --profile %s", c.Profile) - } - return "" -} - -// OutputYAML will print out the YAML representation of the provided data -func (c *cliState) OutputYAML(v interface{}) error { - y, err := yaml.Marshal(v) - if err != nil { - c.Log.Debugw("unable to pretty print YAML object", "raw", v) - return err - } - fmt.Fprint(os.Stdout, string(y)) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/package_manifest.go b/vendor/github.com/lacework/go-sdk/cli/cmd/package_manifest.go deleted file mode 100644 index 5fe900f5e..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/package_manifest.go +++ /dev/null @@ -1,560 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "bufio" - "encoding/json" - "fmt" - "os" - "os/exec" - "regexp" - "runtime" - "strings" - "syscall" - "time" - - "github.com/pkg/errors" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/file" -) - -var SupportedPackageManagers = []string{"dpkg-query", "rpm"} // @afiune can we support yum and apk? - -type OS struct { - Name string - Version string -} - -var ( - procUAStatusFile = "/proc/1/root/var/lib/ubuntu-advantage/status.json" - osReleaseFile = "/etc/os-release" - sysReleaseFile = "/etc/system-release" - rexNameFromID = regexp.MustCompile(`^ID=(.*)$`) - rexVersionID = regexp.MustCompile(`^VERSION_ID=(.*)$`) -) - -func (c *cliState) GeneratePackageManifest() (*api.VulnerabilitiesPackageManifest, error) { - var ( - err error - start = time.Now() - ) - - defer func() { - c.Event.DurationMs = time.Since(start).Milliseconds() - if err == nil { - // if this function returns an error, most likely, - // the command will send a honeyvent with that error, - // therefore we should duplicate events and only send - // one here if there is NO error - c.SendHoneyvent() - } - }() - - c.Event.Feature = featGenPkgManifest - - manifest := new(api.VulnerabilitiesPackageManifest) - osInfo, err := c.GetOSInfo() - if err != nil { - return manifest, err - } - - if osInfo.Name == "ubuntu" { - // ESM support - if c.IsEsmEnabled() { - osInfo.Version += "esm" - } - } - - c.Event.AddFeatureField("os", osInfo.Name) - c.Event.AddFeatureField("os_ver", osInfo.Version) - - manager, err := c.DetectPackageManager() - if err != nil { - return manifest, err - } - c.Event.AddFeatureField("pkg_manager", manager) - - var managerQuery []byte - switch manager { - case "rpm": - managerQuery, err = exec.Command( - "rpm", "-qa", "--queryformat", "%{NAME},%|EPOCH?{%{EPOCH}}:{0}|:%{VERSION}-%{RELEASE}\n", - ).Output() - if err != nil { - return manifest, errors.Wrap(err, "unable to query packages from package manager") - } - case "dpkg-query": - managerQuery, err = exec.Command( - "dpkg-query", "--show", "--showformat", "${Package},${Version}\n", - ).Output() - if err != nil { - return manifest, errors.Wrap(err, "unable to query packages from package manager") - } - case "yum": - return manifest, errors.New("yum not yet supported") - case "apk": - apkInfo, err := exec.Command("apk", "info").Output() - if err != nil { - return manifest, errors.Wrap(err, "unable to query packages from package manager") - } - apkInfoT := strings.TrimSuffix(string(apkInfo), "\n") - apkInfoArray := strings.Split(apkInfoT, "\n") - - apkInfoWithVersion, err := exec.Command("apk", "info", "-v").Output() - if err != nil { - return manifest, errors.Wrap(err, "unable to query packages from package manager") - } - apkInfoWithVersionT := strings.TrimSuffix(string(apkInfoWithVersion), "\n") - apkInfoWithVersionArray := strings.Split(apkInfoWithVersionT, "\n") - - mq := []string{} - for i, pkg := range apkInfoWithVersionArray { - mq = append(mq, - fmt.Sprintf("%s,%s", - apkInfoArray[i], - strings.Trim( - strings.Replace(pkg, apkInfoArray[i], "", 1), - "-", - ), - ), - ) - } - managerQuery = []byte(strings.Join(mq, "\n")) - default: - return manifest, errors.New( - "this is most likely a mistake on us, please report it to support@lacework.com.", - ) - } - - c.Log.Debugw("package-manager query", "raw", string(managerQuery)) - - // @afiune this is an example of the output from the query we - // send to the local package-manager: - // - // {PkgName},{PkgVersion}\n - // ... - // {PkgName},{PkgVersion}\n - // - // first, trim the last carriage return - managerQueryOut := strings.TrimSuffix(string(managerQuery), "\n") - // then, split by carriage return - for _, pkg := range strings.Split(managerQueryOut, "\n") { - // finally, split by comma to get PackageName and PackageVersion - pkgDetail := strings.Split(pkg, ",") - - // the splitted package detail must be size of 2 elements - if len(pkgDetail) != 2 { - c.Log.Warnw("unable to parse package, expected length=2, skipping", - "raw_pkg_details", pkg, - "split_pkg_details", pkgDetail, - ) - continue - } - - manifest.OsPkgInfoList = append(manifest.OsPkgInfoList, - api.VulnerabilitiesOsPkgInfo{ - Os: osInfo.Name, - OsVer: osInfo.Version, - Pkg: pkgDetail[0], - PkgVer: pkgDetail[1], - }, - ) - } - - c.Event.AddFeatureField("total_manifest_pkgs", len(manifest.OsPkgInfoList)) - c.Log.Debugw("package-manifest", "raw", manifest) - return c.removeInactivePackagesFromManifest(manifest, manager), nil -} - -func (c *cliState) removeInactivePackagesFromManifest( - manifest *api.VulnerabilitiesPackageManifest, manager string, -) *api.VulnerabilitiesPackageManifest { - // Detect Active Kernel - // - // The default behavior of most linux distros is to keep the last N kernel packages - // installed for users that need to fallback in case the new kernel do not boot. - // However, the presence of the package does not mean that kernel is active. - // We must continue to allow the standard kernel package preservation behavior - // without providing false-positives of vulnerabilities that are not active. - // - // We will try to detect the active kernel and remove any other installed-inactive - // kernel from the generated package manifest - activeKernel, detected := c.detectActiveKernel() - c.Event.AddFeatureField("active_kernel", activeKernel) - if !detected { - return manifest - } - - newManifest := new(api.VulnerabilitiesPackageManifest) - for i, pkg := range manifest.OsPkgInfoList { - - switch manager { - case "rpm": - kernelPkgName := "kernel" - pkgVer := removeEpochFromPkgVersion(pkg.PkgVer) - if pkg.Pkg == kernelPkgName && !strings.Contains(activeKernel, pkgVer) { - // this package is NOT the active kernel - c.Log.Warnw("inactive kernel package detected, removing from generated pkg manifest", - "pkg_name", kernelPkgName, - "pkg_version", pkg.PkgVer, - "active_kernel", activeKernel, - ) - c.Event.AddFeatureField( - fmt.Sprintf("kernel_suppressed_%d", i), - fmt.Sprintf("%s-%s", pkg.Pkg, pkg.PkgVer)) - continue - } - case "dpkg-query": - kernelPkgName := "linux-image-" - if strings.Contains(pkg.Pkg, kernelPkgName) { - // this is a kernel package, trim the package name prefix to get the version - kernelVer := strings.TrimPrefix(pkg.Pkg, kernelPkgName) - - if !strings.Contains(activeKernel, kernelVer) { - // this package is NOT the active kernel - c.Log.Warnw("inactive kernel package detected, removing from generated pkg manifest", - "pkg_name", kernelPkgName, - "pkg_version", pkg.PkgVer, - "active_kernel", activeKernel, - ) - c.Event.AddFeatureField( - fmt.Sprintf("kernel_suppressed_%d", i), - fmt.Sprintf("%s-%s", pkg.Pkg, pkg.PkgVer)) - continue - } - } - } - - newManifest.OsPkgInfoList = append(newManifest.OsPkgInfoList, pkg) - } - - if len(manifest.OsPkgInfoList) != len(newManifest.OsPkgInfoList) { - c.Log.Debugw("package-manifest modified", "raw", newManifest) - } - return newManifest -} - -func (c *cliState) detectActiveKernel() (string, bool) { - kernel, err := exec.Command("uname", "-r").Output() - fmt.Println(kernel) - if err != nil { - fmt.Println("nooooooope") - fmt.Println(err) - c.Log.Warnw("unable to detect active kernel", - "cmd", "uname -r", - "error", err, - ) - return "", false - } - return strings.TrimSuffix(string(kernel), "\n"), true -} - -func (c *cliState) IsEsmEnabled() bool { - type uaStatusFile struct { - SchemaVersion string `json:"_schema_version,omitempty"` - Status string `json:"execution_status,omitempty"` - Services []struct { - Name string `json:"name,omitempty"` - Status string `json:"status,omitempty"` - } `json:"services,omitempty"` - } - - if file.FileExists(procUAStatusFile) { - c.Log.Debugw("detecting ubuntu ESM support", "file", procUAStatusFile) - uaStatusBytes, err := os.ReadFile(procUAStatusFile) - if err != nil { - c.Log.Warnw("unable to read UA status file", "error", err) - return false - } - - var uaStatus uaStatusFile - if err = json.Unmarshal(uaStatusBytes, &uaStatus); err != nil { - c.Log.Warnw("unable to unmarshal UA status file", "error", err) - return false - } - - for _, svc := range uaStatus.Services { - if strings.Contains(svc.Name, "esm") && svc.Status == "enabled" { - c.Log.Debug("ESM is enabled") - return true - } - } - - c.Log.Debug("no UA service enabled") - return false - } - - c.Log.Warnw("unable to detect ubuntu ESM support, file not found", "file", procUAStatusFile) - return false -} - -func (c *cliState) GetOSInfo() (*OS, error) { - osInfo := new(OS) - - c.Log.Debugw("detecting operating system information", - "os", runtime.GOOS, - "arch", runtime.GOARCH, - ) - - if file.FileExists(osReleaseFile) { - c.Log.Debugw("parsing os release file", "file", osReleaseFile) - return openOsReleaseFile(osReleaseFile) - } - - if file.FileExists(sysReleaseFile) { - c.Log.Debugw("parsing system release file", "file", sysReleaseFile) - return openSystemReleaseFile(sysReleaseFile) - } - - msg := `unsupported platform - -For more information about supported platforms, visit: - https://docs.lacework.com/host-vulnerability-assessment-overview` - return osInfo, errors.New(msg) -} - -func openSystemReleaseFile(filename string) (*OS, error) { - osInfo := new(OS) - - f, err := os.Open(filename) - - if err != nil { - return osInfo, err - } - - defer f.Close() - - s := bufio.NewScanner(f) - for s.Scan() { - m := strings.Split(s.Text(), " ") - if len(m) > 0 { - osInfo.Name = strings.ToLower(m[0]) - osInfo.Version = strings.ToLower(m[2]) - break - } - } - - return osInfo, err -} - -func openOsReleaseFile(filename string) (*OS, error) { - osInfo := new(OS) - - f, err := os.Open(filename) - if err != nil { - return osInfo, err - } - - defer f.Close() - - s := bufio.NewScanner(f) - for s.Scan() { - if m := rexNameFromID.FindStringSubmatch(s.Text()); m != nil { - osInfo.Name = strings.Trim(m[1], `"`) - } else if m := rexVersionID.FindStringSubmatch(s.Text()); m != nil { - osInfo.Version = strings.Trim(m[1], `"`) - } - } - - return osInfo, err -} - -func (c *cliState) DetectPackageManager() (string, error) { - c.Log.Debugw("detecting package-manager") - - for _, manager := range SupportedPackageManagers { - if c.checkPackageManager(manager) { - c.Log.Debugw("detected", "package-manager", manager) - return manager, nil - } - } - msg := "unable to find supported package managers." - msg = fmt.Sprintf("%s Supported package managers are %s.", - msg, strings.Join(SupportedPackageManagers, ", ")) - return "", errors.New(msg) -} - -func (c *cliState) checkPackageManager(manager string) bool { - var ( - cmd = exec.Command("which", manager) - _, err = cmd.CombinedOutput() - ) - if err != nil { - c.Log.Debugw("error trying to check package-manager", - "cmd", "which", - "package-manager", manager, - "error", err, - ) - if exitError, ok := err.(*exec.ExitError); ok { - waitStatus := exitError.Sys().(syscall.WaitStatus) - return waitStatus.ExitStatus() == 0 - } - c.Log.Warnw("something went wrong with 'which', trying native command") - return c.checkPackageManagerWithNativeCommand(manager) - } - waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus) - return waitStatus.ExitStatus() == 0 -} - -func (c *cliState) checkPackageManagerWithNativeCommand(manager string) bool { - var ( - cmd = exec.Command("command", "-v", manager) - _, err = cmd.CombinedOutput() - ) - if err != nil { - c.Log.Debugw("error trying to check package-manager", - "cmd", "command", - "package-manager", manager, - "error", err, - ) - if exitError, ok := err.(*exec.ExitError); ok { - waitStatus := exitError.Sys().(syscall.WaitStatus) - return waitStatus.ExitStatus() == 0 - } - return false - } - waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus) - return waitStatus.ExitStatus() == 0 -} - -func removeEpochFromPkgVersion(pkgVer string) string { - if strings.Contains(pkgVer, ":") { - pkgVerSplit := strings.Split(pkgVer, ":") - if len(pkgVerSplit) == 2 { - return pkgVerSplit[1] - } - } - - return pkgVer -} - -// split the provided package_manifest into chucks, if the manifest -// is smaller than the provided chunk size, it will return the manifest -// as an array without modifications -func splitPackageManifest( - manifest *api.VulnerabilitiesPackageManifest, chunks int, -) []*api.VulnerabilitiesPackageManifest { - if len(manifest.OsPkgInfoList) <= chunks { - return []*api.VulnerabilitiesPackageManifest{manifest} - } - - var batches []*api.VulnerabilitiesPackageManifest - for i := 0; i < len(manifest.OsPkgInfoList); i += chunks { - batch := manifest.OsPkgInfoList[i:min(i+chunks, len(manifest.OsPkgInfoList))] - cli.Log.Infow("manifest batch", "total_packages", len(batch)) - batches = append(batches, &api.VulnerabilitiesPackageManifest{OsPkgInfoList: batch}) - } - return batches -} - -func min(a, b int) int { - if a <= b { - return a - } - return b -} - -// fan-out a number of package manifests into multiple requests all at once -func fanOutHostScans(manifests ...*api.VulnerabilitiesPackageManifest) ( - api.VulnerabilitySoftwarePackagesResponse, error, -) { - var ( - resCh = make(chan api.VulnerabilitySoftwarePackagesResponse) - errCh = make(chan error) - workers = len(manifests) - fanInRes = api.VulnerabilitySoftwarePackagesResponse{} - ) - - // disallow more than 10 workers which are 10 calls all at once, - // the API has a rate-limit of 10 calls per hour, per access key - if workers > 10 { - return fanInRes, errors.New("limit of packages exceeded") - } - - var ( - err error - start = time.Now() - ) - defer func() { - cli.Event.DurationMs = time.Since(start).Milliseconds() - // avoid duplicating events - if err == nil { - cli.SendHoneyvent() - } - }() - - // ensure that the api client has a valid token - // before creating workers - if !cli.LwApi.ValidAuth() { - _, err = cli.LwApi.GenerateToken() - if err != nil { - return fanInRes, err - } - } - - // for every manifest, create a new worker, that is, spawn - // a new goroutine that will send the manifest to scan - for n, m := range manifests { - if m == nil { - workers-- - continue - } - cli.Log.Infow("spawn worker", "number", n+1) - go cli.triggerHostVulnScan(m, resCh, errCh) - } - - cli.Event.AddFeatureField("workers", workers) - - // lock the main process and read both, the error and response - // channels, if we receive at least one error, we will stop - // processing and bubble up the error to the caller - for processed := 0; processed < workers; processed++ { - select { - case err = <-errCh: - // end processing as soon as we receive the first error - return fanInRes, err - case res := <-resCh: - // processing scan - cli.Log.Infow("processing worker response", "n", processed+1) - cli.Event.AddFeatureField(fmt.Sprintf("worker%d_total_vulns", processed), len(res.Data)) - mergeHostVulnScanPkgManifestResponses(&fanInRes, &res) - } - } - - return fanInRes, nil -} - -func mergeHostVulnScanPkgManifestResponses(to, from *api.VulnerabilitySoftwarePackagesResponse) { - // append vulnerabilities from -> to - to.Data = append(to.Data, from.Data...) -} - -func (c *cliState) triggerHostVulnScan(manifest *api.VulnerabilitiesPackageManifest, - resCh chan<- api.VulnerabilitySoftwarePackagesResponse, - errCh chan<- error, -) { - response, err := c.LwApi.V2.Vulnerabilities.SoftwarePackages.Scan(*manifest) - if err != nil { - errCh <- err - return - } - resCh <- response -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy.go deleted file mode 100644 index 45714c80f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/policy.go +++ /dev/null @@ -1,807 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "io" - "net/http" - "os" - "regexp" - "sort" - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/pointer" - "github.com/lacework/go-sdk/lwseverity" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "gopkg.in/yaml.v3" -) - -var ( - policyCmdState = struct { - AlertEnabled bool - Enabled bool - File string - Severity string - Tag string - State *bool - URL string - CUFromLibrary string - CascadeDelete bool - }{} - - policyTableHeaders = []string{ - "Policy ID", - "Severity", - "Title", - "State", - "Alert State", - "Frequency", - "Tags", - } - - // policyCmd represents the policy parent command - policyCmd = &cobra.Command{ - Use: "policy", - Aliases: []string{"policies"}, - Short: "Manage policies", - Long: `Manage policies in your Lacework account. - -Policies add annotated metadata to queries for improving the context of alerts, -reports, and information displayed in the Lacework Console. - -Policies also facilitate the scheduled execution of Lacework queries. - -Queries let you interactively request information from specified -curated datasources. Queries have a defined structure for authoring detections. - -Lacework ships a set of default LQL policies that are available in your account. - -Limitations: - * The maximum number of records that each policy will return is 1000 - * The maximum number of API calls is 120 per hour for on-demand LQL query executions - -To view all the policies in your Lacework account. - - lacework policy ls - -To view more details about a single policy. - - lacework policy show - -To view the LQL query associated with the policy, use the query ID. - - lacework query show - -**Note: LQL syntax may change.** -`, - } - - // policyListCmd represents the policy list command - policyListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all policies", - Long: `List all registered policies in your Lacework account.`, - Args: cobra.NoArgs, - PreRunE: func(_ *cobra.Command, _ []string) error { - if policyCmdState.Severity != "" && - !lwseverity.IsValid(policyCmdState.Severity) { - return errors.Errorf("the severity %s is not valid, use one of %s", - policyCmdState.Severity, lwseverity.ValidSeverities.String(), - ) - } - return nil - }, - RunE: listPolicies, - } - // policyListTagsCmd represents the policy list command - policyListTagsCmd = &cobra.Command{ - Use: "list-tags", - Aliases: []string{"ls"}, - Short: "List policy tags", - Long: `List all tags associated with policies in your Lacework account.`, - Args: cobra.NoArgs, - RunE: listPolicyTags, - } - // policyShowCmd represents the policy show command - policyShowCmd = &cobra.Command{ - Use: "show ", - Aliases: []string{"ls"}, - Short: "Show details about a policy", - Long: `Show details about the provided policy ID.`, - Args: cobra.ExactArgs(1), - PreRunE: func(cmd *cobra.Command, _ []string) error { - b, err := cmd.Flags().GetBool("yaml") - if err != nil { - return errors.Wrap(err, "unable to parse --yaml flag") - } - if b { - cli.EnableYAMLOutput() - } - return nil - }, - RunE: showPolicy, - } - policyIDIntRE = regexp.MustCompile(`^(.*-)(\d+)$`) -) - -func init() { - // add the policy command - rootCmd.AddCommand(policyCmd) - - // add sub-commands to the policy command - policyCmd.AddCommand(policyListCmd) - policyCmd.AddCommand(policyListTagsCmd) - policyCmd.AddCommand(policyShowCmd) - - // Lacework Content Library - if cli.isLCLInstalled() { - policyCreateCmd.Flags().StringVarP( - &policyCmdState.CUFromLibrary, - "library", "l", "", - "create policy from Lacework Content Library", - ) - policyUpdateCmd.Flags().StringVarP( - &policyCmdState.CUFromLibrary, - "library", "l", "", - "update policy from Lacework Content Library", - ) - } - - // policy list specific flags - policyListCmd.Flags().StringVar( - &policyCmdState.Severity, - "severity", "", - fmt.Sprintf("filter policies by severity threshold (%s)", - strings.Join(api.ValidPolicySeverities, ", ")), - ) - policyListCmd.Flags().BoolVar( - &policyCmdState.Enabled, - "enabled", false, "only show enabled policies", - ) - policyListCmd.Flags().BoolVar( - &policyCmdState.AlertEnabled, - "alert_enabled", false, "only show alert_enabled policies", - ) - policyListCmd.Flags().StringVar( - &policyCmdState.Tag, - "tag", "", "only show policies with the specified tag", - ) - // policy show specific flags - policyShowCmd.Flags().Bool( - "yaml", false, "output query in YAML format", - ) -} - -func setPolicySourceFlags(cmds ...*cobra.Command) { - for _, cmd := range cmds { - if cmd == nil { - return - } - action := strings.Split(cmd.Use, " ")[0] - - // file flag to specify a policy from disk - cmd.Flags().StringVarP( - &policyCmdState.File, - "file", "f", "", - fmt.Sprintf("path to a policy to %s", action), - ) - /* repo flag to specify a policy from repo - cmd.Flags().BoolVarP( - &policyCmdState.Repo, - "repo", "r", false, - fmt.Sprintf("id of a policy to %s via active repo", action), - )*/ - // url flag to specify a policy from url - cmd.Flags().StringVarP( - &policyCmdState.URL, - "url", "u", "", - fmt.Sprintf("url to a policy to %s", action), - ) - } -} - -// for commands that take a policy as input -func inputPolicy(cmd *cobra.Command, args ...string) (string, error) { - // if running via library (CU) - if policyCmdState.CUFromLibrary != "" { - return inputPolicyFromLibrary(policyCmdState.CUFromLibrary) - } - // if running via file - if policyCmdState.File != "" { - return inputPolicyFromFile(policyCmdState.File) - } - // if running via URL - if policyCmdState.URL != "" { - return inputPolicyFromURL(policyCmdState.URL) - } - stat, err := os.Stdin.Stat() - if err != nil { - cli.Log.Debugw("error retrieving stdin mode", "error", err.Error()) - } else if (stat.Mode() & os.ModeCharDevice) == 0 { - bytes, err := io.ReadAll(os.Stdin) - return string(bytes), err - } - // if running via editor - action := strings.Split(cmd.Use, " ")[0] - - if action == "create" { - return inputPolicyFromEditor(action, "") - } - - policyYaml, err := fetchExistingPolicy(args) - if err != nil { - return "", err - } - return inputPolicyFromEditor(action, policyYaml) - -} - -func fetchExistingPolicy(args []string) (string, error) { - var policyID string - - if len(args) > 0 && len(args[0]) > 0 { - policyID = args[0] - } else { - if err := promptSetPolicyID(&policyID); err != nil { - return "", err - } - } - - cli.StartProgress(fmt.Sprintf("Retrieving policy '%s'...", policyID)) - policy, err := cli.LwApi.V2.Policy.Get(policyID) - cli.StopProgress() - if err != nil { - return "", errors.Wrap(err, fmt.Sprintf("unable to retrieve %s", policyID)) - } - - policyYaml, err := yaml.Marshal(policy.Data) - if err != nil { - return "", errors.Wrap(err, fmt.Sprintf("unable to yaml marshall %s", policyID)) - } - return string(policyYaml), nil -} - -func inputPolicyFromLibrary(id string) (string, error) { - var ( - lcl *LaceworkContentLibrary - err error - ) - - if lcl, err = cli.LoadLCL(); err != nil { - return "", err - } - return lcl.GetPolicy(id) -} - -func inputPolicyFromFile(filePath string) (string, error) { - fileData, err := os.ReadFile(filePath) - - if err != nil { - return "", errors.Wrap(err, "unable to read file") - } - - return string(fileData), nil -} - -func inputPolicyFromURL(url string) (policy string, err error) { - msg := "unable to access URL" - - response, err := http.Get(url) - if err != nil { - err = errors.Wrap(err, msg) - return - } - defer response.Body.Close() - - if response.StatusCode != 200 { - err = errors.Wrap(errors.New(response.Status), msg) - return - } - - body, err := io.ReadAll(response.Body) - if err != nil { - err = errors.Wrap(err, msg) - return - } - policy = string(body) - return -} - -func inputPolicyFromEditor(action string, policyYaml string) (policy string, err error) { - prompt := &survey.Editor{ - Message: fmt.Sprintf("Use the editor to %s your policy", action), - FileName: "policy*.yaml", - HideDefault: true, - AppendDefault: true, - Default: policyYaml, - } - - err = survey.AskOne(prompt, &policy) - return -} - -func promptSetPolicyID(policyID *string) error { - cli.StartProgress("Retrieving policies...") - policiesResponse, err := cli.LwApi.V2.Policy.List() - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to retrieve policies to select") - } - - var policyIds []string - for _, policy := range policiesResponse.Data { - policyIds = append(policyIds, policy.PolicyID) - } - - return survey.AskOne(&survey.Select{ - Message: "Select policy to update:", - Options: policyIds, - }, policyID) -} - -func promptSetPolicyIDs() ([]string, error) { - cli.StartProgress("Retrieving policies...") - policiesResponse, err := cli.LwApi.V2.Policy.List() - cli.StopProgress() - if err != nil { - return nil, errors.Wrap(err, "unable to retrieve policies to select") - } - - var policyIDs []string - for _, policy := range policiesResponse.Data { - // exclude manual PolicyTypes and exclude policies whose state matches the new state - // eg. do not show already enabled policyIDs when running 'lacework policy enable' - if policy.PolicyType != api.PolicyTypeManual.String() { - if pointer.CompareBoolPtr(policyCmdState.State, policy.Enabled) { - continue - } - policyIDs = append(policyIDs, policy.PolicyID) - } - } - - var response []string - err = survey.AskOne(&survey.MultiSelect{ - Message: "Select which policy ids to update:", - Options: policyIDs, - }, &response) - - if err != nil { - return nil, err - } - - return response, nil -} - -func sortPolicyTable(out [][]string, policyIDIndex int) { - // order by ID (special handling for policy ID numbers) - sort.Slice(out, func(i, j int) bool { - iMatch := policyIDIntRE.FindStringSubmatch(out[i][policyIDIndex]) - jMatch := policyIDIntRE.FindStringSubmatch(out[j][policyIDIndex]) - // both regexes must match - // both regexes must have proper lengths since we'll be using... - // ...direct access from here on out - if iMatch == nil || jMatch == nil || len(iMatch) != 3 || len(jMatch) != 3 { - return out[i][policyIDIndex] < out[j][policyIDIndex] - } - // if string portions aren't the same - if iMatch[1] != jMatch[1] { - return out[i][policyIDIndex] < out[j][policyIDIndex] - } - // if string portions are the same; compare based on ints - // no error checking needed for Atoi since use regexp \d+ - iNum, _ := strconv.Atoi(iMatch[2]) - jNum, _ := strconv.Atoi(jMatch[2]) - return iNum < jNum - }) -} - -func policyTable(policies []api.Policy) (out [][]string) { - for _, policy := range policies { - state := "Disabled" - if policy.Enabled { - state = "Enabled" - } - alertState := "Disabled" - if policy.AlertEnabled { - alertState = "Enabled" - } - out = append(out, []string{ - policy.PolicyID, - policy.Severity, - policy.Title, - state, - alertState, - policy.EvalFrequency, - strings.Join(policy.Tags, ", "), - }) - } - sortPolicyTable(out, 0) - - return -} - -func filterPolicies(policies []api.Policy) []api.Policy { - newPolicies := []api.Policy{} - - for _, policy := range policies { - // filter severity if desired - if lwseverity.ShouldFilter(policy.Severity, policyCmdState.Severity) { - continue - } - // filter enabled=false if requesting "enabled-only" - if policyCmdState.Enabled && !policy.Enabled { - continue - } - // filter alert_enabled=false if requesting "alert_enabled-only" - if policyCmdState.AlertEnabled && !policy.AlertEnabled { - continue - } - // filter tag - if policyCmdState.Tag != "" && !policy.HasTag(policyCmdState.Tag) { - continue - } - newPolicies = append(newPolicies, policy) - } - return newPolicies -} - -func listPolicies(_ *cobra.Command, _ []string) error { - cli.Log.Info("listing policies") - cli.StartProgress("Retrieving policies...") - policiesResponse, err := cli.LwApi.V2.Policy.List() - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to list policies") - } - - cli.Log.Infow("total policies", "count", len(policiesResponse.Data)) - policies := filterPolicies(policiesResponse.Data) - if cli.JSONOutput() { - return cli.OutputJSON(policies) - } - - if len(policies) == 0 { - cli.OutputHuman("There were no policies found.") - } else { - cli.OutputHuman(renderCustomTable(policyTableHeaders, policyTable(policies), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetRowLine(true) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(true) - }), - )) - - if policyCmdState.Tag == "" { - cli.OutputHuman( - "\nTry using '--tag ' to only show policies with the specified tag.\n", - ) - } else if policyCmdState.Severity == "" { - cli.OutputHuman( - "\nTry using '--severity ' to filter policies by severity threshold.\n", - ) - } - } - return nil -} - -func showPolicy(_ *cobra.Command, args []string) error { - var ( - msg string = "unable to show policy" - policyResponse api.PolicyResponse - err error - ) - - cli.Log.Infow("retrieving policy", "id", args[0]) - cli.StartProgress("Retrieving policy...") - policyResponse, err = cli.LwApi.V2.Policy.Get(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, msg) - } - - if cli.JSONOutput() { - return cli.OutputJSON(policyResponse.Data) - } - - if cli.YAMLOutput() { - return cli.OutputYAML(&api.Policy{ - PolicyID: policyResponse.Data.PolicyID, - PolicyType: policyResponse.Data.PolicyType, - QueryID: policyResponse.Data.QueryID, - Title: policyResponse.Data.Title, - Enabled: policyResponse.Data.Enabled, - Description: policyResponse.Data.Description, - Remediation: policyResponse.Data.Remediation, - Severity: policyResponse.Data.Severity, - Limit: policyResponse.Data.Limit, - EvalFrequency: policyResponse.Data.EvalFrequency, - AlertEnabled: policyResponse.Data.AlertEnabled, - AlertProfile: policyResponse.Data.AlertProfile, - Tags: policyResponse.Data.Tags, - }) - } - - cli.OutputHuman(renderSimpleTable( - policyTableHeaders, policyTable([]api.Policy{policyResponse.Data}), - )) - cli.OutputHuman("\n") - cli.OutputHuman(buildPolicyDetailsTable(policyResponse.Data)) - cli.OutputHuman("\n") - cli.OutputHuman(renderOneLineCustomTable("DESCRIPTION", - policyResponse.Data.Description, - tableFunc(func(t *tablewriter.Table) { - t.SetAlignment(tablewriter.ALIGN_LEFT) - t.SetColWidth(120) - t.SetBorder(false) - t.SetAutoWrapText(true) - }), - )) - cli.OutputHuman("\n") - cli.OutputHuman(renderOneLineCustomTable("REMEDIATION", - policyResponse.Data.Remediation, - tableFunc(func(t *tablewriter.Table) { - t.SetAlignment(tablewriter.ALIGN_LEFT) - t.SetColWidth(120) - t.SetBorder(false) - t.SetAutoWrapText(true) - t.SetReflowDuringAutoWrap(false) - }), - )) - cli.OutputHuman("\n") - - if policyResponse.Data.QueryID != "" { - cli.StartProgress("Retrieving query...") - queryResponse, err := cli.LwApi.V2.Query.Get(policyResponse.Data.QueryID) - cli.StopProgress() - if err != nil { - // something went wrong trying to fetch the LQL query, since this is not - // the main purpose of this command, we don't error out but instead, log - // the error and show breadcrumbs to manually fetch the query - cli.Log.Warnw("unable to get query", "error", err) - cli.OutputHuman( - fmt.Sprintf( - "\nUse 'lacework query show %s' to see the query used by this policy.\n", - policyResponse.Data.QueryID, - ), - ) - } - // we know we are in human-readable format - cli.OutputHuman(renderOneLineCustomTable("QUERY TEXT", - queryResponse.Data.QueryText, - tableFunc(func(t *tablewriter.Table) { - t.SetAlignment(tablewriter.ALIGN_LEFT) - t.SetColWidth(120) - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - )) - cli.OutputHuman("\n") - } - return nil -} - -func buildPolicyDetailsTable(policy api.Policy) string { - details := [][]string{ - {"QUERY ID", policy.QueryID}, - {"POLICY TYPE", policy.PolicyType}, - {"LIMIT", fmt.Sprintf("%d", policy.Limit)}, - {"ALERT PROFILE", policy.AlertProfile}, - {"TAGS", strings.Join(policy.Tags, "\n")}, - {"OWNER", policy.Owner}, - {"UPDATED AT", policy.LastUpdateTime}, - {"UPDATED BY", policy.LastUpdateUser}, - {"EVALUATION FREQUENCY", policy.EvalFrequency}, - } - // Append VALID EXCEPTION CONSTRAINTS to the table - // Add "None" when ExceptionConfiguration is empty - exceptionConstraints := strings.Join( - getPolicyExceptionConstraintsSlice(policy.ExceptionConfiguration), ", ") - if exceptionConstraints == "" { - exceptionConstraints = "None" - } - entry := []string{"VALID EXCEPTION CONSTRAINTS", exceptionConstraints} - details = append(details, entry) - - return renderOneLineCustomTable("POLICY DETAILS", - renderCustomTable([]string{}, details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ) -} - -func getPolicyExceptionConstraintsSlice(exceptionConfiguration map[string][]api. - PolicyExceptionConfigurationConstraints) []string { - var exceptionConstraints []string - constraintFields := exceptionConfiguration["constraintFields"] - for _, constraint := range constraintFields { - exceptionConstraints = append(exceptionConstraints, constraint.FieldKey) - } - return exceptionConstraints -} - -func policyTagsTable(pt []string) (out [][]string) { - for _, tag := range pt { - out = append(out, []string{tag}) - } - - // order by Tag - sort.Slice(out, func(i, j int) bool { - return out[i][0] < out[j][0] - }) - - return -} - -func listPolicyTags(_ *cobra.Command, args []string) error { - cli.Log.Debugw("listing policy tags") - - cli.StartProgress("Retrieving policy tags...") - policyTagsResponse, err := cli.LwApi.V2.Policy.ListTags() - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to list policy tags") - } - - if cli.JSONOutput() { - return cli.OutputJSON(policyTagsResponse.Data) - } - if len(policyTagsResponse.Data) == 0 { - cli.OutputHuman("There were no policy tags found.\n") - return nil - } - cli.OutputHuman(renderSimpleTable([]string{"Tag"}, policyTagsTable(policyTagsResponse.Data))) - return nil -} - -func setPoliciesState(_ *cobra.Command, args []string) error { - var ( - state = policyCmdState.State - msg = "enable" - ) - - if !*state { - msg = "disable" - } - - // if tag is provided enable/disable all policies matching the tag - if policyCmdState.Tag != "" { - return setPolicyStateByTag(policyCmdState.Tag, *state) - } - - var ( - bulkPolicies api.BulkUpdatePolicies - err error - ) - - if len(args) > 0 { - for _, policyID := range args { - bulkPolicies = append(bulkPolicies, api.BulkUpdatePolicy{PolicyID: policyID, Enabled: state}) - } - } - - // if no arguments are provided enter prompt - if len(args) == 0 { - bulkPolicies, err = promptSetPoliciesState() - if err != nil { - return err - } - } - - if len(bulkPolicies) == 0 { - cli.OutputHuman("unable to find policies to update\n") - return nil - } - - resp, err := cli.LwApi.V2.Policy.UpdateMany(bulkPolicies) - if err != nil { - return err - } - cli.Log.Debugw("bulk policy updated response:", resp) - cli.OutputHuman("%d policies have been %sd \n", len(bulkPolicies), msg) - - return nil -} - -func promptSetPoliciesState() (api.BulkUpdatePolicies, error) { - var ( - policyIDs []string - err error - ) - - if policyIDs, err = promptSetPolicyIDs(); err != nil { - return nil, err - } - - var bulkPolicies api.BulkUpdatePolicies - for _, policyID := range policyIDs { - state := true - bulkPolicies = append(bulkPolicies, api.BulkUpdatePolicy{PolicyID: policyID, Enabled: &state}) - } - - return bulkPolicies, nil -} - -func setPolicyStateByTag(tag string, policyState bool) error { - msg := "disable" - if policyState { - msg = "enable" - } - - cli.StartProgress("Retrieving policies...") - policyTagsResponse, err := cli.LwApi.V2.Policy.List() - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "unable to list policies") - } - - var ( - policiesUpdated []string - matchingPolicies []api.Policy - ) - - for _, p := range policyTagsResponse.Data { - if p.HasTag(tag) && p.Enabled != policyState { - matchingPolicies = append(matchingPolicies, p) - } - } - - if len(matchingPolicies) == 0 { - cli.OutputHuman("No policies found with tag '%s'\n", tag) - return nil - } - - // perform bulk update - var bulkPolicies api.BulkUpdatePolicies - for _, p := range matchingPolicies { - bulkPolicies = append(bulkPolicies, api.BulkUpdatePolicy{ - PolicyID: p.PolicyID, - Enabled: &policyState, - }) - } - - cli.StartProgress(fmt.Sprintf("%sing %d policies...", strings.TrimSuffix(msg, "e"), len(bulkPolicies))) - resp, err := cli.LwApi.V2.Policy.UpdateMany(bulkPolicies) - cli.StopProgress() - - if err != nil { - return errors.Wrapf(err, "failed to complete bulk %s.", msg) - } - - cli.Log.Debugw("bulk policy updated response:", resp) - cli.OutputHuman("%d policies tagged with %q have been %sd\n", len(policiesUpdated), tag, msg) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_create.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_create.go deleted file mode 100644 index 172253ce3..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_create.go +++ /dev/null @@ -1,147 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - // policyCreateCmd represents the policy create command - policyCreateCmd = &cobra.Command{ - Use: "create", - Short: "Create a policy", - Long: `Create a policy. - -A policy is represented in either JSON or YAML format. - -The following attributes are minimally required: - - --- - title: My Policy - enabled: false - policyType: Violation - alertEnabled: false - alertProfile: Alert_Profile_ID.Alert_Template_Name - evalFrequency: Daily - queryId: MyQuery - severity: high - description: My Policy Description - remediation: My Policy Remediation -`, - Args: cobra.NoArgs, - RunE: createPolicy, - } -) - -func init() { - // add sub-commands to the policy command - policyCmd.AddCommand(policyCreateCmd) - - // policy source specific flags - setPolicySourceFlags(policyCreateCmd) -} - -func createQueryFromLibrary(id string) error { - var ( - queryString string - err error - newQuery api.NewQuery - ) - - // input query - queryString, err = inputQueryFromLibrary(id) - if err != nil { - return err - } - - cli.Log.Debugw("creating query", "query", queryString) - - // parse query - newQuery, err = api.ParseNewQuery(queryString) - if err != nil { - return queryErrorCrumbs(queryString) - } - - // create query - _, err = cli.LwApi.V2.Query.Create(newQuery) - return err -} - -func createPolicy(cmd *cobra.Command, _ []string) error { - var ( - msg string = "unable to create policy" - err error - queryExists bool - ) - - // input policy - policyString, err := inputPolicy(cmd) - if err != nil { - return errors.Wrap(err, msg) - } - cli.Log.Debugw("creating policy", "policy", policyString) - - // parse policy - newPolicy, err := api.ParseNewPolicy(policyString) - if err != nil { - return errors.Wrap(err, msg) - } - - // if creating policy from library also create query - if policyCmdState.CUFromLibrary != "" { - cli.StartProgress(" Creating query (then policy)...") - err = createQueryFromLibrary(newPolicy.QueryID) - cli.StopProgress() - - if err != nil { - if queryExists = strings.Contains(err.Error(), "already exists"); !queryExists { - return errors.Wrap(err, "unable to create query") - } - } - } - - // create policy - cli.StartProgress(" Creating policy...") - createResponse, err := cli.LwApi.V2.Policy.Create(newPolicy) - cli.StopProgress() - - // output policy - if err != nil { - return errors.Wrap(err, msg) - } - if cli.JSONOutput() { - return cli.OutputJSON(createResponse.Data) - } - // if human output mode, creating from library, and query exists - if queryExists { - cli.OutputHuman(fmt.Sprintf("The query %s already exists.\n", newPolicy.QueryID)) - } - if policyCmdState.CUFromLibrary != "" { - cli.OutputHuman(fmt.Sprintf("The query %s was created.\n", newPolicy.QueryID)) - } - cli.OutputHuman(fmt.Sprintf("The policy %s was created.\n", createResponse.Data.PolicyID)) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_delete.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_delete.go deleted file mode 100644 index 124fc6a24..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_delete.go +++ /dev/null @@ -1,94 +0,0 @@ -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package cmd - -import ( - "fmt" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - // policyDeleteCmd represents the policy delete command - policyDeleteCmd = &cobra.Command{ - Use: "delete ", - Short: "Delete a policy", - Long: `Delete a policy by providing the policy ID. - -Use the command 'lacework policy list' to list the registered policies in -your Lacework account.`, - Args: cobra.ExactArgs(1), - RunE: deletePolicy, - } -) - -func init() { - // add sub-commands to the policy command - policyCmd.AddCommand(policyDeleteCmd) - - policyDeleteCmd.Flags().BoolVar( - &policyCmdState.CascadeDelete, - "cascade", false, "delete policy and its associated query", - ) -} - -func deletePolicy(_ *cobra.Command, args []string) error { - var ( - getResponse api.PolicyResponse - err error - queryID string - ) - - if policyCmdState.CascadeDelete { - cli.Log.Debugw("retrieving policy", "policyID", args[0]) - cli.StartProgress("Retrieving policy...") - getResponse, err = cli.LwApi.V2.Policy.Get(args[0]) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "unable to retrieve policy") - } - queryID = getResponse.Data.QueryID - } - - cli.Log.Debugw("deleting policy", "policyID", args[0]) - cli.StartProgress(" Deleting policy...") - _, err = cli.LwApi.V2.Policy.Delete(args[0]) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "unable to delete policy") - } - cli.OutputHuman( - fmt.Sprintf("The policy %s was deleted.\n", args[0]), - ) - // delete query - if policyCmdState.CascadeDelete { - cli.Log.Debugw("deleting query", "id", queryID) - cli.StartProgress(" Deleting query...") - _, err := cli.LwApi.V2.Query.Delete(queryID) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "unable to delete query") - } - cli.OutputHuman("The query %s was deleted.\n", queryID) - } - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_disable.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_disable.go deleted file mode 100644 index 6a1e8c4ec..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_disable.go +++ /dev/null @@ -1,71 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/aws/smithy-go/ptr" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // This is an experimental command. - // policyDisableTagCmd represents the policy disable command - policyDisableTagCmd = &cobra.Command{ - Use: "disable [policy_id...]", - Short: "Disable policies", - Long: `Disable policies by ID or all policies matching a tag. - -To disable a single policy by its ID: - - lacework policy disable lacework-policy-id - -To disable many policies by ID provide a list of policy ids: - - lacework policy disable lacework-policy-id-one lacework-policy-id-two - -To disable all policies for AWS CIS 1.4.0: - - lacework policy disable --tag framework:cis-aws-1-4-0 - -To disable all policies for GCP CIS 1.3.0: - - lacework policy disable --tag framework:cis-gcp-1-3-0 -`, - PreRunE: func(_ *cobra.Command, args []string) error { - if len(args) > 0 && policyCmdState.Tag != "" { - return errors.New("'--tag' flag may not be use in conjunction with 'policy_id' arg") - } - policyCmdState.State = ptr.Bool(false) - return nil - }, - RunE: setPoliciesState, - } -) - -func init() { - // add sub-commands to the policy command - policyCmd.AddCommand(policyDisableTagCmd) - - // policy disable specific flags - policyDisableTagCmd.Flags().StringVar( - &policyCmdState.Tag, - "tag", "", "disable all policies with the specified tag", - ) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_enable.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_enable.go deleted file mode 100644 index cae77b604..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_enable.go +++ /dev/null @@ -1,75 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/aws/smithy-go/ptr" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // policyEnableTagCmd represents the policy enable command - policyEnableTagCmd = &cobra.Command{ - Use: "enable [policy_id...]", - Short: "Enable policies", - Long: `Enable policies by ID or all policies matching a tag. - -To enter the policy enable prompt: - - lacework policy enable - -To enable a single policy by its ID: - - lacework policy enable lacework-policy-id - -To enable many policies by ID provide a list of policy ids: - - lacework policy enable lacework-policy-id-one lacework-policy-id-two - -To enable all policies for AWS CIS 1.4.0: - - lacework policy enable --tag framework:cis-aws-1-4-0 - -To enable all policies for GCP CIS 1.3.0: - - lacework policy enable --tag framework:cis-gcp-1-3-0 - -`, - PreRunE: func(_ *cobra.Command, args []string) error { - if len(args) > 0 && policyCmdState.Tag != "" { - return errors.New("'--tag' flag may not be use in conjunction with 'policy_id' arg") - } - policyCmdState.State = ptr.Bool(true) - return nil - }, - RunE: setPoliciesState, - } -) - -func init() { - // add sub-commands to the policy command - policyCmd.AddCommand(policyEnableTagCmd) - - // policy enable specific flags - policyEnableTagCmd.Flags().StringVar( - &policyCmdState.Tag, - "tag", "", "enable all policies with the specified tag", - ) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_exceptions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_exceptions.go deleted file mode 100644 index 0f31255ec..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_exceptions.go +++ /dev/null @@ -1,640 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" -) - -var ( - // policyExceptionCmd represents the policy parent command - policyExceptionCmd = &cobra.Command{ - Use: "policy-exception", - Aliases: []string{"policy-exceptions", "pe", "px"}, - Short: "Manage policy exceptions", - Long: `Manage policy exceptions in your Lacework account. - -To view all the policies in your Lacework account. - - lacework policy list -`, - } - - // policyExceptionListCmd represents the policy exception list command - policyExceptionListCmd = &cobra.Command{ - Use: "list ", - Aliases: []string{"ls"}, - Short: "List all exceptions from a single policy", - Long: `List all of the policy exceptions from the provided policy ID.`, - Args: cobra.ExactArgs(1), - RunE: listPolicyExceptions, - } - - // policyExceptionShowCmd represents the policy exception show command - policyExceptionShowCmd = &cobra.Command{ - Use: "show ", - Aliases: []string{"get"}, - Short: "Show details about a policy exception", - Long: `Show the details of a policy exception.`, - Args: cobra.ExactArgs(2), - RunE: showPolicyException, - } - - // policyExceptionDeleteCmd represents the policy exception delete command - policyExceptionDeleteCmd = &cobra.Command{ - Use: "delete ", - Aliases: []string{"rm"}, - Short: "Delete a policy exception", - Long: `Delete a policy exception. - -To remove a policy exception, run the delete command with policy ID and exception ID arguments: - - lacework policy-exception delete `, - Args: cobra.ExactArgs(2), - RunE: deletePolicyException, - } - - // policyExceptionCreateCmd represents the policy exception create command - policyExceptionCreateCmd = &cobra.Command{ - Use: "create [policy_id]", - Aliases: []string{"rm"}, - Short: "Create a policy exception", - Long: `Create a new policy exception. - -To create a new policy exception, run the command: - - lacework policy-exception create [policy_id] - -If you run the command without providing the policy_id, a -list of policies is displayed in an interactive prompt. -`, - Args: cobra.MaximumNArgs(1), - RunE: createPolicyException, - } -) - -func init() { - // add the policy exception command - rootCmd.AddCommand(policyExceptionCmd) - - // add sub-commands to the policy exception command - policyExceptionCmd.AddCommand(policyExceptionListCmd) - policyExceptionCmd.AddCommand(policyExceptionShowCmd) - policyExceptionCmd.AddCommand(policyExceptionDeleteCmd) - policyExceptionCmd.AddCommand(policyExceptionCreateCmd) -} - -func listPolicyExceptions(_ *cobra.Command, args []string) error { - if len(args) > 0 { - cli.StartProgress(fmt.Sprintf( - "Retrieving policy exceptions from policy ID '%s'...", args[0], - )) - policyExceptionResponse, err := cli.LwApi.V2.Policy.Exceptions.List(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrapf(err, "unable to list policy exceptions for ID %s", args[0]) - } - - if cli.JSONOutput() { - return cli.OutputJSON(policyExceptionResponse.Data) - } - - if len(policyExceptionResponse.Data) == 0 { - cli.OutputHuman("There were no policy exceptions found.\n") - return nil - } - - cli.OutputHuman(renderSimpleTable(policyExceptionTableHeaders, - policyExceptionTable(policyExceptionResponse.Data, args[0])), - ) - } - - return nil -} - -func showPolicyException(_ *cobra.Command, args []string) error { - var policyException api.PolicyExceptionResponse - cli.StartProgress(fmt.Sprintf( - "Fetching policy exception '%s' from policy '%s'...", args[0], args[1], - )) - err := cli.LwApi.V2.Policy.Exceptions.Get(args[0], args[1], &policyException) - cli.StopProgress() - if err != nil { - return errors.Wrapf(err, "unable to fetch policy exception for ID %s", args[0]) - } - - if cli.JSONOutput() { - return cli.OutputJSON(policyException) - } - - cli.OutputHuman(policyExceptionDetailsTable(policyException.Data, args[0])) - return nil -} - -func deletePolicyException(_ *cobra.Command, args []string) error { - cli.StartProgress(fmt.Sprintf( - "Deleting policy exception '%s' from policy '%s'...", args[0], args[1], - )) - err := cli.LwApi.V2.Policy.Exceptions.Delete(args[0], args[1]) - cli.StopProgress() - if err != nil { - return errors.Wrapf(err, "unable to remove policy exception for ID %s", args[0]) - } - - cli.OutputHuman("Policy exception '%s' deleted from policy '%s'\n", args[0], args[1]) - return nil -} - -func createPolicyException(_ *cobra.Command, args []string) error { - res, policyID, err := promptCreatePolicyException(args) - - if err != nil { - return errors.Wrap(err, "unable to create policy exception") - } - - cli.OutputHuman( - "The policy exception '%s' was created for policy '%s' \n", - res.Data.ExceptionID, policyID, - ) - return nil -} - -func promptCreatePolicyException(args []string) (api.PolicyExceptionResponse, string, error) { - var ( - policy api.PolicyResponse - policyList []string - policyID string - err error - ) - - if len(args) > 0 { - policy, err = cli.LwApi.V2.Policy.Get(args[0]) - if err != nil { - return api.PolicyExceptionResponse{}, "", errors.Wrapf(err, "invalid policy ID %s", args[0]) - } - policyID = policy.Data.PolicyID - } else { - cli.StartProgress("Retrieving list of policies...") - policies, err := cli.LwApi.V2.Policy.List() - cli.StopProgress() - if err != nil { - return api.PolicyExceptionResponse{}, "", errors.Wrap(err, "unable to fetch policies") - } - for _, p := range policies.Data { - if p.PolicyType != "Violation" { - policyList = append(policyList, p.PolicyID) - } - } - if err = survey.AskOne(&survey.Select{ - Message: "Policy ID:", - Options: policyList, - }, &policyID); err != nil { - return api.PolicyExceptionResponse{}, "", err - } - policy, err = cli.LwApi.V2.Policy.Get(policyID) - if err != nil { - return api.PolicyExceptionResponse{}, "", errors.Wrapf(err, "invalid policy ID %s", policyID) - } - } - - questions := []*survey.Question{ - { - Name: "description", - Prompt: &survey.Input{Message: "Exception Description: "}, - Validate: survey.Required, - }, - } - - answers := struct { - Description string `json:"description"` - }{} - - err = survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return api.PolicyExceptionResponse{}, "", err - } - - constraints, err := promptAddExceptionConstraints(policy.Data) - if err != nil { - return api.PolicyExceptionResponse{}, "", err - } - - exception := api.PolicyException{Description: answers.Description, Constraints: constraints} - cli.StartProgress("Creating policy exception ...") - response, err := cli.LwApi.V2.Policy.Exceptions.Create(policyID, exception) - - cli.StopProgress() - return response, policyID, err -} - -var policyExceptionTableHeaders = []string{"POLICY ID", "EXCEPTION ID", "DESCRIPTION", "UPDATED AT", "UPDATED BY"} - -func policyExceptionTable(policyException []api.PolicyException, policyID string) (out [][]string) { - for _, exception := range policyException { - out = append(out, []string{ - policyID, - exception.ExceptionID, - exception.Description, - exception.LastUpdateTime, - exception.LastUpdateUser, - }) - } - return -} - -func policyExceptionDetailsTable(policyException api.PolicyException, policyID string) string { - var ( - table strings.Builder - out [][]string - details [][]string - ) - - out = append(out, []string{ - policyID, - policyException.ExceptionID, - policyException.Description, - policyException.LastUpdateTime, - policyException.LastUpdateUser, - }) - - table.WriteString(renderSimpleTable(policyExceptionTableHeaders, out)) - table.WriteString("\n") - - for _, constraint := range policyException.Constraints { - jsonFieldValues, _ := json.Marshal(constraint.FieldValues) - details = append(details, []string{constraint.FieldKey, string(jsonFieldValues)}) - } - - table.WriteString(renderOneLineCustomTable("CONSTRAINTS", - renderSimpleTable([]string{"FIELD KEY", "FIELD VALUES"}, details), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }))) - - return table.String() -} - -func promptAddExceptionConstraints(policy api.Policy) ([]api.PolicyExceptionConstraint, error) { - if len(policy.ExceptionConfiguration) == 0 { - return []api.PolicyExceptionConstraint{}, nil - } - var responses []api.PolicyExceptionConstraint - questions := buildPromptAddExceptionConstraintListQuestions(policy.ExceptionConfiguration["constraintFields"]) - - for _, q := range questions { - addConstraint, err := promptAddConstraint(q.constraint.FieldKey) - if err != nil { - return []api.PolicyExceptionConstraint{}, nil - } - - if !addConstraint { - continue - } - - answer, err := promptAskConstraintsQuestion(q) - - if err != nil { - return []api.PolicyExceptionConstraint{}, err - } - - if answer != nil { - responses = append(responses, *answer) - } - } - - if len(responses) == 0 { - return []api.PolicyExceptionConstraint{}, errors.New("policy exceptions must have at least 1 constraint") - } - - return responses, nil -} - -func promptAskConstraintsQuestion(constraintQuestion PolicyExceptionSurveyQuestion) ( - *api.PolicyExceptionConstraint, error, -) { - var ( - answer *api.PolicyExceptionConstraint - err error - ) - - switch constraintQuestion.constraint.DataType { - case "String": - if constraintQuestion.constraint.MultiValue { - // prompt string list question - answer, err = promptAddExceptionConstraintList( - constraintQuestion.constraint.FieldKey, constraintQuestion.questions, - ) - } else { - // prompt string question - answer, err = promptAddExceptionConstraintString( - constraintQuestion.constraint.FieldKey, constraintQuestion.questions, - ) - } - case "KVTagPair": - if constraintQuestion.constraint.MultiValue { - // workaround for the unique shape of resourceTags data, see GROW-2831 - if constraintQuestion.constraint.FieldKey == "resourceTags" { - answer, err = promptAddExceptionConstraintResourceTags(constraintQuestion.constraint.FieldKey) - } else { - //prompt kv tag list - answer, err = promptAddExceptionConstraintMapList( - constraintQuestion.constraint.FieldKey, constraintQuestion.questions, - ) - } - } else { - //prompt kv tag - mapAnswers, err := promptAddExceptionConstraintMap(constraintQuestion.questions) - if err != nil { - return nil, err - } - return &api.PolicyExceptionConstraint{ - FieldKey: constraintQuestion.constraint.FieldKey, - FieldValues: []any{mapAnswers}, - }, nil - } - } - - if err != nil { - return nil, err - } - - return answer, nil -} - -func buildPromptAddExceptionConstraintListQuestions( - constraints []api.PolicyExceptionConfigurationConstraints, -) []PolicyExceptionSurveyQuestion { - questions := []PolicyExceptionSurveyQuestion{} - - for _, constraint := range constraints { - switch constraint.DataType { - case "String": - if constraint.MultiValue { - questions = append(questions, PolicyExceptionSurveyQuestion{[]*survey.Question{{ - Name: constraint.FieldKey, - Prompt: &survey.Multiline{Message: fmt.Sprintf("%s values:", constraint.FieldKey)}, - Validate: survey.Required, - }}, - constraint, - }) - } else { - questions = append(questions, PolicyExceptionSurveyQuestion{[]*survey.Question{{ - Name: constraint.FieldKey, - Prompt: &survey.Input{Message: fmt.Sprintf("%s value:", constraint.FieldKey)}, - Validate: survey.Required, - }}, - constraint, - }) - } - case "KVTagPair": - if constraint.MultiValue { - questions = append(questions, PolicyExceptionSurveyQuestion{[]*survey.Question{{ - Name: "key", - Prompt: &survey.Input{Message: "key:"}, - Validate: survey.Required, - }, { - Name: "value", - Prompt: &survey.Input{Message: "value:"}, - Validate: survey.Required, - }, - }, - constraint, - }) - } else { - questions = append(questions, PolicyExceptionSurveyQuestion{[]*survey.Question{{ - Name: "key", - Prompt: &survey.Input{Message: "key:"}, - Validate: survey.Required, - }, { - Name: "value", - Prompt: &survey.Input{Message: "value:"}, - Validate: survey.Required, - }, - }, - constraint, - }) - } - } - } - - return questions -} - -type PolicyExceptionSurveyQuestion struct { - questions []*survey.Question - constraint api.PolicyExceptionConfigurationConstraints -} - -func promptAddExceptionConstraintList( - key string, questions []*survey.Question, -) (*api.PolicyExceptionConstraint, error) { - if key == "accountIds" { - return promptAddExceptionConstraintAwsAccountsList() - } - - var ( - values []any - fieldValues string - ) - - err := survey.Ask(questions, &fieldValues) - if err != nil { - return nil, err - } - - for _, v := range strings.Split(fieldValues, "\n") { - values = append(values, v) - } - - return &api.PolicyExceptionConstraint{ - FieldKey: key, - FieldValues: values, - }, nil -} - -func promptAddExceptionConstraintString( - key string, questions []*survey.Question, -) (*api.PolicyExceptionConstraint, error) { - var fieldValue string - - err := survey.Ask(questions, &fieldValue) - if err != nil { - return nil, err - } - - return &api.PolicyExceptionConstraint{ - FieldKey: key, - FieldValues: []any{fieldValue}, - }, nil -} - -func promptAddExceptionConstraintAwsAccountsList() (*api.PolicyExceptionConstraint, error) { - var ( - values []any - fieldValues []string - accountIds []string - ) - - cli.StartProgress("Retrieving AWS accounts...") - accounts, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AwsCfgCloudAccount) - cli.StopProgress() - - if err != nil { - return nil, err - } - - if len(accounts.Data) == 0 { - return nil, errors.New("no aws cloud accounts found") - } - - for _, ca := range accounts.Data { - if caMap, ok := ca.GetData().(map[string]interface{}); ok { - accountIds = append(accountIds, caMap["awsAccountId"].(string)) - } - } - - question := survey.Question{Name: "awsAccounts", - Prompt: &survey.MultiSelect{Message: "Select AWS Accounts:", - Options: array.Unique(accountIds)}, - Validate: survey.MinItems(1)} - - err = survey.Ask([]*survey.Question{&question}, &fieldValues) - if err != nil { - return nil, err - } - - for _, v := range fieldValues { - values = append(values, v) - } - return &api.PolicyExceptionConstraint{ - FieldKey: "accountIds", - FieldValues: values, - }, nil -} - -func promptAddExceptionConstraintMap(mapQuestions []*survey.Question) (constraintMapAnswer, error) { - var mapAnswer constraintMapAnswer - err := survey.Ask(mapQuestions, &mapAnswer, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return constraintMapAnswer{}, err - } - - return mapAnswer, nil -} - -// resourceTags requires special handling, see GROW-2831 -type resourceTagsConstraintAnswer struct { - Key string `json:"key"` - Value string `json:"value"` -} - -// resourceTags requires special handling, see GROW-2831 -func promptAddExceptionConstraintResourceTags(key string) (*api.PolicyExceptionConstraint, error) { - questions := []*survey.Question{{ - Name: "key", - Prompt: &survey.Input{Message: "tag name:"}, - Validate: survey.Required, - }, { - Name: "value", - Prompt: &survey.Input{Message: "values:", Help: "Supply a comma seperated list for multiple"}, - Validate: survey.Required, - }} - - var answer resourceTagsConstraintAnswer - if err := survey.Ask(questions, &answer); err != nil { - return nil, err - } - - finalAnswer := strings.Split(answer.Value, ",") - return &api.PolicyExceptionConstraint{ - FieldKey: key, - FieldValues: []any{ - map[string]interface{}{"key": answer.Key, "value": finalAnswer}, - }, - }, nil - -} - -type constraintMapAnswer struct { - Key string `json:"key"` - Value []string `json:"value"` -} - -func promptAddExceptionConstraintMapList( - key string, mapQuestions []*survey.Question, -) (*api.PolicyExceptionConstraint, error) { - var mapAnswers []any - - res, err := promptAddExceptionConstraintMap(mapQuestions) - if err != nil { - return nil, err - } - mapAnswers = append(mapAnswers, res) - - addTag := false - for { - if err := survey.AskOne(&survey.Confirm{ - Message: fmt.Sprintf("Add another %s constraint?", key), - }, &addTag); err != nil { - return nil, err - } - - if addTag { - res, err := promptAddExceptionConstraintMap(mapQuestions) - if err != nil { - return nil, err - } - mapAnswers = append(mapAnswers, res) - } else { - break - } - } - - return &api.PolicyExceptionConstraint{ - FieldKey: key, - FieldValues: mapAnswers, - }, nil -} - -func promptAddConstraint(key string) (bool, error) { - addConstraint := false - - if err := survey.AskOne(&survey.Confirm{ - Message: fmt.Sprintf("Add constraint %q?", key), - }, &addConstraint); err != nil { - return false, err - } - - return addConstraint, nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_library.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_library.go deleted file mode 100644 index 31bfea5c8..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_library.go +++ /dev/null @@ -1,552 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -var ( - policyLibraryTableHeaders []string = []string{"Policy ID", "Title", "Query ID", "TAGS"} - - policyListLibraryCmd = &cobra.Command{ - Use: "list-library", - Short: "List policies from library", - Long: `List all LQL policies in your Lacework Content Library.`, - Args: cobra.NoArgs, - RunE: listPoliciesLibrary, - } - policyShowLibraryCmd = &cobra.Command{ - Use: "show-library ", - Short: "Show a policy from library", - Long: `Show a policy in your Lacework Content Library.`, - Args: cobra.ExactArgs(1), - RunE: showPolicyLibrary, - } - policySyncLibraryCmd = &cobra.Command{ - Use: "sync-library", - Short: "Synchronize library policies", - Long: `Synchronize library policies with your Lacework account. - -Requirements: -Specify the --tag flag to select policies and queries. - -Behavior: -1. Policies and queries that exist in the library but not in your account will be created. -2. Policies and queries that exist in both the library and your account will be updated. -3. Nothing will be deleted. - -To view all policies in the library and their associated tags. - - lacework policy list-library`, - Args: cobra.NoArgs, - RunE: syncPolicyLibrary, - } -) - -func init() { - if !cli.isLCLInstalled() { - return - } - - policyCmd.AddCommand(policyListLibraryCmd) - policyListLibraryCmd.Flags().StringVar( - &policyCmdState.Tag, - "tag", "", "only show policies with the specified tag", - ) - - policyCmd.AddCommand(policyShowLibraryCmd) - - policyCmd.AddCommand(policySyncLibraryCmd) - policySyncLibraryCmd.Flags().StringVar( - &policyCmdState.Tag, - "tag", "", "sync policies and queries with the specified tag", - ) -} - -func policyLibraryTable(policies map[string]LCLPolicy) (out [][]string) { - for _, policy := range policies { - out = append(out, []string{ - policy.PolicyID, - policy.Title, - policy.QueryID, - strings.Join(policy.Tags, "\n"), - }) - } - sortPolicyTable(out, 0) - return -} - -func buildPolicyLibraryDetailsTable(policy api.Policy) string { - details := [][]string{ - {"DESCRIPTION", policy.Description}, - {"REMEDIATION", policy.Remediation}, - {"POLICY TYPE", policy.PolicyType}, - {"LIMIT", fmt.Sprintf("%d", policy.Limit)}, - {"ALERT PROFILE", policy.AlertProfile}, - {"EVALUATION FREQUENCY", policy.EvalFrequency}, - } - - return renderOneLineCustomTable("POLICY DETAILS", - renderCustomTable([]string{}, details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ) -} - -func listPoliciesLibrary(_ *cobra.Command, args []string) error { - cli.Log.Debugw("listing policies from library") - - cli.StartProgress("Retrieving policies...") - lcl, err := cli.LoadLCL() - cli.StopProgress() - - var policies map[string]LCLPolicy - if policyCmdState.Tag == "" { - policies = lcl.Policies - } else { - policies = lcl.GetPoliciesByTag(policyCmdState.Tag) - } - - if err != nil { - return errors.Wrap(err, "unable to list policies") - } - if cli.JSONOutput() { - return cli.OutputJSON(policies) - } - if len(policies) == 0 { - cli.OutputHuman("There were no policies found.") - return nil - } - cli.OutputHuman( - renderCustomTable( - policyLibraryTableHeaders, - policyLibraryTable(policies), - tableFunc(func(t *tablewriter.Table) { - t.SetAutoWrapText(false) - t.SetBorder(false) - }), - ), - ) - return nil -} - -func showPolicyLibrary(_ *cobra.Command, args []string) error { - var ( - msg string = "unable to show policy" - policyString string - newPolicy api.NewPolicy - policyResponse api.PolicyResponse - err error - ) - cli.Log.Debugw("retrieving policy", "id", args[0]) - - cli.StartProgress("Retrieving policy...") - // input policy - if policyString, err = inputPolicyFromLibrary(args[0]); err != nil { - cli.StopProgress() - return errors.Wrap(err, msg) - } - // parse policy - newPolicy, err = api.ParseNewPolicy(policyString) - policyResponse.Data = api.Policy{ - PolicyID: newPolicy.PolicyID, - PolicyType: newPolicy.PolicyType, - QueryID: newPolicy.QueryID, - Title: newPolicy.Title, - Enabled: newPolicy.Enabled, - Description: newPolicy.Description, - Remediation: newPolicy.Remediation, - Severity: newPolicy.Severity, - Limit: newPolicy.Limit, - EvalFrequency: newPolicy.EvalFrequency, - AlertEnabled: newPolicy.AlertEnabled, - AlertProfile: newPolicy.AlertProfile, - } - cli.StopProgress() - - // output policy - if err != nil { - return errors.Wrap(err, msg) - } - if cli.JSONOutput() { - return cli.OutputJSON(newPolicy) - } - cli.OutputHuman( - renderSimpleTable(policyTableHeaders, policyTable([]api.Policy{policyResponse.Data}))) - cli.OutputHuman("\n") - cli.OutputHuman(buildPolicyLibraryDetailsTable(policyResponse.Data)) - return nil -} - -type PolicySyncOperation struct { - ID string - ContentType string - Operation string -} - -func getPolicySyncOperations(policies map[string]LCLPolicy) ([]PolicySyncOperation, error) { - policyOps := []PolicySyncOperation{} - - cli.StartProgress("Retrieving platform policies...") - policiesResponse, err := cli.LwApi.V2.Policy.List() - cli.StopProgress() - - if err != nil { - return policyOps, err - } - var platformPolicyIDs = make([]string, len(policiesResponse.Data)) - for i, policy := range policiesResponse.Data { - platformPolicyIDs[i] = policy.PolicyID - } - - cli.StartProgress("Retrieving platform queries...") - queryResponse, err := cli.LwApi.V2.Query.List() - cli.StopProgress() - - if err != nil { - return policyOps, err - } - var platformQueryIDs = make([]string, len(queryResponse.Data)) - for i, query := range queryResponse.Data { - platformQueryIDs[i] = query.QueryID - } - - for _, lclPolicy := range policies { - qso := PolicySyncOperation{ - ID: lclPolicy.QueryID, - ContentType: "query", - Operation: "create", - } - if array.ContainsStr(platformQueryIDs, lclPolicy.QueryID) { - qso.Operation = "update" - } - policyOps = append(policyOps, qso) - - pso := PolicySyncOperation{ - ID: lclPolicy.PolicyID, - ContentType: "policy", - Operation: "create", - } - suf := fmt.Sprintf("-%s", lclPolicy.PolicyID) - for _, platformPolicyID := range platformPolicyIDs { - // TODO: proper handling for $account based ids - if platformPolicyID == lclPolicy.PolicyID || strings.HasSuffix(platformPolicyID, suf) { - pso.Operation = "update" - break - } - } - policyOps = append(policyOps, pso) - } - - return policyOps, nil -} - -func policySyncOpsDetails(psos []PolicySyncOperation) string { - var ( - ops = []string{"Operation details:"} - detailTemplate = " %s %s will be %sd." - validOperations = []string{"policy-create", "policy-update", "query-create", "query-update"} - caser = cases.Title(language.Und) - ) - - for _, pso := range psos { - key := fmt.Sprintf("%s-%s", pso.ContentType, pso.Operation) - - if !array.ContainsStr(validOperations, key) { - continue - } - - ops = append(ops, fmt.Sprintf( - detailTemplate, - caser.String(pso.ContentType), - pso.ID, - pso.Operation, - )) - } - - return strings.Join(ops, "\n") + "\n\n" -} - -func policySyncOpsSummary(psos []PolicySyncOperation) string { - var ( - msg = "Policy sync-library will create %d policies, update %d policies, create %d queries, update %d queries." - obs = map[string]int{ - "policy-create": 0, - "policy-update": 0, - "query-create": 0, - "query-update": 0, - } - ) - - for _, pso := range psos { - key := fmt.Sprintf("%s-%s", pso.ContentType, pso.Operation) - if v, ok := obs[key]; ok { - obs[key] = v + 1 - } - } - - return fmt.Sprintf( - msg, - obs["policy-create"], - obs["policy-update"], - obs["query-create"], - obs["query-update"], - ) -} - -// Simple helper to prompt for next steps after TF plan -func policySyncPrompt(psos []PolicySyncOperation, previewShown *bool) (int, error) { - options := []string{ - "Apply sync", - } - - // Omit option to show details if we already have - if !*previewShown { - options = append(options, "Show details") - } - options = append(options, "Quit") - - var answer int - err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: policySyncOpsSummary(psos), - Options: options, - }, - Response: &answer, - }) - - return answer, err -} - -func policySyncDisplayChanges(psos []PolicySyncOperation) (bool, error) { - // Prompt for next steps - prompt := true - previewShown := false - var answer int - - // Displaying resources - for prompt { - id, err := policySyncPrompt(psos, &previewShown) - if err != nil { - return false, err - } - - switch { - case id == 1 && !previewShown: - cli.OutputHuman(policySyncOpsDetails(psos)) - default: - answer = id - prompt = false - } - - if id == 1 && !previewShown { - previewShown = true - } - } - - // Run apply - if answer == 0 { - return true, nil - } - - // Quit - return false, nil -} - -func policySyncExecuteChanges(lcl *LaceworkContentLibrary, psos []PolicySyncOperation) error { - for _, pso := range psos { - msg := fmt.Sprintf("unable to %s %s", pso.Operation, pso.ContentType) - - if pso.ContentType == "query" { - // input query - queryString, err := lcl.GetQuery(pso.ID) - if err != nil { - return errors.Wrap(err, msg) - } - - if pso.Operation == "create" { - cli.Log.Debugw("creating query", "query", queryString) - - // parse query - newQuery, err := api.ParseNewQuery(queryString) - if err != nil { - return errors.Wrap(queryErrorCrumbs(queryString), msg) - } - cli.StartProgress(" Creating query...") - create, err := cli.LwApi.V2.Query.Create(newQuery) - cli.StopProgress() - - // output - if err != nil { - return errors.Wrap(err, msg) - } - cli.OutputHuman("The query %s was created.\n", create.Data.QueryID) - } - - if pso.Operation == "update" { - cli.Log.Debugw("updating query", "query", queryString) - - // parse query - newQuery, err := api.ParseNewQuery(queryString) - if err != nil { - return errors.Wrap(queryErrorCrumbs(queryString), msg) - } - updateQuery := api.UpdateQuery{ - QueryText: newQuery.QueryText, - } - - // update query - cli.StartProgress(" Updating query...") - update, err := cli.LwApi.V2.Query.Update(newQuery.QueryID, updateQuery) - cli.StopProgress() - - // output - if err != nil { - return errors.Wrap(err, msg) - } - cli.OutputHuman("The query %s was updated.\n", update.Data.QueryID) - } - } - - if pso.ContentType == "policy" { - // input policy - policyString, err := lcl.GetPolicy(pso.ID) - if err != nil { - return errors.Wrap(err, msg) - } - - if pso.Operation == "create" { - cli.Log.Debugw("creating policy", "policy", policyString) - - // parse policy - newPolicy, err := api.ParseNewPolicy(policyString) - if err != nil { - return errors.Wrap(err, msg) - } - - // create policy - cli.StartProgress(" Creating policy...") - createResponse, err := cli.LwApi.V2.Policy.Create(newPolicy) - cli.StopProgress() - - // output policy - if err != nil { - return errors.Wrap(err, msg) - } - cli.OutputHuman(fmt.Sprintf("The policy %s was created.\n", createResponse.Data.PolicyID)) - } - - if pso.Operation == "update" { - cli.Log.Debugw("updating policy", "policy", policyString) - - // parse policy - updatePolicy, err := api.ParseUpdatePolicy(policyString) - if err != nil { - return errors.Wrap(err, msg) - } - // remove state from policies we're updating - updatePolicy.Enabled = nil - updatePolicy.AlertEnabled = nil - - cli.StartProgress(" Updating policy...") - updateResponse, err := cli.LwApi.V2.Policy.Update(updatePolicy) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, msg) - } - cli.OutputHuman("The policy %s was updated.\n", updateResponse.Data.PolicyID) - } - } - } - return nil -} - -func syncPolicyLibrary(_ *cobra.Command, args []string) error { - msg := "unable to sync policies" - - // check tag - if policyCmdState.Tag == "" { - return errors.New("must specify the --tag flag when performing library sync") - } - // check json output - if cli.JSONOutput() { - return errors.New("json output format not supported for sync-library") - } - - // load content library - cli.StartProgress("Retrieving library policies...") - lcl, err := cli.LoadLCL() - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, msg) - } - - // get policies for tag - policies := lcl.GetPoliciesByTag(policyCmdState.Tag) - if len(policies) == 0 { - cli.OutputHuman("There were no policies found.") - return nil - } - - // build smart list of changes - psos, err := getPolicySyncOperations(policies) - if err != nil { - return errors.Wrap(err, msg) - } - - // prompt for changes - proceed, err := policySyncDisplayChanges(psos) - if err != nil { - return errors.Wrap(err, msg) - } - if !proceed { - return nil - } - - // execute changes - err = policySyncExecuteChanges(lcl, psos) - if err != nil { - return errors.Wrap(err, msg) - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_update.go b/vendor/github.com/lacework/go-sdk/cli/cmd/policy_update.go deleted file mode 100644 index 4342579ac..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/policy_update.go +++ /dev/null @@ -1,225 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strings" - - "github.com/lacework/go-sdk/lwseverity" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - // policyUpdateCmd represents the policy update command - policyUpdateCmd = &cobra.Command{ - Use: "update [policy_id...]", - Short: "Update a policy", - Long: `Update a policy. - -A policy identifier is required to update a policy. - -A policy identifier can be specified via: - -1. A policy update command argument - - lacework policy update my-policy-1 - -2. The policy update payload - - { - "policy_id": "my-policy-1", - "severity": "critical" - } - -A policy identifier specified via command argument always takes precedence over -a policy identifer specified via payload. - -The severity of many policies can be updated at once by passing a list of policy identifiers: - - lacework policy update my-policy-1 my-policy-2 --severity critical - -`, - PreRunE: func(cmd *cobra.Command, args []string) error { - // return error if multiple policy-ids are supplied without severity flag - if len(args) > 1 && policyCmdState.Severity == "" { - return errors.Errorf(`policy bulk update is only supported with the '--severity' flag - -For example: - - lacework policy update %s --severity critical - `, strings.Join(args, " ")) - } - - if policyCmdState.Severity != "" && !lwseverity.IsValid(policyCmdState.Severity) { - return errors.Errorf("invalid severity %q valid severities are: %s", - policyCmdState.Severity, lwseverity.ValidSeverities.String()) - } - - return nil - }, - RunE: updatePolicy, - } -) - -func init() { - // add sub-commands to the policy command - policyCmd.AddCommand(policyUpdateCmd) - - // policy source specific flags - setPolicySourceFlags(policyUpdateCmd) - - // add severity flag - policyUpdateCmd.Flags().StringVar(&policyCmdState.Severity, "severity", "", - "update the policy severity", - ) -} - -func updateQueryFromLibrary(id string) error { - var ( - queryString string - err error - newQuery api.NewQuery - ) - - // input query - queryString, err = inputQueryFromLibrary(id) - if err != nil { - return err - } - - cli.Log.Debugw("creating query", "query", queryString) - - // parse query - newQuery, err = api.ParseNewQuery(queryString) - if err != nil { - return queryErrorCrumbs(queryString) - } - updateQuery := api.UpdateQuery{ - QueryText: newQuery.QueryText, - } - - // update query - _, err = cli.LwApi.V2.Query.Update(newQuery.QueryID, updateQuery) - return err -} - -func updatePolicy(cmd *cobra.Command, args []string) error { - var ( - msg = "unable to update policy" - err error - queryUpdated bool - policyID string - ) - - // if severity flag is provided, attempt bulk update - if policyCmdState.Severity != "" { - err = policyBulkUpdate(args) - if err != nil { - return err - } - return nil - } - - if len(args) != 0 && len(args[0]) != 0 { - policyID = args[0] - } - // input policy - policyString, err := inputPolicy(cmd, policyID) - if err != nil { - return errors.Wrap(err, msg) - } - cli.Log.Debugw("updating policy", "policy", policyString) - - // parse policy - updatePolicy, err := api.ParseUpdatePolicy(policyString) - if err != nil { - return errors.Wrap(err, msg) - } - // set policy id if not already set - if policyID != "" && updatePolicy.PolicyID == "" { - updatePolicy.PolicyID = policyID - } - - cli.StartProgress("Updating policy...") - updateResponse, err := cli.LwApi.V2.Policy.Update(updatePolicy) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, msg) - } - // if updating policy from library also update query - if policyCmdState.CUFromLibrary != "" { - cli.StartProgress("Updating query...") - err = updateQueryFromLibrary(updatePolicy.QueryID) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, msg) - } - queryUpdated = true - } - - // output policy - if cli.JSONOutput() { - return cli.OutputJSON(updateResponse.Data) - } - if queryUpdated { - cli.OutputHuman(fmt.Sprintf("The query %s was updated.\n", updatePolicy.QueryID)) - } - cli.OutputHuman("The policy %s was updated.\n", updateResponse.Data.PolicyID) - return nil -} - -func policyBulkUpdate(args []string) error { - var ( - policyIds []string - err error - ) - // if no policy ids are provided; prompt a list of policy ids - if len(args) == 0 { - policyIds, err = promptSetPolicyIDs() - if err != nil { - return err - } - } else { - policyIds = args - } - - var bulkPolicies api.BulkUpdatePolicies - for _, p := range policyIds { - bulkPolicies = append(bulkPolicies, api.BulkUpdatePolicy{ - PolicyID: p, - Severity: policyCmdState.Severity, - }) - } - - response, err := cli.LwApi.V2.Policy.UpdateMany(bulkPolicies) - if err != nil { - return errors.Wrap(err, "unable to update policies") - } - - cli.Log.Debugw("bulk policy updated", "response", response) - cli.OutputHuman("%d policies updated with new severity %q\n", len(policyIds), policyCmdState.Severity) - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/prompt.go b/vendor/github.com/lacework/go-sdk/cli/cmd/prompt.go deleted file mode 100644 index 8db56153e..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/prompt.go +++ /dev/null @@ -1,32 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/pkg/errors" -) - -func promptRequiredStringLen(size int, err string) func(interface{}) error { - return func(input interface{}) error { - if str, ok := input.(string); !ok || len(str) < size { - return errors.New(err) - } - return nil - } -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions.go deleted file mode 100644 index f69e113d0..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions.go +++ /dev/null @@ -1,375 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strconv" - "strings" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - CreateReportDefinitionQuestion = "Create from an existing report definition template?" - CreateReportDefinitionReportNameQuestion = "Report Name: " - CreateReportDefinitionDisplayNameQuestion = "Display Name: " - CreateReportDefinitionReportSubTypeQuestion = "Report SubType: " - CreateReportDefinitionAddSectionQuestion = "Add another policy section?" - CreateReportDefinitionSectionTitleQuestion = "Section Title: " - CreateReportDefinitionPoliciesQuestion = "Select Policies in this Section: " - SelectReportDefinitionQuestion = "Select an existing report definition as a template?" - - UpdateReportDefinitionQuestion = "Update report definition in editor?" - UpdateReportDefinitionReportNameQuestion = "Report Name: " - UpdateReportDefinitionDisplayNameQuestion = "Display Name: " - UpdateReportDefinitionEditSectionQuestion = "Update an existing policy section?" - UpdateReportDefinitionEditAnotherSectionQuestion = "Update another existing policy section?" - UpdateReportDefinitionAddSectionQuestion = "Add a new policy section?" - UpdateReportDefinitionSelectSectionQuestion = "Select a section to edit" - - reportDefinitionsCmdState = struct { - // filter report definitions by subtype. 'AWS', 'GCP' or 'Azure' - SubType string - // create report definitions from a file input - File string - // retrieve report definition by version - Version string - }{} - - // report-definitions command is used to manage lacework report definitions - reportDefinitionsCommand = &cobra.Command{ - Use: "report-definition", - Hidden: true, - Aliases: []string{"report-definitions", "rd"}, - Short: "Manage report definitions", - Long: `Manage report definitions to configure the data retrieval and layout information for a report. -`, - } - - // list command is used to list all lacework report definitions - reportDefinitionsListCommand = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all report definitions", - Long: "List all report definitions configured in your Lacework account.", - Args: cobra.NoArgs, - PreRunE: func(_ *cobra.Command, _ []string) error { - if reportDefinitionsCmdState.SubType != "" && - !array.ContainsStr(api.ReportDefinitionSubtypes, reportDefinitionsCmdState.SubType) { - return errors.Errorf("'%s' is not valid. Report definitions subtype can be %s", - reportDefinitionsCmdState.SubType, api.ReportDefinitionSubtypes, - ) - } - return nil - }, - RunE: func(_ *cobra.Command, _ []string) error { - cli.StartProgress(" Fetching report definitions...") - reportDefinitions, err := cli.LwApi.V2.ReportDefinitions.List() - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get report definitions") - } - - if len(reportDefinitions.Data) == 0 { - cli.OutputHuman("There are no report definitions configured in your account.\n") - return nil - } - - // filter definitions by subtype - if reportDefinitionsCmdState.SubType != "" { - filterReportDefinitions(&reportDefinitions) - } - - if cli.JSONOutput() { - return cli.OutputJSON(reportDefinitions) - } - - var rows [][]string - for _, definition := range reportDefinitions.Data { - rows = append(rows, []string{definition.ReportDefinitionGuid, definition.ReportName, - definition.ReportType, definition.SubReportType}) - } - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "TYPE", "SUB-TYPE"}, rows)) - return nil - }, - } - // show command is used to retrieve a lacework report definition by guid - reportDefinitionsShowCommand = &cobra.Command{ - Use: "show ", - Short: "Show a report definition by ID", - Long: `Show a single report definition by it's ID. -To show specific report definition version: - - lacework report-definition show --version - -To show all versions of a report definition: - - lacework report-definition show --version all - -`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - if reportDefinitionsCmdState.Version != "" { - return fetchReportDefinitionVersion(args[0]) - } - - cli.StartProgress(" Fetching report definition...") - response, err := cli.LwApi.V2.ReportDefinitions.Get(args[0]) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "unable to get report definition") - } - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - outputReportDefinitionTable(response.Data) - - return nil - }, - } - - // delete command is used to remove a lacework report definition by id - reportDefinitionsDeleteCommand = &cobra.Command{ - Use: "delete ", - Short: "Delete a report definition", - Long: "Delete a single report definition by it's ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress("Deleting report definition...") - err := cli.LwApi.V2.ReportDefinitions.Delete(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to delete report definition") - } - cli.OutputHuman("The report definition with GUID %s was deleted\n", args[0]) - return nil - }, - } -) - -func outputReportDefinitionTable(reportDefinition api.ReportDefinition) { - headers := [][]string{ - {reportDefinition.ReportDefinitionGuid, reportDefinition.ReportName, reportDefinition.ReportType, - reportDefinition.SubReportType}, - } - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "TYPE", "SUB-TYPE"}, headers)) - cli.OutputHuman("\n") - cli.OutputHuman(buildReportDefinitionDetailsTable(reportDefinition)) -} - -func outputReportVersionsList(guid string, versions []string) { - details := [][]string{{"VERSIONS", strings.Join(versions, ", ")}} - - detailsTable := &strings.Builder{} - detailsTable.WriteString(renderOneLineCustomTable(guid, - renderCustomTable([]string{}, details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ), - ) - - cli.OutputHuman(detailsTable.String()) -} - -func fetchReportDefinitionVersion(id string) error { - var ( - err error - version int - reportDefinition api.ReportDefinition - versions []string - ) - - // if no version is supplied return all previous versions - if reportDefinitionsCmdState.Version == "all" { - cli.StartProgress("Fetching all report definition versions...") - response, err := cli.LwApi.V2.ReportDefinitions.GetVersions(id) - cli.StopProgress() - - if err != nil { - return err - } - - if cli.JSONOutput() { - err := cli.OutputJSON(response) - if err != nil { - return err - } - } - - for _, reportVersion := range response.Data { - versions = append(versions, strconv.Itoa(reportVersion.Version)) - } - - outputReportVersionsList(response.Data[0].ReportDefinitionGuid, versions) - return nil - } - - if version, err = strconv.Atoi(reportDefinitionsCmdState.Version); err != nil { - return errors.Wrap(err, "unable to parse version") - } - - cli.StartProgress(fmt.Sprintf("Fetching report definition version %d...", version)) - response, err := cli.LwApi.V2.ReportDefinitions.GetVersions(id) - cli.StopProgress() - - if err != nil { - return err - } - - for _, reportVersion := range response.Data { - versions = append(versions, strconv.Itoa(reportVersion.Version)) - if reportVersion.Version == version { - reportDefinition = reportVersion - } - } - - if cli.JSONOutput() { - return cli.OutputJSON(reportDefinition) - } - - if reportDefinition.ReportDefinitionGuid == "" { - cli.OutputHuman("version %d not found\nvalid versions are: %s\n", version, strings.Join(versions, ", ")) - return nil - } - - outputReportDefinitionTable(reportDefinition) - - return nil -} - -func filterReportDefinitions(reportDefinitions *api.ReportDefinitionsResponse) { - var filteredDefinitions []api.ReportDefinition - for _, rd := range reportDefinitions.Data { - if rd.SubReportType == reportDefinitionsCmdState.SubType { - filteredDefinitions = append(filteredDefinitions, rd) - } - } - reportDefinitions.Data = filteredDefinitions -} - -func init() { - // add the report-definition command - rootCmd.AddCommand(reportDefinitionsCommand) - - // add sub-commands to the report-definition command - reportDefinitionsCommand.AddCommand(reportDefinitionsCreateCommand) - reportDefinitionsCommand.AddCommand(reportDefinitionsListCommand) - reportDefinitionsCommand.AddCommand(reportDefinitionsShowCommand) - reportDefinitionsCommand.AddCommand(reportDefinitionsDeleteCommand) - reportDefinitionsCommand.AddCommand(reportDefinitionsUpdateCommand) - reportDefinitionsCommand.AddCommand(reportDefinitionsRevertCommand) - reportDefinitionsCommand.AddCommand(reportDefinitionsDiffCommand) - - // add flags to report-definition commands - reportDefinitionsShowCommand.Flags().StringVar(&reportDefinitionsCmdState.Version, - "version", "", "show a version of a report definition", - ) - reportDefinitionsListCommand.Flags().StringVar(&reportDefinitionsCmdState.SubType, - "subtype", "", "filter report definitions by subtype. 'AWS', 'GCP' or 'Azure'", - ) - reportDefinitionsCreateCommand.Flags().StringVar(&reportDefinitionsCmdState.File, - "file", "", "create a report definition from an existing definition file", - ) - reportDefinitionsUpdateCommand.Flags().StringVar(&reportDefinitionsCmdState.File, - "file", "", "update a report definition from an existing definition file", - ) -} - -func buildReportDefinitionDetailsTable(definition api.ReportDefinition) string { - var ( - details [][]string - engine = "" - releaseLabel = "" - ) - - details = append(details, []string{"FREQUENCY", definition.Frequency}) - if definition.Props != nil { - engine = definition.Props.Engine - releaseLabel = definition.Props.ReleaseLabel - } - - details = append(details, []string{"ENGINE", engine}) - details = append(details, []string{"RELEASE LABEL", releaseLabel}) - details = append(details, []string{"UPDATED BY", definition.CreatedBy}) - details = append(details, []string{"LAST UPDATED", definition.CreatedTime.String()}) - details = append(details, []string{"VERSION", strconv.Itoa(definition.Version)}) - - detailsTable := &strings.Builder{} - detailsTable.WriteString(renderOneLineCustomTable("REPORT DEFINITION DETAILS", - renderCustomTable([]string{}, details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ), - ) - - policiesTable := &strings.Builder{} - if len(definition.ReportDefinitionDetails.Sections) > 0 { - - for _, s := range definition.ReportDefinitionDetails.Sections { - var policies [][]string - policies = append(policies, []string{s.Title, strings.Join(s.Policies, ", ")}) - - policiesTable.WriteString(renderCustomTable([]string{"title", "policy"}, policies, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - ) - } - policiesTable.WriteString("\n") - - detailsTable.WriteString(renderOneLineCustomTable( - "POLICIES", policiesTable.String(), tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - }), - )) - } - - return detailsTable.String() -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_create.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_create.go deleted file mode 100644 index f07f21c61..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_create.go +++ /dev/null @@ -1,343 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "os" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/exp/slices" - "gopkg.in/yaml.v3" -) - -// create command is used to create a new lacework report definition -var reportDefinitionsCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a report definition", - Long: `Create a new report definition to view the evaluation of a set of policies in a report. - -To create a new report definition: - - lacework report-definition create - -To create a new report definition from an existing file: - - lacework report-definition create --file custom-report.json -`, - Args: cobra.NoArgs, - RunE: createReportDefinition, -} - -func createReportDefinition(_ *cobra.Command, args []string) error { - var ( - reportDefinition api.ReportDefinition - err error - ) - - if reportDefinitionsCmdState.File != "" { - fileInput, err := inputReportDefinitionFromFile(reportDefinitionsCmdState.File) - if err != nil { - return err - } - - cfg, err := parseNewReportDefinition(fileInput) - if err != nil { - return err - } - reportDefinition = api.NewReportDefinition(cfg) - } else { - reportDefinition, err = promptCreateReportDefinition() - if err != nil { - return err - } - } - - cli.StartProgress("Creating report definition...") - resp, err := cli.LwApi.V2.ReportDefinitions.Create(reportDefinition) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to create report definition") - } - - cli.OutputHuman("New report definition created. To view the report run:\n\n"+ - "lacework report-definition show %s \n", resp.Data.ReportDefinitionGuid) - return nil -} - -func inputReportDefinitionFromFile(filePath string) (string, error) { - fileData, err := os.ReadFile(filePath) - - if err != nil { - return "", errors.Wrap(err, "unable to read file") - } - - return string(fileData), nil -} - -func promptCreateReportDefinition() (api.ReportDefinition, error) { - var useExisting bool - - if err := survey.AskOne(&survey.Confirm{Message: CreateReportDefinitionQuestion}, &useExisting); err != nil { - return api.ReportDefinition{}, err - } - - if useExisting { - return promptCreateReportDefinitionFromExisting() - } - - return promptCreateReportDefinitionFromNew() -} - -func promptCreateReportDefinitionFromNew() (reportDefinition api.ReportDefinition, err error) { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: CreateReportDefinitionReportNameQuestion}, - Validate: survey.Required, - }, - { - Name: "display", - Prompt: &survey.Input{Message: CreateReportDefinitionDisplayNameQuestion}, - Validate: survey.Required, - }, - { - Name: "subType", - Prompt: &survey.Select{ - Message: CreateReportDefinitionReportSubTypeQuestion, - Options: api.ReportDefinitionSubTypes(), - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string `survey:"name"` - DisplayName string `survey:"display"` - SubType string `survey:"subType"` - }{} - - if err = survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { - return - } - - reportDefinition = api.ReportDefinition{ - ReportName: answers.Name, - DisplayName: answers.DisplayName, - SubReportType: answers.SubType, - ReportType: api.ReportDefinitionTypeCompliance.String(), - } - - var sections []api.ReportDefinitionSection - - cli.StartProgress("Fetching list of policy ids...") - resp, err := cli.LwApi.V2.Policy.List() - cli.StopProgress() - - //filter the policies not in current report's domain - var policies []api.Policy - for _, p := range resp.Data { - domain := strings.ToUpper(answers.SubType) - if slices.Contains(p.Tags, fmt.Sprintf("domain:%s", domain)) { - policies = append(policies, p) - } - } - - if err != nil { - return api.ReportDefinition{}, err - } - - if len(policies) == 0 { - return api.ReportDefinition{}, errors.New("unable to find policies") - } - - // add sections - if err = promptAddReportDefinitionSection(§ions, policies); err != nil { - return - } - - addSection := false - for { - if err = survey.AskOne(&survey.Confirm{ - Message: CreateReportDefinitionAddSectionQuestion, - }, &addSection); err != nil { - return - } - - if addSection { - if err = promptAddReportDefinitionSection(§ions, policies); err != nil { - return - } - } else { - break - } - } - - reportDefinition.ReportDefinitionDetails = api.ReportDefinitionDetails{Sections: sections} - - return -} - -func promptAddReportDefinitionSection(sections *[]api.ReportDefinitionSection, policies []api.Policy) error { - var policyIDs []string - - for _, policy := range policies { - policyIDs = append(policyIDs, policy.PolicyID) - } - - questions := []*survey.Question{ - { - Name: "title", - Prompt: &survey.Input{Message: CreateReportDefinitionSectionTitleQuestion}, - Validate: survey.Required, - }, - { - Name: "policies", - Prompt: &survey.MultiSelect{Message: CreateReportDefinitionPoliciesQuestion, Options: policyIDs}, - Validate: survey.MinItems(1), - }, - } - - answers := struct { - Title string `survey:"title"` - Policies []string `survey:"policies"` - }{} - - if err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { - return err - } - - section := api.ReportDefinitionSection{ - Title: answers.Title, - Policies: answers.Policies, - } - - *sections = append(*sections, section) - return nil -} - -func promptCreateReportDefinitionFromExisting() (reportDefinition api.ReportDefinition, err error) { - var ( - reports = make(map[string]api.ReportDefinition) - reportNames []string - selectedReport string - reportDefinitionConfig api.ReportDefinitionConfig - ) - - cli.StartProgress("Fetching existing report definitions...") - resp, err := cli.LwApi.V2.ReportDefinitions.List() - cli.StopProgress() - - if err != nil { - return - } - - for _, report := range resp.Data { - reports[report.ReportName] = report - reportNames = append(reportNames, report.ReportName) - } - - // Add option for blank template - reports["BLANK TEMPLATE"] = api.ReportDefinition{ReportName: "TEMPLATE", - ReportType: api.ReportDefinitionTypeCompliance.String(), - ReportDefinitionDetails: api.ReportDefinitionDetails{ - Sections: []api.ReportDefinitionSection{ - {Title: "CUSTOM SECTION TITLE", Policies: []string{"example-policy-1"}}}}} - reportNames = append([]string{"BLANK TEMPLATE"}, reportNames...) - - if err = survey.AskOne(&survey.Select{ - Message: SelectReportDefinitionQuestion, - Options: reportNames, - }, &selectedReport); err != nil { - return - } - - reportTemplateYaml, err := yaml.Marshal(reports[selectedReport].Config()) - if err != nil { - return - } - - // open editor with report yaml - report, err := inputReportDefinitionFromEditor("create", string(reportTemplateYaml)) - if err != nil { - return - } - err = yaml.Unmarshal([]byte(report), &reportDefinitionConfig) - - reportDefinition = api.NewReportDefinition(reportDefinitionConfig) - return -} - -func inputReportDefinitionFromEditor(action string, reportYaml string) (report string, err error) { - prompt := &survey.Editor{ - Message: fmt.Sprintf("Use the editor to %s your report definition", action), - FileName: "report-definition*.yaml", - HideDefault: true, - AppendDefault: true, - Default: reportYaml, - } - - err = survey.AskOne(prompt, &report) - return -} - -func parseNewReportDefinition(s string) (report api.ReportDefinitionConfig, err error) { - var res api.ReportDefinitionResponse - if err = json.Unmarshal([]byte(s), &res); err == nil && res.Data.ReportName != "" { - report = api.ReportDefinitionConfig{ - ReportName: res.Data.ReportName, - ReportType: res.Data.ReportType, - SubReportType: res.Data.SubReportType, - DisplayName: res.Data.DisplayName, - Sections: res.Data.ReportDefinitionDetails.Sections, - } - return report, nil - } - - var cfg api.ReportDefinition - if err = json.Unmarshal([]byte(s), &cfg); err == nil && cfg.ReportName != "" { - report = api.ReportDefinitionConfig{ - ReportName: cfg.ReportName, - ReportType: cfg.ReportType, - SubReportType: cfg.SubReportType, - DisplayName: cfg.DisplayName, - Sections: cfg.ReportDefinitionDetails.Sections, - } - return report, nil - } - - var yamlCfg api.ReportDefinition - if err = yaml.Unmarshal([]byte(s), &yamlCfg); err == nil && yamlCfg.ReportName != "" { - report = api.ReportDefinitionConfig{ - ReportName: yamlCfg.ReportName, - ReportType: yamlCfg.ReportType, - SubReportType: yamlCfg.SubReportType, - DisplayName: yamlCfg.DisplayName, - Sections: yamlCfg.ReportDefinitionDetails.Sections, - } - return report, nil - } - - return report, errors.New("unable to parse report definition file") -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_diff.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_diff.go deleted file mode 100644 index f08c348e8..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_diff.go +++ /dev/null @@ -1,166 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strconv" - "strings" - - "github.com/fatih/color" - "github.com/pkg/errors" - "github.com/pmezard/go-difflib/difflib" - "github.com/spf13/cobra" - "gopkg.in/yaml.v3" -) - -// reportDefinitionsDiffCommand command is used to compare 2 lacework report definition versions -var reportDefinitionsDiffCommand = &cobra.Command{ - Use: "diff ", - Short: "Compare two versions of a report definition", - Long: `Compare two versions of a report definition. - -To see a diff of two report definition versions: - - lacework report-definition diff -`, - Args: cobra.ExactArgs(3), - RunE: diffReportDefinition, -} - -func diffReportDefinition(_ *cobra.Command, args []string) error { - var ( - err error - versionOne int - versionTwo int - reportOne *diffCfg - reportTwo *diffCfg - ) - - if versionOne, err = strconv.Atoi(args[1]); err != nil { - return errors.Wrap(err, "unable to parse version") - } - - if versionTwo, err = strconv.Atoi(args[2]); err != nil { - return errors.Wrap(err, "unable to parse version") - } - - cli.StartProgress("Fetching all report definition versions...") - response, err := cli.LwApi.V2.ReportDefinitions.GetVersions(args[0]) - cli.StopProgress() - - if err != nil { - return err - } - - for _, r := range response.Data { - if r.Version == versionOne { - reportOne = &diffCfg{ - name: fmt.Sprintf("Version-%d", r.Version), - object: r, - } - } - if r.Version == versionTwo { - reportTwo = &diffCfg{ - name: fmt.Sprintf("Version-%d", r.Version), - object: r, - } - } - } - - if reportOne == nil || reportTwo == nil { - return errors.New("unable to find report definition versions") - } - - diff, err := diffAsYamlString(*reportOne, *reportTwo) - if err != nil { - return err - } - - cli.OutputHuman(diff) - return nil -} - -type diffCfg struct { - name string - object any -} - -func diffAsYamlString(objectOne, objectTwo diffCfg) (string, error) { - yamlBytesOne, err := yaml.Marshal(objectOne.object) - if err != nil { - return "", err - } - - yamlBytesTwo, err := yaml.Marshal(objectTwo.object) - if err != nil { - return "", err - } - - diff := difflib.UnifiedDiff{ - A: difflib.SplitLines(string(yamlBytesOne)), - B: difflib.SplitLines(string(yamlBytesTwo)), - FromFile: objectOne.name, - ToFile: objectTwo.name, - Context: 3, - } - - diffText, err := difflib.GetUnifiedDiffString(diff) - if err != nil { - return "", err - } - - output := prettyPrintDiff(diffText) - return output, nil -} - -func prettyPrintDiff(diff string) string { - if diff == "" { - return "" - } - - var sb = &strings.Builder{} - lines := strings.Split(diff, "\n") - - //colourize lines in diff - for _, s := range lines { - if strings.HasPrefix(s, "+") { - addition := color.HiGreenString(fmt.Sprintf("%s\n", s)) - sb.WriteString(addition) - continue - } - - if strings.HasPrefix(s, "-") { - subtraction := color.HiRedString(fmt.Sprintf("%s\n", s)) - sb.WriteString(subtraction) - continue - } - - if strings.HasPrefix(s, "@@") { - lineDiff := color.HiBlueString(fmt.Sprintf("%s\n", s)) - sb.WriteString(lineDiff) - continue - } - - sb.WriteString(fmt.Sprintf("%s\n", s)) - - } - - return sb.String() -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_revert.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_revert.go deleted file mode 100644 index 7206416a0..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_revert.go +++ /dev/null @@ -1,67 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strconv" - - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -// revert command is used to rollback lacework report definition to a previous version -var reportDefinitionsRevertCommand = &cobra.Command{ - Use: "revert ", - Aliases: []string{"restore"}, - Short: "Update a report definition", - Long: `Update an existing custom report definition. - -To revert a report definition: - - lacework report-definition revert - -To compare two report definition versions before a revert: - - lacework report-definition diff -`, - Args: cobra.ExactArgs(2), - RunE: revertReportDefinition, -} - -func revertReportDefinition(_ *cobra.Command, args []string) error { - var ( - err error - version int - ) - - if version, err = strconv.Atoi(args[1]); err != nil { - return errors.Wrap(err, "unable to parse version") - } - - cli.StartProgress("Reverting report definition...") - resp, err := cli.LwApi.V2.ReportDefinitions.Revert(args[0], version) - cli.StopProgress() - - if err != nil { - return err - } - - cli.OutputHuman("The report definition %s was reverted to version %d \n", resp.Data.ReportDefinitionGuid, version) - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_update.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_update.go deleted file mode 100644 index 8bcd687fa..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/report_definitions_update.go +++ /dev/null @@ -1,345 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/exp/slices" - "gopkg.in/yaml.v3" -) - -// update command is used to update a new lacework report definition -var reportDefinitionsUpdateCommand = &cobra.Command{ - Use: "update ", - Short: "Update a report definition", - Long: `Update an existing custom report definition. - -To update an existing report definition: - - lacework report-definition update - -To update a new report definition from an existing file: - - lacework report-definition update --file custom-report.json -`, - Args: cobra.ExactArgs(1), - RunE: updateReportDefinition, -} - -func updateReportDefinition(_ *cobra.Command, args []string) error { - var ( - reportDefinition api.ReportDefinitionUpdate - err error - ) - - cli.StartProgress("Fetching report definition...") - existingReport, err := cli.LwApi.V2.ReportDefinitions.Get(args[0]) - cli.StopProgress() - - if err != nil { - return err - } - - if existingReport.Data.CreatedBy == "SYSTEM" { - return errors.New("only user created report definitions can be modified") - } - - if reportDefinitionsCmdState.File != "" { - fileInput, err := inputReportDefinitionFromFile(reportDefinitionsCmdState.File) - if err != nil { - return err - } - - cfg, err := parseNewReportDefinition(fileInput) - if err != nil { - return err - } - reportDefinition = api.NewReportDefinitionUpdate(cfg) - } else { - reportDefinition, err = promptUpdateReportDefinition(existingReport.Data) - if err != nil { - return err - } - } - - cli.StartProgress("Updating report definition...") - resp, err := cli.LwApi.V2.ReportDefinitions.Update(args[0], reportDefinition) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to update report definition") - } - - cli.OutputHuman("The report definition %s was updated. \n", resp.Data.ReportDefinitionGuid) - return nil -} - -func promptUpdateReportDefinition(existingReport api.ReportDefinition) (api.ReportDefinitionUpdate, error) { - var useEditor bool - - if err := survey.AskOne(&survey.Confirm{Message: UpdateReportDefinitionQuestion}, &useEditor); err != nil { - return api.ReportDefinitionUpdate{}, err - } - - if useEditor { - return promptUpdateReportDefinitionFromEditor(existingReport) - } - - return promptUpdateReportDefinitionQuestions(existingReport) -} - -func promptUpdateReportDefinitionQuestions(existingReport api.ReportDefinition) ( - reportDefinition api.ReportDefinitionUpdate, err error, -) { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: UpdateReportDefinitionReportNameQuestion, Default: existingReport.ReportName}, - Validate: survey.Required, - }, - { - Name: "display", - Prompt: &survey.Input{Message: UpdateReportDefinitionDisplayNameQuestion, Default: existingReport.DisplayName}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string `survey:"name"` - DisplayName string `survey:"display"` - }{} - - if err = survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { - return - } - - reportDefinition = api.ReportDefinitionUpdate{ - ReportName: answers.Name, - DisplayName: answers.DisplayName, - } - - sections, err := promptUpdateReportDefinitionSections(existingReport) - if err != nil { - return - } - - reportDefinition.ReportDefinitionDetails = &api.ReportDefinitionDetails{Sections: sections} - - return -} - -func promptUpdateReportDefinitionSections(report api.ReportDefinition) ([]api.ReportDefinitionSection, error) { - var ( - sections = report.ReportDefinitionDetails.Sections - newSections []api.ReportDefinitionSection - err error - ) - - cli.StartProgress("Fetching list of policy ids...") - resp, err := cli.LwApi.V2.Policy.List() - cli.StopProgress() - - if err != nil { - return nil, err - } - - //filter the policies not in current report's domain - var policies []api.Policy - for _, p := range resp.Data { - domain := strings.ToUpper(report.SubReportType) - if slices.Contains(p.Tags, fmt.Sprintf("domain:%s", domain)) { - policies = append(policies, p) - } - } - - if len(policies) == 0 { - return nil, errors.New("unable to find policies") - } - - // edit existing sections - editSection := false - if err := survey.AskOne(&survey.Confirm{ - Message: UpdateReportDefinitionEditSectionQuestion, - }, &editSection); err != nil { - return nil, err - } - - if editSection { - if sections, err = promptUpdateReportDefinitionSection(§ions, policies); err != nil { - return nil, err - } - - editAnotherSection := false - for { - if err := survey.AskOne(&survey.Confirm{ - Message: UpdateReportDefinitionEditAnotherSectionQuestion, - }, &editAnotherSection); err != nil { - return nil, err - } - - if editAnotherSection { - if sections, err = promptUpdateReportDefinitionSection(&newSections, policies); err != nil { - return nil, err - } - } else { - break - } - } - } - - // add new sections - addSection := false - if err := survey.AskOne(&survey.Confirm{ - Message: UpdateReportDefinitionAddSectionQuestion, - }, &addSection); err != nil { - return nil, err - } - - if addSection { - if err := promptAddReportDefinitionSection(&newSections, policies); err != nil { - return nil, err - } - - addAnotherSection := false - for { - if err := survey.AskOne(&survey.Confirm{ - Message: CreateReportDefinitionAddSectionQuestion, - }, &addAnotherSection); err != nil { - return nil, err - } - - if addAnotherSection { - if err := promptAddReportDefinitionSection(&newSections, policies); err != nil { - return nil, err - } - } else { - break - } - } - } - - sections = append(sections, newSections...) - - return sections, nil - -} - -func promptUpdateReportDefinitionFromEditor(existingReport api.ReportDefinition) ( - reportDefinition api.ReportDefinitionUpdate, err error, -) { - var reportDefinitionConfig api.ReportDefinitionConfig - - if err != nil { - return - } - - updateCfg := api.NewReportDefinitionUpdate(existingReport.Config()) - reportTemplateYaml, err := yaml.Marshal(updateCfg) - if err != nil { - return - } - - // open editor with report yaml - report, err := inputReportDefinitionFromEditor("update", string(reportTemplateYaml)) - if err != nil { - return - } - err = yaml.Unmarshal([]byte(report), &reportDefinitionConfig) - - reportDefinition = api.NewReportDefinitionUpdate(reportDefinitionConfig) - - return -} - -func promptUpdateReportDefinitionSection(currentSections *[]api.ReportDefinitionSection, policies []api.Policy) ( - []api.ReportDefinitionSection, error, -) { - type sectionMapping struct { - section api.ReportDefinitionSection - position int - } - - var ( - policyIDs []string - selectedSections = make(map[string]sectionMapping) - sectionTitles []string - sections = *currentSections - ) - - for _, policy := range policies { - policyIDs = append(policyIDs, policy.PolicyID) - } - - for i, section := range sections { - sectionTitles = append(sectionTitles, section.Title) - selectedSections[section.Title] = sectionMapping{section: section, position: i} - } - - var selectedTitle string - if err := survey.AskOne(&survey.Select{ - Message: UpdateReportDefinitionSelectSectionQuestion, - Options: sectionTitles, - }, &selectedTitle); err != nil { - return nil, err - } - - selectedSection := selectedSections[selectedTitle] - - questions := []*survey.Question{ - { - Name: "title", - Prompt: &survey.Input{ - Message: CreateReportDefinitionSectionTitleQuestion, - Default: selectedSection.section.Title, - }, - Validate: survey.Required, - }, - { - Name: "policies", - Prompt: &survey.MultiSelect{ - Message: CreateReportDefinitionPoliciesQuestion, - Options: policyIDs, - Default: selectedSection.section.Policies, - }, - Validate: survey.MinItems(1), - }, - } - - answers := struct { - Title string `survey:"title"` - Policies []string `survey:"policies"` - }{} - - if err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { - return nil, err - } - - updatedSection := api.ReportDefinitionSection{ - Title: answers.Title, - Policies: answers.Policies, - } - - sections[selectedSection.position] = updatedSection - return sections, nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions.go deleted file mode 100644 index b97f2519b..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions.go +++ /dev/null @@ -1,226 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strings" - - "github.com/fatih/structs" - "github.com/lacework/go-sdk/api" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - CreateReportDistributionReportNameQuestion = "Report Distribution Name: " - CreateReportDistributionFrequencyQuestion = "Select Frequency: " - CreateReportDistributionDefinitionQuestion = "Select Report Definition: " - CreateReportDistributionAlertChannelsQuestion = "Select Alert Channels: " - CreateReportDistributionResourceGroupsQuestion = "Select Resource Groups: " - CreateReportDistributionIntegrationAwsQuestion = "Select Aws Accounts: " - CreateReportDistributionAddSeveritiesQuestion = "Add Severities? " - CreateReportDistributionSeveritiesQuestion = "Select Severities: " - CreateReportDistributionAddViolationsQuestion = "Add Violations? " - CreateReportDistributionScopeQuestion = "Select Distribution Scope:" - CreateReportDistributionViolationsQuestion = "Select Violations: " - UpdateReportDistributionReportNameQuestion = "Update Report Distribution Name? " - UpdateReportDistributionFrequencyQuestion = "Update Frequency?" - UpdateReportDistributionAlertChannelsQuestion = "Update Alert Channels? " - UpdateReportDistributionAddSeveritiesQuestion = "Update Severities? " - UpdateReportDistributionAddViolationsQuestion = "Update Violations? " - - // report-distributions command is used to manage lacework report distributions - reportDistributionsCommand = &cobra.Command{ - Use: "report-distribution", - Hidden: true, - Aliases: []string{"report-distributions"}, - Short: "Manage report distributions", - Long: `Manage report distributions to configure the data retrieval and layout information for a report. -`, - } - - // list command is used to list all lacework report distributions - reportDistributionsListCommand = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all report distributions", - Long: "List all report distributions configured in your Lacework account.", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - cli.StartProgress(" Fetching report distributions...") - reportDistributions, err := cli.LwApi.V2.ReportDistributions.List() - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get report distributions") - } - - if len(reportDistributions.Data) == 0 { - cli.OutputHuman("There are no report distributions configured in your account.\n") - return nil - } - - if cli.JSONOutput() { - return cli.OutputJSON(reportDistributions) - } - - var rows [][]string - for _, distribution := range reportDistributions.Data { - rows = append(rows, []string{distribution.ReportDistributionGuid, distribution.DistributionName, - distribution.Frequency, distribution.ReportDefinitionGuid}) - } - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "FREQUENCY", "DEFINITION GUID"}, rows)) - return nil - }, - } - // show command is used to retrieve a lacework report distribution by guid - reportDistributionsShowCommand = &cobra.Command{ - Use: "show ", - Short: "Show a report distribution by ID", - Long: `Show a single report distribution by it's ID. -To show specific report distribution details: - - lacework report-distribution show - -`, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - cli.StartProgress(" Fetching report distribution...") - response, err := cli.LwApi.V2.ReportDistributions.Get(args[0]) - cli.StopProgress() - - if err != nil { - return errors.Wrap(err, "unable to get report distribution") - } - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - outputReportDistributionTable(response.Data) - - return nil - }, - } - - // delete command is used to remove a lacework report distribution by id - reportDistributionsDeleteCommand = &cobra.Command{ - Use: "delete ", - Short: "Delete a report distribution", - Long: "Delete a single report distribution by it's ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress("Deleting report distribution...") - err := cli.LwApi.V2.ReportDistributions.Delete(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to delete report distribution") - } - cli.OutputHuman("The report distribution with GUID %s was deleted\n", args[0]) - return nil - }, - } -) - -func outputReportDistributionTable(reportDistribution api.ReportDistribution) { - headers := [][]string{ - {reportDistribution.ReportDistributionGuid, reportDistribution.DistributionName, reportDistribution.Frequency, - reportDistribution.ReportDefinitionGuid}, - } - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "FREQUENCY", "DEFINITION GUID"}, headers)) - cli.OutputHuman("\n") - cli.OutputHuman(buildReportDistributionDetailsTable(reportDistribution)) -} - -func init() { - // add the report-distribution command - rootCmd.AddCommand(reportDistributionsCommand) - - // add sub-commands to the report-distribution command - reportDistributionsCommand.AddCommand(reportDistributionsListCommand) - reportDistributionsCommand.AddCommand(reportDistributionsShowCommand) - reportDistributionsCommand.AddCommand(reportDistributionsDeleteCommand) - reportDistributionsCommand.AddCommand(reportDistributionsCreateCommand) - reportDistributionsCommand.AddCommand(reportDistributionsUpdateCommand) -} - -func buildReportDistributionDetailsTable(distribution api.ReportDistribution) string { - var ( - details [][]string - integrations []string - ) - - if distribution.Data.Integrations != nil { - for _, integration := range distribution.Data.Integrations { - var integrationKV strings.Builder - integrationMap := structs.Map(integration) - for k, v := range integrationMap { - if v == "" { - continue - } - integrationKV.WriteString(fmt.Sprintf("%s: %s ", k, v)) - } - integrations = append(integrations, integrationKV.String()) - } - } - - details = append(details, []string{"SEVERITY", strings.Join(distribution.Data.Severities, ", ")}) - details = append(details, []string{"VIOLATIONS", strings.Join(distribution.Data.Violations, ", ")}) - - detailsTable := &strings.Builder{} - dataTable := &strings.Builder{} - - dataTable.WriteString(renderCustomTable([]string{}, details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - )) - dataTable.WriteString("\n") - - var data [][]string - channels := strings.Join(distribution.AlertChannels, "\n") - groups := strings.Join(distribution.Data.ResourceGroups, "\n") - integrationList := strings.Join(integrations, "\n") - - data = append(data, []string{channels, groups, integrationList}) - - dataTable.WriteString(renderCustomTable([]string{"ALERT CHANNELS", "RESOURCE GROUPS", "INTEGRATIONS"}, data, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - ) - - detailsTable.WriteString(renderOneLineCustomTable("REPORT DISTRIBUTION DETAILS", - dataTable.String(), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ), - ) - - return detailsTable.String() -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_create.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_create.go deleted file mode 100644 index 5b8af9b22..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_create.go +++ /dev/null @@ -1,441 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/lwseverity" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -// create command is used to create a new lacework report distribution -var reportDistributionsCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a report distribution", - Long: `Create associates a report definition with a distribution channel for the report. -A report distribution can refine the scope of the report by filtering its content by incident severity, -resource groups, and integrations. - -To create a new report distribution: - - lacework report-distribution create -`, - Args: cobra.NoArgs, - RunE: createReportDistribution, -} - -func createReportDistribution(_ *cobra.Command, args []string) error { - var ( - reportDistribution api.ReportDistribution - err error - ) - reportDistribution, err = promptCreateReportDistributionFromNew() - if err != nil { - return err - } - - cli.StartProgress("Creating report distribution...") - resp, err := cli.LwApi.V2.ReportDistributions.Create(reportDistribution) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to create report distribution") - } - - cli.OutputHuman("New report distribution created. To view the report run:\n\n"+ - "lacework report-distribution show %s \n", resp.Data.ReportDistributionGuid) - return nil -} - -func promptCreateReportDistributionFromNew() (reportDistribution api.ReportDistribution, err error) { - cli.StartProgress("Fetching list of report definitions...") - reportDefinitions, err := cli.LwApi.V2.ReportDefinitions.List() - cli.StopProgress() - if err != nil { - return api.ReportDistribution{}, err - } - definitionMap := make(map[string]api.ReportDefinition, len(reportDefinitions.Data)) - var definitionDisplayOptions []string - - for _, definition := range reportDefinitions.Data { - definitionMap[definition.DisplayName] = definition - definitionDisplayOptions = append(definitionDisplayOptions, definition.DisplayName) - } - - cli.StartProgress("Fetching list of alert channels...") - alertChannels, err := cli.LwApi.V2.AlertChannels.List() - cli.StopProgress() - if err != nil { - return api.ReportDistribution{}, err - } - channelMap := make(map[string]string, len(alertChannels.Data)) - var channelOptions []string - - for _, channel := range alertChannels.Data { - if channel.Type == api.EmailUserAlertChannelType.String() { - channelMap[channel.Name] = channel.IntgGuid - channelOptions = append(channelOptions, channel.Name) - } - } - - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: CreateReportDistributionReportNameQuestion}, - Validate: survey.Required, - }, - { - Name: "frequency", - Prompt: &survey.Select{ - Message: CreateReportDistributionFrequencyQuestion, - Options: api.ReportDistributionFrequencies(), - }, - Validate: survey.Required, - }, - { - Name: "definition", - Prompt: &survey.Select{Message: CreateReportDistributionDefinitionQuestion, Options: definitionDisplayOptions}, - Validate: survey.Required, - }, - { - Name: "channels", - Prompt: &survey.MultiSelect{Message: CreateReportDistributionAlertChannelsQuestion, Options: channelOptions}, - Validate: survey.Required, - }, - } - - answers := struct { - Name string `survey:"name"` - Frequency string `survey:"frequency"` - Definition string `survey:"definition"` - Channels []string `survey:"channels"` - ResourceGroups []string `survey:"groups"` - Integrations []string `survey:"integrations"` - }{} - - if err = survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { - return - } - - var channelAnswers []string - for _, c := range answers.Channels { - channelAnswers = append(channelAnswers, channelMap[c]) - } - - reportDistribution = api.ReportDistribution{ - ReportDefinitionGuid: definitionMap[answers.Definition].ReportDefinitionGuid, - DistributionName: answers.Name, - AlertChannels: channelAnswers, - Frequency: answers.Frequency, - } - - // scope can only be either resource group or integration - if err = promptReportDistributionScope(&reportDistribution, - definitionMap[answers.Definition].SubReportType); err != nil { - return api.ReportDistribution{}, err - } - - // prompt optional fields - violations, err := promptAddReportDistributionViolations() - if err != nil { - return api.ReportDistribution{}, err - } - reportDistribution.Data.Violations = violations - - severities, err := promptAddReportDistributionSeverities() - - if err != nil { - return api.ReportDistribution{}, err - } - reportDistribution.Data.Severities = severities - - return -} - -func promptReportDistributionScope(distribution *api.ReportDistribution, subReportType string) error { - distributionScope := "" - if err := survey.AskOne(&survey.Select{ - Message: CreateReportDistributionScopeQuestion, - Options: api.ReportDistributionScopes(), - }, &distributionScope); err != nil { - return err - } - - if distributionScope == api.ReportDistributionScopeCloudIntegration.String() { - if err := promptReportDistributionIntegration(distribution, subReportType); err != nil { - return err - } - return nil - } else { - if err := promptReportDistributionResourceGroup(distribution); err != nil { - return err - } - return nil - } -} - -func promptReportDistributionResourceGroup(distribution *api.ReportDistribution) error { - cli.StartProgress("Fetching list of resource groups...") - resourceGroups, err := cli.LwApi.V2.ResourceGroups.List() - cli.StopProgress() - if err != nil { - return err - } - groupMap := make(map[string]string, len(resourceGroups.Data)) - - var ( - groupOptions []string - groupAnswers []string - ) - - for _, definition := range resourceGroups.Data { - groupMap[definition.Name] = definition.ID() - groupOptions = append(groupOptions, definition.Name) - } - - var selectedGroups []string - if err = survey.AskOne(&survey.MultiSelect{ - Message: CreateReportDistributionResourceGroupsQuestion, - Options: groupOptions, - }, &selectedGroups); err != nil { - return err - } - - for _, c := range selectedGroups { - groupAnswers = append(groupAnswers, groupMap[c]) - } - - distribution.Data.ResourceGroups = groupAnswers - - return nil -} - -func promptReportDistributionIntegration(distribution *api.ReportDistribution, subReportType string) error { - var integrations []api.ReportDistributionIntegration - - switch subReportType { - case api.ReportDefinitionSubTypeAws.String(): - if err := promptReportDistributionIntegrationsAws(&integrations); err != nil { - return err - } - case api.ReportDefinitionSubTypeGcp.String(): - if err := promptReportDistributionIntegrationsGcp(&integrations); err != nil { - return err - } - case api.ReportDefinitionSubTypeAzure.String(): - if err := promptReportDistributionIntegrationsAzure(&integrations); err != nil { - return err - } - default: - return errors.Errorf("unsupported report definition type '%s'", - subReportType) - } - - distribution.Data.Integrations = integrations - return nil -} - -func promptReportDistributionIntegrationsAws(integrations *[]api.ReportDistributionIntegration) error { - cli.StartProgress("Fetching Aws Account IDs...") - accounts, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AwsCfgCloudAccount) - cli.StopProgress() - - if err != nil { - return err - } - - var integrationOptions []string - - for _, ca := range accounts.Data { - if caMap, ok := ca.GetData().(map[string]interface{}); ok { - integrationOptions = append(integrationOptions, caMap["awsAccountId"].(string)) - } - } - - var integrationAnswers []string - if err = survey.AskOne(&survey.MultiSelect{ - Renderer: survey.Renderer{}, - Message: CreateReportDistributionIntegrationAwsQuestion, - Options: integrationOptions, - }, &integrationAnswers); err != nil { - return err - } - - for _, integration := range integrationAnswers { - *integrations = append(*integrations, api.ReportDistributionIntegration{AccountID: integration}) - } - - return nil -} - -func promptReportDistributionIntegrationsGcp(integrations *[]api.ReportDistributionIntegration) error { - err := promptReportDistributionIntegrationsGcpData(integrations) - if err != nil { - return err - } - - addIntegration := false - for { - if err := survey.AskOne(&survey.Confirm{ - Message: "Add another Gcp Integration?", - }, &addIntegration); err != nil { - return err - } - - if addIntegration { - err = promptReportDistributionIntegrationsGcpData(integrations) - if err != nil { - return err - } - } else { - break - } - } - - return nil -} - -func promptReportDistributionIntegrationsGcpData(integrations *[]api.ReportDistributionIntegration) error { - questions := []*survey.Question{ - { - Name: "org", - Prompt: &survey.Input{Message: "Organization ID:"}, - Validate: survey.Required, - }, - { - Name: "project", - Prompt: &survey.Input{Message: "Project ID:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Org string `survey:"org"` - Project string `survey:"project"` - }{} - - if err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { - return err - } - - *integrations = append(*integrations, api.ReportDistributionIntegration{OrganizationID: answers.Org, - ProjectID: answers.Project}) - - return nil -} - -func promptReportDistributionIntegrationsAzure(integrations *[]api.ReportDistributionIntegration) error { - err := promptReportDistributionIntegrationsAzureData(integrations) - if err != nil { - return err - } - - addIntegration := false - for { - if err := survey.AskOne(&survey.Confirm{ - Message: "Add another Azure Integration?", - }, &addIntegration); err != nil { - return err - } - - if addIntegration { - err = promptReportDistributionIntegrationsAzureData(integrations) - if err != nil { - return err - } - } else { - break - } - } - - return nil -} - -func promptReportDistributionIntegrationsAzureData(integrations *[]api.ReportDistributionIntegration) error { - questions := []*survey.Question{ - { - Name: "tenant", - Prompt: &survey.Input{Message: "Tenant ID:"}, - Validate: survey.Required, - }, - { - Name: "subscription", - Prompt: &survey.Input{Message: "Subscription ID:"}, - Validate: survey.Required, - }, - } - - answers := struct { - Tenant string `survey:"tenant"` - Subscription string `survey:"subscription"` - }{} - - if err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { - return err - } - - *integrations = append(*integrations, api.ReportDistributionIntegration{TenantID: answers.Tenant, - SubscriptionID: answers.Subscription}) - - return nil -} - -func promptAddReportDistributionViolations() (violations []string, err error) { - addViolations := false - if err = survey.AskOne(&survey.Confirm{ - Message: CreateReportDistributionAddViolationsQuestion, - }, &addViolations); err != nil { - return - } - - if addViolations { - if err = survey.AskOne(&survey.MultiSelect{ - Renderer: survey.Renderer{}, - Message: CreateReportDistributionViolationsQuestion, - Options: api.ReportDistributionViolations(), - }, &violations); err != nil { - return - } - } - - return -} - -func promptAddReportDistributionSeverities() (sevs []string, err error) { - addSevs := false - if err = survey.AskOne(&survey.Confirm{ - Message: CreateReportDistributionAddSeveritiesQuestion, - }, &addSevs); err != nil { - return - } - - if addSevs { - if err = survey.AskOne(&survey.MultiSelect{ - Message: CreateReportDistributionSeveritiesQuestion, - Options: strings.Split(lwseverity.ValidSeverities.String(), ","), - }, &sevs); err != nil { - return - } - } - - return -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_update.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_update.go deleted file mode 100644 index cc2020e5c..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/report_distributions_update.go +++ /dev/null @@ -1,360 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2023, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/lwseverity" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -// update command is used to update an existing lacework report distribution -var reportDistributionsUpdateCommand = &cobra.Command{ - Use: "update ", - Short: "Update an existing report distribution", - Long: `Update an existing report distribution. - -To update a report distribution: - - lacework report-distribution update -`, - Args: cobra.ExactArgs(1), - RunE: updateReportDistribution, -} - -func updateReportDistribution(_ *cobra.Command, args []string) error { - var ( - reportDistribution api.ReportDistributionUpdate - err error - ) - - existing, err := cli.LwApi.V2.ReportDistributions.Get(args[0]) - if err != nil { - return err - } - - reportDistribution, err = promptUpdateReportDistribution(existing.Data) - if err != nil { - return err - } - - cli.StartProgress("Updated report distribution...") - resp, err := cli.LwApi.V2.ReportDistributions.Update(args[0], reportDistribution) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to update report distribution") - } - - cli.OutputHuman("Report distribution updated. To view the report run:\n\n"+ - "lacework report-distribution show %s \n", resp.Data.ReportDistributionGuid) - return nil -} - -func promptUpdateReportDistribution(existing api.ReportDistribution) ( - reportDistribution api.ReportDistributionUpdate, err error, -) { - cli.StartProgress("Fetching list of alert channels...") - definition, err := cli.LwApi.V2.ReportDefinitions.Get(existing.ReportDefinitionGuid) - cli.StopProgress() - if err != nil { - return api.ReportDistributionUpdate{}, err - } - - cli.StartProgress("Fetching list of alert channels...") - alertChannels, err := cli.LwApi.V2.AlertChannels.List() - cli.StopProgress() - if err != nil { - return api.ReportDistributionUpdate{}, err - } - - channelMap := make(map[string]string, len(alertChannels.Data)) - channelIDMap := make(map[string]string, len(alertChannels.Data)) - var channelOptions []string - var channelDefaults []string - - for _, channel := range alertChannels.Data { - if channel.Type == api.EmailUserAlertChannelType.String() { - channelMap[channel.Name] = channel.IntgGuid - channelIDMap[channel.IntgGuid] = channel.Name - channelOptions = append(channelOptions, channel.Name) - } - } - - for _, def := range existing.AlertChannels { - channelDefaults = append(channelDefaults, channelIDMap[def]) - } - - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: UpdateReportDistributionReportNameQuestion, Default: existing.DistributionName}, - Validate: survey.Required, - }, - { - Name: "frequency", - Prompt: &survey.Select{ - Message: UpdateReportDistributionFrequencyQuestion, - Options: api.ReportDistributionFrequencies(), - Default: existing.Frequency, - }, - Validate: survey.Required, - }, - { - Name: "channels", - Prompt: &survey.MultiSelect{ - Message: UpdateReportDistributionAlertChannelsQuestion, - Options: channelOptions, - Default: channelDefaults, - }, - Validate: survey.Required, - }, - } - - answers := struct { - Name string `survey:"name"` - Frequency string `survey:"frequency"` - Definition string `survey:"definition"` - Channels []string `survey:"channels"` - Integrations []string `survey:"integrations"` - }{} - - if err = survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)); err != nil { - return - } - - var channelAnswers []string - for _, c := range answers.Channels { - channelAnswers = append(channelAnswers, channelMap[c]) - } - - reportDistribution = api.ReportDistributionUpdate{ - DistributionName: answers.Name, - AlertChannels: channelAnswers, - Frequency: answers.Frequency, - } - - // scope can only be either resource group or integration - if err = promptUpdateReportDistributionScope(&reportDistribution, - definition.Data.SubReportType, existing); err != nil { - return api.ReportDistributionUpdate{}, err - } - - // prompt optional fields - violations, err := promptUpdateReportDistributionViolations(existing) - if err != nil { - return api.ReportDistributionUpdate{}, err - } - - reportDistribution.Data.Violations = violations - - severities, err := promptUpdateReportDistributionSeverities(existing) - if err != nil { - return api.ReportDistributionUpdate{}, err - } - reportDistribution.Data.Severities = severities - - return -} - -func promptUpdateReportDistributionScope( - distribution *api.ReportDistributionUpdate, subReportType string, existing api.ReportDistribution, -) error { - distributionScope := "" - if err := survey.AskOne(&survey.Select{ - Message: CreateReportDistributionScopeQuestion, - Options: api.ReportDistributionScopes(), - }, &distributionScope); err != nil { - return err - } - - if distributionScope == api.ReportDistributionScopeCloudIntegration.String() { - if err := promptUpdateReportDistributionIntegration(distribution, subReportType, existing); err != nil { - return err - } - // clear resource group to avoid conflict - distribution.Data.ResourceGroups = []string{} - return nil - } else { - if err := promptUpdateReportDistributionResourceGroup(distribution, existing); err != nil { - return err - } - // clear integrations to avoid conflict - distribution.Data.Integrations = []api.ReportDistributionIntegration{} - return nil - } -} - -func promptUpdateReportDistributionResourceGroup( - distribution *api.ReportDistributionUpdate, existing api.ReportDistribution, -) error { - cli.StartProgress("Fetching list of resource groups...") - resourceGroups, err := cli.LwApi.V2.ResourceGroups.List() - cli.StopProgress() - if err != nil { - return err - } - - groupMap := make(map[string]string, len(resourceGroups.Data)) - groupIDMap := make(map[string]string, len(resourceGroups.Data)) - var ( - groupOptions []string - groupDefaults []string - groupAnswers []string - ) - - for _, group := range resourceGroups.Data { - groupMap[group.Name] = group.ID() - groupIDMap[group.ID()] = group.Name - groupOptions = append(groupOptions, group.Name) - } - - for _, group := range existing.Data.ResourceGroups { - groupDefaults = append(groupDefaults, groupIDMap[group]) - } - - var selectedGroups []string - if err = survey.AskOne(&survey.MultiSelect{ - Message: CreateReportDistributionResourceGroupsQuestion, - Options: groupOptions, - Default: groupDefaults, - }, &selectedGroups); err != nil { - return err - } - - for _, c := range selectedGroups { - groupAnswers = append(groupAnswers, groupMap[c]) - } - - distribution.Data.ResourceGroups = groupAnswers - return nil -} - -func promptUpdateReportDistributionIntegration( - distribution *api.ReportDistributionUpdate, reportType string, existing api.ReportDistribution, -) error { - var integrations []api.ReportDistributionIntegration - - switch reportType { - case api.ReportDefinitionSubTypeAws.String(): - if err := promptUpdateReportDistributionIntegrationsAws(&integrations, existing); err != nil { - return err - } - case api.ReportDefinitionSubTypeGcp.String(): - if err := promptReportDistributionIntegrationsGcp(&integrations); err != nil { - return err - } - case api.ReportDefinitionSubTypeAzure.String(): - if err := promptReportDistributionIntegrationsAzure(&integrations); err != nil { - return err - } - default: - return errors.Errorf("unsupported report definition type '%s'", - reportType) - } - - distribution.Data.Integrations = integrations - return nil -} - -func promptUpdateReportDistributionViolations(existing api.ReportDistribution) (violations []string, err error) { - addViolations := false - if err := survey.AskOne(&survey.Confirm{ - Message: UpdateReportDistributionAddViolationsQuestion, - }, &addViolations); err != nil { - return nil, err - } - - if addViolations { - if err := survey.AskOne(&survey.MultiSelect{ - Message: CreateReportDistributionViolationsQuestion, - Options: api.ReportDistributionViolations(), - Default: existing.Data.Violations, - }, &violations); err != nil { - return nil, err - } - } - - return violations, nil -} - -func promptUpdateReportDistributionSeverities(existing api.ReportDistribution) (sevs []string, err error) { - addSevs := false - if err = survey.AskOne(&survey.Confirm{ - Message: UpdateReportDistributionAddSeveritiesQuestion, - }, &addSevs); err != nil { - return nil, err - } - - if addSevs { - if err := survey.AskOne(&survey.MultiSelect{ - Message: CreateReportDistributionSeveritiesQuestion, - Options: strings.Split(lwseverity.ValidSeverities.String(), ","), - Default: existing.Data.Severities, - }, &sevs); err != nil { - return nil, err - } - } - - return sevs, nil -} - -func promptUpdateReportDistributionIntegrationsAws( - integrations *[]api.ReportDistributionIntegration, existing api.ReportDistribution, -) error { - cli.StartProgress("Fetching Aws Account IDs...") - accounts, err := cli.LwApi.V2.CloudAccounts.ListByType(api.AwsCfgCloudAccount) - cli.StopProgress() - - if err != nil { - return err - } - - var integrationOptions []string - - for _, ca := range accounts.Data { - if caMap, ok := ca.GetData().(map[string]interface{}); ok { - integrationOptions = append(integrationOptions, caMap["awsAccountId"].(string)) - } - } - - var existingAccounts []string - for _, integration := range existing.Data.Integrations { - existingAccounts = append(existingAccounts, integration.AccountID) - } - - var integrationAnswers []string - if err = survey.AskOne(&survey.MultiSelect{ - Renderer: survey.Renderer{}, - Message: CreateReportDistributionIntegrationAwsQuestion, - Options: integrationOptions, - Default: existingAccounts, - }, &integrationAnswers); err != nil { - return err - } - - for _, integration := range integrationAnswers { - *integrations = append(*integrations, api.ReportDistributionIntegration{AccountID: integration}) - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/report_rules.go b/vendor/github.com/lacework/go-sdk/cli/cmd/report_rules.go deleted file mode 100644 index 77a8fa1fa..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/report_rules.go +++ /dev/null @@ -1,412 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sort" - "strconv" - "strings" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/fatih/structs" - "github.com/lacework/go-sdk/api" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -var ( - // report-rules command is used to manage lacework report rules - reportRulesCommand = &cobra.Command{ - Use: "report-rule", - Aliases: []string{"report-rules", "rr"}, - Short: "Manage report rules", - Long: `Manage report rules to route reports to one or more email alert channels. - -A report rule has four parts: - - 1. Email alert channel(s) that should receive the report - 2. One or more severities to include - 3. Resource group(s) containing the subset of your environment to consider - 4. Notification types containing which report information to send -`, - } - - // list command is used to list all lacework report rules - reportRulesListCommand = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all report rules", - Long: "List all report rules configured in your Lacework account.", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - cli.StartProgress(" Fetching report rules...") - reportRules, err := cli.LwApi.V2.ReportRules.List() - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get report rules") - } - if len(reportRules.Data) == 0 { - msg := `There are no report rules configured in your account. - -Get started by configuring your report rules using the command: - - lacework report-rule create - -If you prefer to configure report rules via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Settings > Report Rules. -` - cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) - return nil - } - if cli.JSONOutput() { - return cli.OutputJSON(reportRules) - } - - var rows [][]string - for _, rule := range reportRules.Data { - rows = append(rows, []string{rule.Guid, rule.Filter.Name, rule.Filter.Status()}) - } - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "ENABLED"}, rows)) - return nil - }, - } - // show command is used to retrieve a lacework report rule by guid - reportRulesShowCommand = &cobra.Command{ - Use: "show ", - Short: "Show a report rule by ID", - Long: "Show a single report rule by it's ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var response api.ReportRuleResponse - cli.StartProgress(" Fetching report rule...") - - err := cli.LwApi.V2.ReportRules.Get(args[0], &response) - if err != nil { - cli.StopProgress() - return errors.Wrap(err, "unable to get report rule") - } - cli.StopProgress() - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - - reportRule := response.Data - headers := [][]string{ - []string{reportRule.Guid, reportRule.Filter.Name, reportRule.Filter.Status()}, - } - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "ENABLED"}, headers)) - cli.OutputHuman("\n") - cli.OutputHuman(buildReportRuleDetailsTable(reportRule)) - - return nil - }, - } - - // delete command is used to remove a lacework report rule by id - reportRulesDeleteCommand = &cobra.Command{ - Use: "delete ", - Short: "Delete a report rule", - Long: "Delete a single report rule by it's ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress(" Deleting report rule...") - err := cli.LwApi.V2.ReportRules.Delete(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to delete report rule") - } - cli.OutputHuman("The report rule with GUID %s was deleted\n", args[0]) - return nil - }, - } - - // create command is used to create a new lacework report rule - reportRulesCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a new report rule", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, args []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - - response, err := promptCreateReportRule() - if err != nil { - return errors.Wrap(err, "unable to create report rule") - } - - cli.OutputHuman("The report rule was created with GUID %s\n", response.Data.Guid) - return nil - }, - } -) - -func init() { - // add the report-rule command - rootCmd.AddCommand(reportRulesCommand) - - // add sub-commands to the report-rule command - reportRulesCommand.AddCommand(reportRulesListCommand) - reportRulesCommand.AddCommand(reportRulesShowCommand) - reportRulesCommand.AddCommand(reportRulesCreateCommand) - reportRulesCommand.AddCommand(reportRulesDeleteCommand) -} - -func buildReportRuleDetailsTable(rule api.ReportRule) string { - var ( - details [][]string - notifications [][]string - updatedTime string - ) - severities := api.NewReportRuleSeveritiesFromIntSlice(rule.Filter.Severity).ToStringSlice() - - if nano, err := strconv.ParseInt(rule.Filter.CreatedOrUpdatedTime, 10, 64); err == nil { - updatedTime = time.Unix(nano/1000, 0).Format(time.RFC3339) - } - details = append(details, []string{"SEVERITIES", strings.Join(severities, ", ")}) - details = append(details, []string{"DESCRIPTION", rule.Filter.Description}) - details = append(details, []string{"UPDATED BY", rule.Filter.CreatedOrUpdatedBy}) - details = append(details, []string{"LAST UPDATED", updatedTime}) - - detailsTable := &strings.Builder{} - detailsTable.WriteString(renderOneLineCustomTable("ALERT RULE DETAILS", - renderCustomTable([]string{}, details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ), - ) - - notifcationsMap := rule.ReportNotificationTypes.ToMap() - // sort keys - keys := make([]string, 0, len(notifcationsMap)) - for k := range notifcationsMap { - keys = append(keys, k) - } - sort.Strings(keys) - - for _, key := range keys { - notifications = append(notifications, - []string{key, cases.Title(language.English).String(strconv.FormatBool(notifcationsMap[key]))}, - ) - } - - detailsTable.WriteString(renderCustomTable([]string{"NOTIFICATION TYPES", "ENABLED"}, notifications, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - }), - ), - ) - detailsTable.WriteString("\n") - - if len(rule.EmailAlertChannels) > 0 { - channels := [][]string{{strings.Join(rule.EmailAlertChannels, "\n")}} - detailsTable.WriteString(renderCustomTable([]string{"EMAIL ALERT CHANNELS"}, channels, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - ) - detailsTable.WriteString("\n") - } - - if len(rule.Filter.ResourceGroups) > 0 { - resourceGroups := [][]string{{strings.Join(rule.Filter.ResourceGroups, "\n")}} - detailsTable.WriteString(renderCustomTable([]string{"RESOURCE GROUPS"}, resourceGroups, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - ) - } - - return detailsTable.String() -} - -func promptCreateReportRule() (api.ReportRuleResponse, error) { - channelList, channelMap := getEmailAlertChannels() - notificationFields := structs.Names(api.ReportRuleNotificationTypes{}) - notificationsMap := make(map[string]bool) - - if len(channelList) < 1 { - return api.ReportRuleResponse{}, errors.New("no email alert channels found.") - } - - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "description", - Prompt: &survey.Input{Message: "Description: "}, - Validate: survey.Required, - }, - { - Name: "channels", - Prompt: &survey.MultiSelect{ - Message: "Select email alert channels:", - Options: channelList, - }, - Validate: survey.Required, - }, - { - Name: "severities", - Prompt: &survey.MultiSelect{ - Message: "Select severities:", - Options: []string{"Critical", "High", "Medium", "Low", "Info"}, - }, - }, - { - Name: "notifications", - Prompt: &survey.MultiSelect{ - Message: "Select report notification types:", - Options: notificationFields, - }, - }, - } - - answers := struct { - Name string - Description string `survey:"description"` - Channels []string `survey:"channels"` - Severities []string `survey:"severities"` - ResourceGroups []string `survey:"resourceGroups"` - Notifications []string `survey:"notifications"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return api.ReportRuleResponse{}, err - } - - var channels []string - for _, channel := range answers.Channels { - channels = append(channels, channelMap[channel]) - } - - resourceGroups, resourceGroupMap := promptAddResourceGroupsToReportRule() - var groups []string - for _, group := range resourceGroups { - groups = append(groups, resourceGroupMap[group]) - } - - for _, n := range answers.Notifications { - notificationsMap[n] = true - } - - notifications := api.ReportRuleNotificationTypes{} - err = api.TransformReportRuleNotification(notificationsMap, ¬ifications) - if err != nil { - return api.ReportRuleResponse{}, err - } - - reportRule, err := api.NewReportRule( - answers.Name, - api.ReportRuleConfig{ - Description: answers.Description, - Severities: api.NewReportRuleSeverities(answers.Severities), - ResourceGroups: groups, - EmailAlertChannels: channels, - NotificationTypes: api.ReportRuleNotifications{notifications}, - }) - - if err != nil { - return api.ReportRuleResponse{}, err - } - - cli.StartProgress(" Creating report rule...") - defer cli.StopProgress() - - return cli.LwApi.V2.ReportRules.Create(reportRule) -} - -func getEmailAlertChannels() ([]string, map[string]string) { - cli.StartProgress("") - defer cli.StopProgress() - response, err := cli.LwApi.V2.AlertChannels.List() - - if err != nil { - return nil, nil - } - var items = make(map[string]string) - var channels = make([]string, 0) - for _, i := range response.Data { - if i.AlertChannelType() == api.EmailUserAlertChannelType { - displayName := fmt.Sprintf("%s - %s", i.ID(), i.Name) - channels = append(channels, displayName) - items[displayName] = i.ID() - } - } - - return channels, items -} - -func promptAddResourceGroupsToReportRule() ([]string, map[string]string) { - addResourceGroups := false - err := survey.AskOne(&survey.Confirm{ - Message: "Add Resource Groups to Report Rule?", - }, &addResourceGroups) - - if err != nil { - return nil, nil - } - - if addResourceGroups { - var groups []string - groupList, groupMap := getResourceGroups() - - err = survey.AskOne(&survey.MultiSelect{ - Message: "Select Resource Groups:", - Options: groupList, - }, &groups) - - if err != nil { - return nil, nil - } - return groups, groupMap - } - return nil, nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/resource_group_v2.go b/vendor/github.com/lacework/go-sdk/cli/cmd/resource_group_v2.go deleted file mode 100644 index 8002c82c7..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/resource_group_v2.go +++ /dev/null @@ -1,94 +0,0 @@ -// -// Author:: Zeki Sherif() -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "errors" - "fmt" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" -) - -func createResourceGroup(resourceType string) error { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "description", - Prompt: &survey.Input{Message: "Description: "}, - Validate: survey.Required, - }, - { - Name: "query", - Prompt: inputRGQueryFromEditor(resourceType), - Validate: survey.Required, - }, - } - - answers := struct { - Name string `survey:"name"` - Description string `survey:"description"` - Query string `survey:"query"` - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - - if err != nil { - return err - } - - var rgQuery api.RGQuery - err = json.Unmarshal([]byte(answers.Query), &rgQuery) - if err != nil { - return err - } - - groupType, isValid := api.FindResourceGroupType(resourceType) - if !isValid { - // This should never reach this. The type is controlled by us in cmd/resource_groups - return errors.New("internal error") - } - resourceGroup := api.NewResourceGroup(answers.Name, groupType, answers.Description, &rgQuery) - cli.StartProgress(" Creating resource group...") - _, err = cli.LwApi.V2.ResourceGroups.Create(resourceGroup) - cli.StopProgress() - return err -} - -func inputRGQueryFromEditor(resourceType string) *survey.Editor { - iType, _ := api.FindResourceGroupType(resourceType) - - prompt := &survey.Editor{ - Message: fmt.Sprintf("Type a query for the new %s Resource Group", resourceType), - FileName: "resourceGroupQuery*.json", - Help: "Refer to https://lwdocs-rg2.netlify.app/api/api-resource-group/ for examples of a query", - } - prompt.Default = iType.QueryTemplate() - prompt.HideDefault = true - prompt.AppendDefault = true - - return prompt -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/resource_groups.go b/vendor/github.com/lacework/go-sdk/cli/cmd/resource_groups.go deleted file mode 100644 index e3667c7fd..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/resource_groups.go +++ /dev/null @@ -1,254 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strconv" - "time" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // resource-groups command is used to manage lacework resource groups - resourceGroupsCommand = &cobra.Command{ - Use: "resource-group", - Aliases: []string{"resource-groups", "rg"}, - Short: "Manage resource groups", - Long: "Manage Lacework-identifiable assets via the use of resource groups.", - } - - // list command is used to list all lacework resource groups - resourceGroupsListCommand = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all resource groups", - Long: "List all resource groups configured in your Lacework account.", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - resourceGroups, err := cli.LwApi.V2.ResourceGroups.List() - if err != nil { - return errors.Wrap(err, "unable to get resource groups") - } - if len(resourceGroups.Data) == 0 { - msg := `There are no resource groups configured in your account. - -Get started by integrating your resource groups to manage alerting using the command: - - lacework resource-group create - -If you prefer to configure resource groups via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Settings > Resource Groups. -` - cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) - return nil - } - - groups := make([]resourceGroup, 0) - for _, g := range resourceGroups.Data { - - groups = append(groups, resourceGroup{ - Id: g.ResourceGroupGuid, - ResType: g.Type, - Name: g.Name, - Enabled: g.Enabled, - IsDefaultBoolean: g.IsDefaultBoolean, - Query: g.Query, - }) - } - - if cli.JSONOutput() { - jsonOut := struct { - Groups []resourceGroup `json:"resource_groups"` - }{Groups: groups} - return cli.OutputJSON(jsonOut) - } - - rows := [][]string{} - for _, g := range groups { - rows = append(rows, []string{g.Id, g.ResType, g.Name, strconv.Itoa(g.Enabled), - strconv.FormatBool(*g.IsDefaultBoolean)}) - } - - cli.OutputHuman(renderSimpleTable([]string{"RESOURCE GROUP ID", "TYPE", "NAME", "STATUS", "DEFAULT"}, rows)) - return nil - }, - } - // show command is used to retrieve a lacework resource group by resource id - resourceGroupsShowCommand = &cobra.Command{ - Use: "show ", - Short: "Get resource group by ID", - Long: "Get a single resource group by it's resource group ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var response api.ResourceGroupResponse - err := cli.LwApi.V2.ResourceGroups.Get(args[0], &response) - - if err != nil { - return errors.Wrap(err, "unable to get resource group") - } - - group := resourceGroup{ - Id: response.Data.ResourceGroupGuid, - ResType: response.Data.Type, - Name: response.Data.Name, - Enabled: response.Data.Enabled, - IsDefaultBoolean: response.Data.IsDefaultBoolean, - Query: response.Data.Query, - Description: response.Data.Description, - UpdatedBy: response.Data.UpdatedBy, - UpdatedTime: response.Data.UpdatedTime, - CreatedTime: response.Data.CreatedTime, - CreatedBy: response.Data.CreatedBy, - } - - if cli.JSONOutput() { - jsonOut := struct { - Group resourceGroup `json:"resource_group"` - }{Group: group} - return cli.OutputJSON(jsonOut) - } - - var groupCommon [][]string - - groupCommon = append(groupCommon, - []string{group.Id, group.ResType, group.Name, group.Description, strconv.Itoa(group.Enabled), - strconv.FormatBool(*group.IsDefaultBoolean), group.CreatedBy, group.CreatedTime.UTC().String(), - group.UpdatedBy, group.UpdatedTime.UTC().String()}, - ) - cli.OutputHuman(renderSimpleTable([]string{"RESOURCE GROUP ID", "TYPE", "NAME", "DESCRIPTION", "STATE", - "DEFAULT", "CREATED BY", "CREATED TIME", "UPDATED BY", "UPDATED TIME"}, groupCommon)) - - return nil - }, - } - - // delete command is used to remove a lacework resource group by resource id - resourceGroupsDeleteCommand = &cobra.Command{ - Use: "delete ", - Short: "Delete a resource group", - Long: "Delete a single resource group by it's resource group ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - err := cli.LwApi.V2.ResourceGroups.Delete(args[0]) - if err != nil { - return errors.Wrap(err, "unable to delete resource group") - } - - cli.OutputHuman("The resource group was deleted.\n") - return nil - }, - } - - // create command is used to create a new lacework resource group - resourceGroupsCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a new resource group", - Long: "Creates a new single resource group.", - RunE: func(_ *cobra.Command, args []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - - err := promptCreateResourceGroup() - if err != nil { - return errors.Wrap(err, "unable to create resource group") - } - - cli.OutputHuman("The resource group was created.\n") - return nil - }, - } -) - -func promptCreateResourceGroup() error { - - resourceGroupOptions := []string{ - "AWS", - "AZURE", - "CONTAINER", - "GCP", - "MACHINE", - "OCI", - "KUBERNETES", - } - - var ( - group = "" - prompt = &survey.Select{ - Message: "Choose a resource group type to create: ", - Options: resourceGroupOptions, - } - err = survey.AskOne(prompt, &group) - ) - if err != nil { - return err - } - - switch group { - case "AWS": - return createResourceGroup("AWS") - case "AZURE": - return createResourceGroup("AZURE") - case "GCP": - return createResourceGroup("GCP") - case "CONTAINER": - return createResourceGroup("CONTAINER") - case "MACHINE": - return createResourceGroup("MACHINE") - case "OCI": - return createResourceGroup("OCI") - case "KUBERNETES": - return createResourceGroup("KUBERNETES") - default: - return errors.New("unknown resource group type") - } -} - -func init() { - // add the resource-group command - rootCmd.AddCommand(resourceGroupsCommand) - - // add sub-commands to the resource-group command - resourceGroupsCommand.AddCommand(resourceGroupsListCommand) - resourceGroupsCommand.AddCommand(resourceGroupsShowCommand) - resourceGroupsCommand.AddCommand(resourceGroupsCreateCommand) - resourceGroupsCommand.AddCommand(resourceGroupsDeleteCommand) -} - -type resourceGroup struct { - Id string `json:"resourceGroupGuid"` - ResType string `json:"type"` - Name string `json:"name"` - Enabled int `json:"enabled"` - IsDefaultBoolean *bool `json:"isDefaultBoolean"` - Query *api.RGQuery `json:"query"` - Description string `json:"description,omitempty"` - UpdatedTime *time.Time `json:"updatedTime,omitempty"` - UpdatedBy string `json:"updatedBy,omitempty"` - CreatedBy string `json:"createdBy,omitempty"` - CreatedTime *time.Time `json:"createdTime,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/root.go b/vendor/github.com/lacework/go-sdk/cli/cmd/root.go deleted file mode 100644 index 79b50f71f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/root.go +++ /dev/null @@ -1,431 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "os" - "strings" - - "github.com/fatih/color" - homedir "github.com/mitchellh/go-homedir" - "github.com/pkg/errors" - "github.com/spf13/cast" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/lacework/go-sdk/lwcomponent" - "github.com/lacework/go-sdk/lwlogger" -) - -var ( - // the global cli state with defaults - cli = NewDefaultState() - - // rootCmd represents the base command when called without any subcommands - rootCmd = &cobra.Command{ - Use: "lacework", - Short: "A tool to manage the Lacework cloud security platform.", - DisableAutoGenTag: true, - SilenceErrors: true, - Long: `The Lacework Command Line Interface is a tool that helps you manage the -Lacework cloud security platform. Use it to manage compliance reports, -external integrations, vulnerability scans, and other operations. - -Start by configuring the Lacework CLI with the command: - - lacework configure - -This will prompt you for your Lacework account and a set of API access keys.`, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - if isComponent(cmd.Annotations) { - return componentPersistentPreRun(cmd, args) - } - return cliPersistentPreRun(cmd, args) - }, - PersistentPostRunE: func(cmd *cobra.Command, _ []string) error { - // skip daily version check if the user is running the version command - if cmd.Use == "version" { - return nil - } - - // run the daily version check but do not fail if we couldn't check - // this is not a critical part of the CLI and we do not want to impact - // cusomters workflows or CI systems - if err := dailyVersionCheck(); err != nil { - cli.Log.Debugw("unable to run daily version check", "error", err) - } - - return nil - }, - } -) - -func cliPersistentPreRun(cmd *cobra.Command, args []string) error { - cli.Log.Debugw("updating honeyvent", "dataset", HoneyDataset) - cli.Event.Command = cmd.CommandPath() - cli.Event.Args = args - cli.Event.Flags = parseFlags(os.Args[1:]) - - switch cmd.Use { - case "help [command]", "configure", "version", "docs ", "generate-pkg-manifest": - return nil - default: - if cmd.HasParent() { - switch cmd.Parent().Use { - case "configure", "completion": - // @afiune no need to create a client for any configure - // command or any completion command - return nil - } - } - if err := cli.NewClient(); err != nil { - if !strings.Contains(err.Error(), "Invalid Account") { - return err - } - - if err := cli.Migrations(); err != nil { - return err - } - } - } - cli.SendHoneyvent() - return nil -} - -func componentPersistentPreRun(cmd *cobra.Command, args []string) error { - cli.Event.Command = cmd.CommandPath() - cli.Event.Component = cmd.Use - cli.Event.Flags = parseFlags(os.Args[1:]) - defer cli.SendHoneyvent() - - // For components, we disable flag parsing, therefore we - // split args into those handled by the CLI and those - // we pass to the component manually - cli.componentParser.parseArgs(cmd.Flags(), args) - cli.Event.Args = cli.componentParser.componentArgs - err := cmd.Flags().Parse(cli.componentParser.cliArgs) - - // We call initConfig() again after global flags have been parsed. - initConfig() - - if err != nil { - cli.Event.Error = err.Error() - cli.Log.Debugw("unable to parse global flags", "error", err, - "provided_flags", cli.componentParser.cliArgs) - } - - cli.Log.Debugw("honeyvent updated", "dataset", HoneyDataset) - - return cli.NewClient() -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() (err error) { - defer func() { - switch err := err.(type) { - case *vulnerabilityPolicyError: - exitwithCode(err, err.ExitCode) - case *queryFailonError: - exitwithCode(err, err.ExitCode) - case *lwcomponent.RunError: - // by default, all our components should display the error to - // the end user, which is why we don't output it, but we still - // exit the main program with the exit code from the component - os.Exit(err.ExitCode) - } - }() - defer cli.Wait() - - setupRootHelpCommand() - - // first, verify if the user provided a command to execute, - // if no command was provided, only print out the usage message - if noCommandProvided() { - errcheckWARN(rootCmd.Help()) - os.Exit(127) - } - - if err = rootCmd.Execute(); err != nil { - // send a new error event to Honeycomb - cli.Event.Error = err.Error() - cli.SendHoneyvent() - } - - return -} - -func setupRootHelpCommand() { - // We want 'lacework help iac org list' to invoke 'iac help org list' - // instead of just showing help for lacework. The command in question is - // the default help command for the root command, so we peek at the - // target and if it's a component then invoke the component. - rootCmd.InitDefaultHelpCmd() - helpCommand, _, _ := rootCmd.Find([]string{"help"}) - defaultRunHelp := helpCommand.Run - helpCommand.Run = nil - helpCommand.RunE = func(cmd *cobra.Command, args []string) error { - target, _, _ := rootCmd.Find(args) - if target != nil && isComponent(target.Annotations) { - f, ok := cli.LwComponents.GetComponent(target.Use) - if ok { - envs := []string{ - fmt.Sprintf("LW_COMPONENT_NAME=%s", target.Use), - } - helpArgs := append([]string{"help"}, args[1:]...) - return f.RunAndOutput(helpArgs, envs...) - } - } - defaultRunHelp(cmd, args) - return nil - } -} - -func init() { - // initialize cobra - cobra.OnInitialize(initConfig) - - // Note - do not add new global flags without considering the consequences - // on components. These global flags will be consumed by the CLI and - // NOT passed onto a component. A new global flag runs the risk of not - // allowing a component to accept a flag that it previously was expecting. - rootCmd.PersistentFlags().Bool("debug", false, - "turn on debug logging", - ) - rootCmd.PersistentFlags().Bool("nocolor", false, - "turn off colors", - ) - rootCmd.PersistentFlags().Bool("nocache", false, - "turn off caching", - ) - rootCmd.PersistentFlags().Bool("noninteractive", false, - "turn off interactive mode (disable spinners, prompts, etc.)", - ) - rootCmd.PersistentFlags().Bool("json", false, - "switch commands output from human-readable to json format", - ) - rootCmd.PersistentFlags().StringP("profile", "p", "", - "switch between profiles configured at ~/.lacework.toml", - ) - rootCmd.PersistentFlags().StringP("api_key", "k", "", - "access key id", - ) - rootCmd.PersistentFlags().StringP("api_secret", "s", "", - "secret access key", - ) - rootCmd.PersistentFlags().String("api_token", "", - "access token (replaces the use of api_key and api_secret)", - ) - rootCmd.PersistentFlags().StringP("account", "a", "", - "account subdomain of URL (i.e. .lacework.net)", - ) - rootCmd.PersistentFlags().String("subaccount", "", - "sub-account name inside your organization (org admins only)", - ) - rootCmd.PersistentFlags().Bool("organization", false, - "access organization level data sets (org admins only)", - ) - - errcheckWARN(viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))) - errcheckWARN(viper.BindPFlag("nocolor", rootCmd.PersistentFlags().Lookup("nocolor"))) - errcheckWARN(viper.BindPFlag("nocache", rootCmd.PersistentFlags().Lookup("nocache"))) - errcheckWARN(viper.BindPFlag("noninteractive", rootCmd.PersistentFlags().Lookup("noninteractive"))) - errcheckWARN(viper.BindPFlag("json", rootCmd.PersistentFlags().Lookup("json"))) - errcheckWARN(viper.BindPFlag("profile", rootCmd.PersistentFlags().Lookup("profile"))) - errcheckWARN(viper.BindPFlag("account", rootCmd.PersistentFlags().Lookup("account"))) - errcheckWARN(viper.BindPFlag("api_key", rootCmd.PersistentFlags().Lookup("api_key"))) - errcheckWARN(viper.BindPFlag("api_secret", rootCmd.PersistentFlags().Lookup("api_secret"))) - errcheckWARN(viper.BindPFlag("api_token", rootCmd.PersistentFlags().Lookup("api_token"))) - errcheckWARN(viper.BindPFlag("subaccount", rootCmd.PersistentFlags().Lookup("subaccount"))) - errcheckWARN(viper.BindPFlag("organization", rootCmd.PersistentFlags().Lookup("organization"))) - - cobra.AddTemplateFunc("isComponent", isComponent) - cobra.AddTemplateFunc("hasInstalledCommands", hasInstalledCommands) - rootCmd.SetUsageTemplate(usageTemplate()) -} - -func usageTemplate() string { - // nolint:lll - return `Usage:{{if .Runnable}} - {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} - {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasExample}} - -Examples: -{{.Example}}{{end}}{{if .HasAvailableSubCommands}} - -Available Commands:{{range .Commands}}{{if not (isComponent .Annotations)}}{{if (or .IsAvailableCommand (eq .Name "help"))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{if (and hasInstalledCommands (not .HasParent))}} - -Commands from components:{{range .Commands}}{{if isComponent .Annotations}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} - -Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} - -Global Flags: -{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} - -Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} - {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} - -Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} -` -} - -// initConfig reads in config file and ENV variables if set -func initConfig() { - // Find home directory - home, err := homedir.Dir() - errcheckEXIT(err) - - // Search config in home directory with name ".lacework" (without extension) - viper.AddConfigPath(home) - viper.SetConfigName(".lacework") - - viper.SetConfigType("toml") // set TOML as the config format - viper.SetEnvPrefix("LW") // set prefix for all env variables LW_ABC - viper.AutomaticEnv() // read in environment variables that match - - logLevel := "" - if viper.GetBool("debug") { - logLevel = "DEBUG" - } - - // initialize a Lacework logger - cli.Log = lwlogger.New(logLevel).Sugar() - - if viper.GetBool("nocolor") { - cli.Log.Info("turning off colors") - cli.JsonF.DisabledColor = true - color.NoColor = true - os.Setenv("NO_COLOR", "true") - } - - if b := viper.Get("noninteractive"); b != nil { - if cast.ToBool(b) { - cli.NonInteractive() - } else { - cli.Interactive() - } - } - - if viper.GetBool("nocache") { - cli.NoCache() - } - - if viper.GetBool("json") { - cli.EnableJSONOutput() - } - - // try to read config file - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - // the config file was not found; ignore error - cli.Log.Debugw("configuration file not found") - } else { - // the config file was found but another error was produced - exitwith(errors.Wrap(err, "unable to read in config file ~/.lacework.toml")) - } - } else { - cli.Log.Debugw("using configuration file", - "path", viper.ConfigFileUsed(), - ) - } - - // initialize cli cache library - cli.InitCache() - - // get the profile passed as a parameter or environment variable - // if any, set it into the CLI state, that will trigger to load the - // state, if no profile was specified just load the default state - if p := viper.GetString("profile"); len(p) != 0 { - err = cli.SetProfile(p) - } else if p, cacheErr := cli.Cache.Read("global/profile"); cacheErr == nil { - cli.Log.Debugw("loading profile from cache", "profile", string(p)) - err = cli.SetProfile(string(p)) - } else { - err = cli.LoadState() - } - - if err != nil { - if isCommand("configure") { - cli.Log.Debugw( - "error ignored", - "reason", "running configure cmd", - "error", err, - ) - } else { - // TODO @afiune figure out how to propagate this to main() - exitwith(err) - } - } -} - -// isCommand checks the overall arguments passed to the lacework cli -// and returns true if the provided command name is the one running -func isCommand(cmd string) bool { - if len(os.Args) <= 1 { - return false - } - - if os.Args[1] == cmd { - return true - } - - return false -} - -// noCommandProvided checks if a command or argument was provided -func noCommandProvided() bool { - return len(os.Args) <= 1 -} - -// errcheckEXIT is a simple macro to check Golang errors, if the provided -// error is nil, it doesn't do anything, but if the error has something, -// it exits the program -func errcheckEXIT(err error) { - if err != nil { - exitwith(err) - } -} - -// errcheckWARN is similar to errcheckEXIT but it doesn't exit the program, -// it only prints a WARNING message to the user, useful for those cases where -// we know there won't be aproblem but the linter still asks to check all errors -func errcheckWARN(err error) { - if err != nil { - fmt.Fprintf(os.Stderr, "WARN %s\n", err) - } -} - -// exitwith prints out an error message and exits the program with exit code 1 -func exitwith(err error) { - exitwithCode(err, 1) -} - -// exitwithCode prints out an error message and exits the program with -// the provided exit code -func exitwithCode(err error, code int) { - fmt.Fprintf(os.Stderr, "\nERROR %s\n", err) - os.Exit(code) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions.go deleted file mode 100644 index c9ad322f8..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions.go +++ /dev/null @@ -1,271 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/fatih/color" - - "github.com/aws/aws-sdk-go-v2/aws/arn" - "github.com/lacework/go-sdk/api" - "github.com/spf13/cobra" - "golang.org/x/exp/slices" -) - -var ( - // top-level suppressions command - suppressionsCommand = &cobra.Command{ - Use: "suppressions", - Hidden: true, - Aliases: []string{"suppression", "sup", "sups"}, - Short: "Manage legacy suppressions", - Long: "Manage legacy suppressions", - } - - // suppressionsAwsCmd represents the aws sub-command inside the suppressions command - suppressionsAwsCmd = &cobra.Command{ - Use: "aws", - Short: "Manage legacy suppressions for aws", - } - - // suppressionsAzureCmd represents the aws sub-command inside the suppressions command - suppressionsAzureCmd = &cobra.Command{ - Use: "azure", - Short: "Manage legacy suppressions for azure", - } - - // suppressionsGcpCmd represents the aws sub-command inside the suppressions command - suppressionsGcpCmd = &cobra.Command{ - Use: "gcp", - Short: "Manage legacy suppressions for gcp", - } -) - -func init() { - rootCmd.AddCommand(suppressionsCommand) - // aws - suppressionsCommand.AddCommand(suppressionsAwsCmd) - suppressionsAwsCmd.AddCommand(suppressionsListAwsCmd) - suppressionsAwsCmd.AddCommand(suppressionsMigrateAwsCmd) - // azure - suppressionsCommand.AddCommand(suppressionsAzureCmd) - suppressionsAzureCmd.AddCommand(suppressionsListAzureCmd) - suppressionsAzureCmd.AddCommand(suppressionsMigrateAzureCmd) - // gcp - suppressionsCommand.AddCommand(suppressionsGcpCmd) - suppressionsGcpCmd.AddCommand(suppressionsListGcpCmd) - suppressionsGcpCmd.AddCommand(suppressionsMigrateGcpCmd) -} - -func autoConvertSuppressions(convertedPolicyExceptions []map[string]api.PolicyException) { - cli.StartProgress("Creating policy exceptions...") - for _, exceptionMap := range convertedPolicyExceptions { - for policyId, exception := range exceptionMap { - response, err := cli.LwApi.V2.Policy.Exceptions.Create(policyId, exception) - if err != nil { - cli.Log.Debug(err, "unable to create exception") - cli.OutputHuman(color.RedString( - "Error creating policy exception to create exception. %s"), - err) - continue - } - cli.OutputHuman("Exception created for PolicyId: %s - ExceptionId: %s\n\n", - color.GreenString(policyId), color.BlueString(response.Data.ExceptionID)) - } - } - - cli.StopProgress() -} - -func printPayloadsText(payloadsText []string) { - if len(payloadsText) >= 1 { - cli.OutputHuman(color.YellowString("#### Legacy Suppressions --> Exceptions payloads\n\n")) - for _, payload := range payloadsText { - cli.OutputHuman(color.GreenString("%s \n\n", payload)) - } - } else { - cli.OutputHuman("No legacy suppressions found that could be migrated\n") - } -} - -func printConvertedSuppressions(convertedSuppressions []map[string]api.PolicyException) { - if len(convertedSuppressions) >= 1 { - cli.OutputHuman(color.YellowString("#### Converted legacy suppressions in Policy Exception" + - " format" + - "\n")) - for _, exception := range convertedSuppressions { - err := cli.OutputJSON(exception) - if err != nil { - return - } - } - colorizeR := color.New(color.FgRed, color.Bold) - cli.OutputHuman(colorizeR.Sprintf("WARNING: Before continuing, " + - "please thoroughly inspect the above exceptions to ensure they are valid and" + - " required. By continuing, you accept liability for any compliance violations" + - " missed as a result of the above exceptions!\n\n")) - - } -} - -func printDiscardedSuppressions(discardedSuppressions []map[string]api.SuppressionV2) { - if len(discardedSuppressions) >= 1 { - cli.OutputHuman(color.YellowString("#### Discarded legacy suppressions\n")) - for _, suppression := range discardedSuppressions { - err := cli.OutputJSON(suppression) - if err != nil { - return - } - } - } -} - -func convertSupCondition(supConditions []string, fieldKey string, - policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint { - if len(supConditions) >= 1 && slices.Contains( - policyIdExceptionsTemplate, fieldKey) { - - var condition []any - // verify for aws: - // if "ALL_ACCOUNTS" OR "ALL_REGIONS" is in the suppression condition slice - // verify for azure: - // if "ALL_TENANTS" OR "ALL_SUBSCRIPTIONS" is in the suppression condition slice - // verify for gcp: - // if "ALL_ORGANIZATIONS" OR "ALL_PROJECTS" is in the suppression condition slice - // if so we should ignore the supplied conditions and replace with a wildcard * - if (slices.Contains(supConditions, "ALL_ACCOUNTS") && fieldKey == "accountIds") || - (slices.Contains(supConditions, "ALL_REGIONS") && fieldKey == "regionNames") { - condition = append(condition, "*") - } else if (slices.Contains(supConditions, "ALL_ORGANIZATIONS") && fieldKey == "organizations") || - (slices.Contains(supConditions, "ALL_PROJECTS") && fieldKey == "projects") { - condition = append(condition, "*") - } else if (slices.Contains(supConditions, "ALL_TENANTS") && fieldKey == "tenants") || - (slices.Contains(supConditions, "ALL_SUBSCRIPTIONS") && fieldKey == "subscriptions") { - condition = append(condition, "*") - } else if fieldKey == "resourceNames" || fieldKey == "resourceName" { - condition = convertResourceNamesSupConditions(supConditions) - } else { - condition = convertToAnySlice(supConditions) - } - - return api.PolicyExceptionConstraint{ - FieldKey: fieldKey, - FieldValues: condition, - } - } - return api.PolicyExceptionConstraint{} -} - -func convertResourceNamesSupConditions(supConditions []string) []any { - var conditions []any - for _, condition := range supConditions { - ok := arn.IsARN(condition) - // If the legacy suppression resourceNames field contains an ARN, we should parse and pull - // out the resource name. ARNs are not supported in policy exceptions - if ok { - parsedEntry, _ := arn.Parse(condition) - condition = parsedEntry.Resource - } - conditions = append(conditions, condition) - } - return conditions -} - -func convertGcpResourceNameSupConditions(supConditions []string, fieldKey string, - policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint { - if len(supConditions) >= 1 && slices.Contains( - policyIdExceptionsTemplate, fieldKey) { - var conditions []any - for _, condition := range supConditions { - // skip this logic if we already have a wildcard - if condition != "*" { - // It appears that for GCP, the resourceName field for policy exceptions is in fact expecting - // users to provider the full GCP resource_id. - // Example resourceId: - // => //compute.googleapis.com/projects/gke-project-01-c8403ba1/zones/us-central1-a/instances/squid-proxy - // This was not the case for legacy suppressions and in most cases it's unlikely that the - // users will have provided this. Instead, we are more likely to have - // the resource name provided. To cover this scenario we prepend the resource name - // from the legacy suppression with "*/" to make it match the resource name while - // wildcarding the rest of the resourceId - condition = "*/" + condition - } - conditions = append(conditions, condition) - } - return api.PolicyExceptionConstraint{ - FieldKey: fieldKey, - FieldValues: conditions, - } - } - return api.PolicyExceptionConstraint{} -} - -func convertSupConditionTags(supCondition []map[string]string, fieldKey string, - policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint { - if len(supCondition) >= 1 && slices.Contains( - policyIdExceptionsTemplate, fieldKey) { - - // api.PolicyExceptionConstraint expects []any for the FieldValues - // Therefore we need to take the supCondition []map[string]string and append each map to - // the new convertedTags []any var - var convertedTags []any - for _, tagMap := range supCondition { - convertedTags = append(convertedTags, tagMap) - } - - return api.PolicyExceptionConstraint{ - FieldKey: fieldKey, - FieldValues: convertedTags, - } - } - return api.PolicyExceptionConstraint{} -} - -func getPoliciesExceptionConstraintsMap() map[string][]string { - // get a list of all policies and parse the valid exception constraints and return a map of - // {"": []} - policies, err := cli.LwApi.V2.Policy.List() - if err != nil { - return nil - } - - policiesSupportedConstraints := make(map[string][]string) - for _, policy := range policies.Data { - exceptionConstraints := getPolicyExceptionConstraintsSlice(policy.ExceptionConfiguration) - policiesSupportedConstraints[policy.PolicyID] = exceptionConstraints - } - - return policiesSupportedConstraints -} - -func convertToAnySlice(slice []string) []any { - s := make([]interface{}, len(slice)) - for i, v := range slice { - s[i] = v - } - return s -} - -func updateDiscardedSupConditionsComments(suppressionInfo api.SuppressionV2, comment string) api.SuppressionV2 { - var updatedSupInfo api.SuppressionV2 - for _, suppression := range suppressionInfo.SuppressionConditions { - suppression.Comment = comment - updatedSupInfo.SuppressionConditions = append(updatedSupInfo.SuppressionConditions, suppression) - } - return updatedSupInfo -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_aws.go b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_aws.go deleted file mode 100644 index cded4d9c7..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_aws.go +++ /dev/null @@ -1,437 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/fatih/color" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // https://docs.lacework.com/console/aws-compliance-policy-exceptions-criteria#lacework-custom-policies-for-aws-iam - // https://docs.lacework.com/console/cis-aws-140-benchmark-report#identity-and-access-management - // old ID to new ID mapping, using the old Constraints with the hope they match the new Constraints - awsEquivalencesMap = map[string]string{ - "AWS_CIS_1_2": "lacework-global-39", - "AWS_CIS_1_3": "lacework-global-41", - "AWS_CIS_1_4": "lacework-global-43", - "AWS_CIS_1_9": "lacework-global-37", - "AWS_CIS_1_10": "lacework-global-38", - "AWS_CIS_1_12": "lacework-global-34", - "AWS_CIS_1_13": "lacework-global-35", - "AWS_CIS_1_14": "lacework-global-69", - "AWS_CIS_1_15": "lacework-global-33", - "AWS_CIS_1_16": "lacework-global-44", // no iam policies to users - //"AWS_CIS_1_19": "lacework-global-31", manual - //"AWS_CIS_1_20": "lacework-global-32", manual - //"AWS_CIS_1_21": "lacework-global-70", manual - "AWS_CIS_1_22": "lacework-global-46", - "AWS_CIS_1_23": "lacework-global-40", - "AWS_CIS_1_24": "lacework-global-45", - "AWS_CIS_2_1": "lacework-global-53", - "AWS_CIS_2_2": "lacework-global-75", - "AWS_CIS_2_3": "lacework-global-54", // s3 bucket cloudtrail log - "AWS_CIS_2_4": "lacework-global-55", - "AWS_CIS_2_5": "lacework-global-76", - "AWS_CIS_2_6": "lacework-global-56", // s3 bucket cloudtrail log - "AWS_CIS_2_7": "lacework-global-77", - "AWS_CIS_2_8": "lacework-global-78", - "AWS_CIS_2_9": "lacework-global-79", - "AWS_CIS_3_1": "lacework-global-57", - "AWS_CIS_3_2": "lacework-global-58", - "AWS_CIS_3_3": "lacework-global-59", - "AWS_CIS_3_4": "lacework-global-60", - "AWS_CIS_3_5": "lacework-global-61", - "AWS_CIS_3_6": "lacework-global-82", - "AWS_CIS_3_7": "lacework-global-83", - "AWS_CIS_3_8": "lacework-global-62", - "AWS_CIS_3_9": "lacework-global-84", - "AWS_CIS_3_10": "lacework-global-85", - "AWS_CIS_3_11": "lacework-global-86", - "AWS_CIS_3_12": "lacework-global-63", - "AWS_CIS_3_13": "lacework-global-64", - "AWS_CIS_3_14": "lacework-global-65", - "AWS_CIS_4_1": "lacework-global-68", - "AWS_CIS_4_2": "lacework-global-68", - "AWS_CIS_4_3": "lacework-global-79", - "AWS_CIS_4_4": "lacework-global-87", - "LW_S3_1": "lacework-global-130", - "LW_S3_2": "lacework-global-131", - "LW_S3_3": "lacework-global-132", - "LW_S3_4": "lacework-global-133", - "LW_S3_5": "lacework-global-134", - "LW_S3_6": "lacework-global-135", - "LW_S3_7": "lacework-global-136", - "LW_S3_8": "lacework-global-137", - "LW_S3_9": "lacework-global-138", - "LW_S3_10": "lacework-global-139", - "LW_S3_11": "lacework-global-140", - "LW_S3_12": "lacework-global-94", - "LW_S3_13": "lacework-global-95", - "LW_S3_14": "lacework-global-217", - "LW_S3_15": "lacework-global-96", - "LW_S3_16": "lacework-global-97", - "LW_S3_18": "lacework-global-98", - "LW_S3_19": "lacework-global-99", - "LW_S3_20": "lacework-global-100", - "LW_S3_21": "lacework-global-101", - "LW_AWS_IAM_1": "lacework-global-115", - "LW_AWS_IAM_2": "lacework-global-116", - "LW_AWS_IAM_3": "lacework-global-117", - "LW_AWS_IAM_4": "lacework-global-118", - "LW_AWS_IAM_5": "lacework-global-119", - "LW_AWS_IAM_6": "lacework-global-120", - "LW_AWS_IAM_7": "lacework-global-121", - "LW_AWS_IAM_11": "lacework-global-181", // non-root user - "LW_AWS_IAM_12": "lacework-global-142", - "LW_AWS_IAM_13": "lacework-global-141", - "LW_AWS_IAM_14": "lacework-global-105", - // "AWS_CIS_4_5" : "88 (Manual)", - "LW_AWS_NETWORKING_1": "lacework-global-227", // sec-group - "LW_AWS_NETWORKING_2": "lacework-global-145", // network acl - "LW_AWS_NETWORKING_3": "lacework-global-146", // network acl - "LW_AWS_NETWORKING_4": "lacework-global-147", - "LW_AWS_NETWORKING_5": "lacework-global-148", - "LW_AWS_NETWORKING_6": "lacework-global-149", - "LW_AWS_NETWORKING_7": "lacework-global-228", - "LW_AWS_NETWORKING_8": "lacework-global-229", - "LW_AWS_NETWORKING_9": "lacework-global-230", - "LW_AWS_NETWORKING_10": "lacework-global-231", - "LW_AWS_NETWORKING_11": "lacework-global-199", - "LW_AWS_NETWORKING_12": "lacework-global-150", - "LW_AWS_NETWORKING_13": "lacework-global-151", - "LW_AWS_NETWORKING_14": "lacework-global-152", - "LW_AWS_NETWORKING_15": "lacework-global-153", - "LW_AWS_NETWORKING_16": "lacework-global-225", - "LW_AWS_NETWORKING_17": "lacework-global-226", - "LW_AWS_NETWORKING_18": "lacework-global-154", - "LW_AWS_NETWORKING_19": "lacework-global-155", - "LW_AWS_NETWORKING_20": "lacework-global-156", - "LW_AWS_NETWORKING_21": "lacework-global-104", - "LW_AWS_NETWORKING_22": "lacework-global-106", - "LW_AWS_NETWORKING_23": "lacework-global-107", - "LW_AWS_NETWORKING_24": "lacework-global-108", - "LW_AWS_NETWORKING_25": "lacework-global-109", - "LW_AWS_NETWORKING_26": "lacework-global-110", - "LW_AWS_NETWORKING_27": "lacework-global-111", - "LW_AWS_NETWORKING_28": "lacework-global-112", - "LW_AWS_NETWORKING_29": "lacework-global-113", - "LW_AWS_NETWORKING_30": "lacework-global-114", - "LW_AWS_NETWORKING_31": "lacework-global-218", - "LW_AWS_NETWORKING_32": "lacework-global-219", - "LW_AWS_NETWORKING_33": "lacework-global-220", - "LW_AWS_NETWORKING_34": "lacework-global-221", - "LW_AWS_NETWORKING_35": "lacework-global-222", - "LW_AWS_NETWORKING_36": "lacework-global-148", - "LW_AWS_NETWORKING_37": "lacework-global-102", - "LW_AWS_NETWORKING_38": "lacework-global-223", - "LW_AWS_NETWORKING_39": "lacework-global-184", - "LW_AWS_NETWORKING_40": "lacework-global-103", - "LW_AWS_NETWORKING_41": "lacework-global-125", // cloudfront - "LW_AWS_NETWORKING_42": "lacework-global-126", // cloudfront - "LW_AWS_NETWORKING_43": "lacework-global-127", - "LW_AWS_NETWORKING_44": "lacework-global-231", - "LW_AWS_NETWORKING_45": "lacework-global-482", - "LW_AWS_NETWORKING_46": "lacework-global-157", - "LW_AWS_NETWORKING_47": "lacework-global-128", - "LW_AWS_NETWORKING_49": "lacework-global-159", - "LW_AWS_NETWORKING_50": "lacework-global-129", // cloudfront - "LW_AWS_NETWORKING_51": "lacework-global-483", - "LW_AWS_MONGODB_1": "lacework-global-196", // not documented - "LW_AWS_MONGODB_2": "lacework-global-196", - "LW_AWS_MONGODB_3": "lacework-global-197", - "LW_AWS_MONGODB_4": "lacework-global-197", - "LW_AWS_MONGODB_5": "lacework-global-198", - "LW_AWS_MONGODB_6": "lacework-global-198", - "LW_AWS_GENERAL_SECURITY_1": "lacework-global-89", // ec2 tags - "LW_AWS_GENERAL_SECURITY_2": "lacework-global-90", - "LW_AWS_GENERAL_SECURITY_3": "lacework-global-160", - "LW_AWS_GENERAL_SECURITY_4": "lacework-global-171", - "LW_AWS_GENERAL_SECURITY_5": "lacework-global-91", - "LW_AWS_GENERAL_SECURITY_6": "lacework-global-92", - "LW_AWS_GENERAL_SECURITY_7": "lacework-global-182", - "LW_AWS_GENERAL_SECURITY_8": "lacework-global-183", - "LW_AWS_SERVERLESS_1": "lacework-global-179", - "LW_AWS_SERVERLESS_2": "lacework-global-180", - "LW_AWS_SERVERLESS_4": "lacework-global-143", - "LW_AWS_SERVERLESS_5": "lacework-global-144", - "LW_AWS_RDS_1": "lacework-global-93", - "LW_AWS_ELASTICSEARCH_1": "lacework-global-122", - "LW_AWS_ELASTICSEARCH_2": "lacework-global-123", - "LW_AWS_ELASTICSEARCH_3": "lacework-global-124", - "LW_AWS_ELASTICSEARCH_4": "lacework-global-161", - } - - // suppressionsMigrateAwsCmd represents the aws sub-command inside the suppressions migrate command - suppressionsMigrateAwsCmd = &cobra.Command{ - Use: "migrate", - Aliases: []string{"mig"}, - Short: "Migrate legacy suppressions for AWS to mapped policy exceptions", - RunE: suppressionsAwsMigrate, - } - - // suppressionsListAwsCmd represents the aws sub-command inside the suppressions list command - suppressionsListAwsCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List legacy suppressions for AWS", - RunE: suppressionsAwsList, - } -) - -func suppressionsAwsList(_ *cobra.Command, _ []string) error { - var ( - suppressions map[string]api.SuppressionV2 - err error - ) - - suppressions, err = cli.LwApi.V2.Suppressions.Aws.List() - if err != nil { - if strings.Contains(err.Error(), "No active AWS accounts") { - cli.OutputHuman("No active AWS accounts found. " + - "Unable to get legacy aws suppressions\n") - return nil - } - return errors.Wrap(err, "Unable to get legacy aws suppressions") - } - - if len(suppressions) == 0 { - cli.OutputHuman("No legacy AWS suppressions found.\n") - return nil - } - return cli.OutputJSON(suppressions) -} - -func suppressionsAwsMigrate(_ *cobra.Command, _ []string) error { - var ( - suppressionsMap map[string]api.SuppressionV2 - err error - - convertedPolicyExceptions []map[string]api.PolicyException - payloadsText []string - discardedSuppressions []map[string]api.SuppressionV2 - ) - suppressionsMap, err = cli.LwApi.V2.Suppressions.Aws.List() - if err != nil { - if strings.Contains(err.Error(), "No active AWS accounts") { - cli.OutputHuman("No active AWS accounts found. " + - "Unable to get legacy aws suppressions") - return nil - } - return errors.Wrap(err, "Unable to get legacy aws suppressions") - } - - if len(suppressionsMap) == 0 { - cli.OutputHuman("No legacy AWS suppressions found.\n") - return nil - } - - answer := "" - manualMigration := "Output translated legacy suppressions as policy exception commands to be" + - " run manually (Recommended)" - autoMigration := "Auto migrate legacy suppressions.\nDISCLAIMER: " + - "By selecting this option, you accept liability for the migration and " + - "any compliance violations missed as a result of the added exceptions" - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: "Select your legacy suppression migration approach?", - Options: []string{ - manualMigration, - autoMigration, - }, - }, - Response: &answer, - }); err != nil { - return err - } - - // get a list of all policies and parse the valid exception constraints and create a map of - // {"": []} - policyExceptionsConstraintsMap := getPoliciesExceptionConstraintsMap() - - switch answer { - case manualMigration: - _, payloadsText, discardedSuppressions = convertAwsSuppressions( - suppressionsMap, - policyExceptionsConstraintsMap, - ) - printPayloadsText(payloadsText) - printDiscardedSuppressions(discardedSuppressions) - case autoMigration: - convertedPolicyExceptions, _, discardedSuppressions = convertAwsSuppressions( - suppressionsMap, - policyExceptionsConstraintsMap, - ) - printConvertedSuppressions(convertedPolicyExceptions) - confirm := false - err := survey.AskOne(&survey.Confirm{ - Message: "Confirm the above exceptions have been reviewed and you wish to continue" + - " with the auto migration.", - }, &confirm) - if err != nil { - return err - } - if confirm { - autoConvertSuppressions(convertedPolicyExceptions) - printDiscardedSuppressions(discardedSuppressions) - cli.OutputHuman(color.GreenString("To view the newly created Exceptions, " + - "try running `lacework policy-exceptions list ")) - } else { - cli.OutputHuman("Cancelled Legacy Suppression to Exception migration!") - } - } - - return nil -} - -func convertAwsSuppressions( - suppressionsMap map[string]api.SuppressionV2, - policyExceptionsConstraintsMap map[string][]string, -) ([]map[string]api.PolicyException, - []string, []map[string]api.SuppressionV2) { - var ( - convertedPolicyExceptions []map[string]api.PolicyException - payloadsText []string - discardedSuppressions []map[string]api.SuppressionV2 - ) - - for id, suppressionInfo := range suppressionsMap { - // verify there is a mapped policy for this recommendation - // if the recommendation is not a key in the map we can assume this is not mapped and - // continue - mappedPolicyId, ok := awsEquivalencesMap[id] - if !ok { - // when we don't have a mapped policy, add the legacy suppression info - if suppressionInfo.SuppressionConditions != nil { - suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, - "Legacy suppression discarded as there is no equivalent policy") - discardedSuppressions = append( - discardedSuppressions, - map[string]api.SuppressionV2{id: suppressionInfo}, - ) - } - continue - } - - // get the supported policy exception fields for the mapped policy - // in order to ensure we have an up-to-date list of exception constraints we need to - // get the policy from the /api/v2/Policies/ api. - // We then parse this into a list of constraints - policyIdExceptionsTemplate := policyExceptionsConstraintsMap[mappedPolicyId] - if policyIdExceptionsTemplate == nil { - // Updating the suppression conditions comments to make it clear why these were - // discarded - if len(suppressionInfo.SuppressionConditions) >= 1 { - suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, - fmt.Sprintf("Legacy suppression discarded as the new policy: %s does not"+ - " support exception conditions", mappedPolicyId)) - - // if the list of supported constraints is empty for a policy, - // we should let the customers know that we have discarded this suppression - discardedSuppressions = append( - discardedSuppressions, - map[string]api.SuppressionV2{id: suppressionInfo}, - ) - } - continue - } - if len(suppressionInfo.SuppressionConditions) >= 1 { - for _, suppression := range suppressionInfo.SuppressionConditions { - // used to store the converted legacy suppressions - var convertedConstraints []api.PolicyExceptionConstraint - - accountIdsConstraint := convertSupCondition(suppression.AccountIds, - "accountIds", - policyIdExceptionsTemplate) - if accountIdsConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, accountIdsConstraint) - } - - regionNamesConstraint := convertSupCondition(suppression.RegionNames, - "regionNames", - policyIdExceptionsTemplate) - if regionNamesConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, regionNamesConstraint) - } - - resourceNamesConstraint := convertSupCondition(suppression.ResourceNames, - "resourceNames", - policyIdExceptionsTemplate) - if resourceNamesConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, resourceNamesConstraint) - } - - resourceTagsConstraint := convertSupConditionTags(suppression.ResourceTags, - "resourceTags", - policyIdExceptionsTemplate) - if resourceTagsConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, resourceTagsConstraint) - } - - description := fmt.Sprintf( - "Migrated exception from legacy compliance policy %s. ", id, - ) - if suppression.Comment != "" { - description = description + fmt.Sprintf( - "Legacy Policy comment: %s", suppression.Comment, - ) - } - if len(convertedConstraints) >= 1 { - convertedPolicyExceptions = append( - convertedPolicyExceptions, - map[string]api.PolicyException{ - mappedPolicyId: { - Description: description, - Constraints: convertedConstraints, - }, - }, - ) - - exception := api.PolicyException{ - Description: description, - Constraints: convertedConstraints, - } - jsonException, err := json.Marshal(exception) - if err != nil { - cli.Log.Error(err) - } - - payloadsText = append( - payloadsText, - fmt.Sprintf( - "lacework api post '/Exceptions?policyId=%s' -d '%s'", - mappedPolicyId, - jsonException, - ), - ) - } - } - } - } - - return convertedPolicyExceptions, payloadsText, discardedSuppressions -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_azure.go b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_azure.go deleted file mode 100644 index c90247456..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_azure.go +++ /dev/null @@ -1,414 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/fatih/color" - - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - azureEquivalencesMap = map[string]string{ - "Azure_CIS_131_1_1": "lacework-global-514", - "Azure_CIS_131_1_2": "lacework-global-597", - "Azure_CIS_131_1_3": "lacework-global-499", - "Azure_CIS_131_1_4": "lacework-global-500", - "Azure_CIS_131_1_5": "lacework-global-501", - "Azure_CIS_131_1_6": "lacework-global-503", - "Azure_CIS_131_1_7": "lacework-global-504", - "Azure_CIS_131_1_8": "lacework-global-505", - "Azure_CIS_131_1_9": "lacework-global-506", - "Azure_CIS_131_1_10": "lacework-global-507", - "Azure_CIS_131_1_11": "lacework-global-508", - "Azure_CIS_131_1_12": "lacework-global-509", - //"Azure_CIS_131_1_13": "N/A", - "Azure_CIS_131_1_14": "lacework-global-590", - "Azure_CIS_131_1_15": "lacework-global-510", - "Azure_CIS_131_1_16": "lacework-global-591", - "Azure_CIS_131_1_17": "lacework-global-592", - "Azure_CIS_131_1_18": "lacework-global-593", - "Azure_CIS_131_1_19": "lacework-global-594", - "Azure_CIS_131_1_20": "lacework-global-511", - "Azure_CIS_131_1_21": "lacework-global-512", - "Azure_CIS_131_1_22": "lacework-global-513", - "Azure_CIS_131_1_23": "lacework-global-595", - "Azure_CIS_131_2_1": "lacework-global-598", - "Azure_CIS_131_2_2": "lacework-global-599", - "Azure_CIS_131_2_3": "lacework-global-601", - "Azure_CIS_131_2_4": "lacework-global-602", - "Azure_CIS_131_2_5": "lacework-global-604", - //"Azure_CIS_131_2_6": "N/A", - "Azure_CIS_131_2_7": "lacework-global-605", - "Azure_CIS_131_2_8": "lacework-global-607", - "Azure_CIS_131_2_9": "lacework-global-614", - "Azure_CIS_131_2_10": "lacework-global-613", - "Azure_CIS_131_2_11": "lacework-global-524", - "Azure_CIS_131_2_12": "lacework-global-523", - "Azure_CIS_131_2_13": "lacework-global-526", - "Azure_CIS_131_2_14": "lacework-global-527", - "Azure_CIS_131_2_15": "lacework-global-525", - "Azure_CIS_131_3_1": "lacework-global-528", - "Azure_CIS_131_3_2": "lacework-global-530", - "Azure_CIS_131_3_3": "lacework-global-616", - "Azure_CIS_131_3_4": "lacework-global-531", - "Azure_CIS_131_3_5": "lacework-global-532", - "Azure_CIS_131_3_6": "lacework-global-533", - "Azure_CIS_131_3_7": "lacework-global-617", - "Azure_CIS_131_3_8": "lacework-global-535", - "Azure_CIS_131_3_9": "lacework-global-618", - "Azure_CIS_131_3_10": "lacework-global-619", - "Azure_CIS_131_3_11": "lacework-global-620", - "Azure_CIS_131_4_1_1": "lacework-global-537", - "Azure_CIS_131_4_1_2": "lacework-global-540", - "Azure_CIS_131_4_1_3": "lacework-global-541", - "Azure_CIS_131_4_2_1": "lacework-global-622", - "Azure_CIS_131_4_2_2": "lacework-global-623", - "Azure_CIS_131_4_2_3": "lacework-global-624", - "Azure_CIS_131_4_2_4": "lacework-global-625", - "Azure_CIS_131_4_2_5": "lacework-global-542", - "Azure_CIS_131_4_3_1": "lacework-global-543", - "Azure_CIS_131_4_3_2": "lacework-global-551", - "Azure_CIS_131_4_3_3": "lacework-global-544", - "Azure_CIS_131_4_3_4": "lacework-global-545", - "Azure_CIS_131_4_3_5": "lacework-global-546", - "Azure_CIS_131_4_3_6": "lacework-global-547", - "Azure_CIS_131_4_3_7": "lacework-global-548", - "Azure_CIS_131_4_3_8": "lacework-global-549", - "Azure_CIS_131_4_4": "lacework-global-539", - "Azure_CIS_131_4_5": "lacework-global-621", - "Azure_CIS_131_5_1_1": "lacework-global-554", - "Azure_CIS_131_5_1_2": "lacework-global-555", - "Azure_CIS_131_5_1_3": "lacework-global-556", - "Azure_CIS_131_5_1_4": "lacework-global-630", - "Azure_CIS_131_5_1_5": "lacework-global-557", - "Azure_CIS_131_5_2_1": "lacework-global-558", - "Azure_CIS_131_5_2_2": "lacework-global-559", - "Azure_CIS_131_5_2_3": "lacework-global-560", - "Azure_CIS_131_5_2_4": "lacework-global-561", - //"Azure_CIS_131_5_2_5": "N/A", - //"Azure_CIS_131_5_2_6": "N/A", - "Azure_CIS_131_5_2_7": "lacework-global-562", - "Azure_CIS_131_5_2_8": "lacework-global-563", - "Azure_CIS_131_5_2_9": "lacework-global-564", - "Azure_CIS_131_5_3": "lacework-global-553", - "Azure_CIS_131_6_1": "lacework-global-568", - "Azure_CIS_131_6_2": "lacework-global-569", - "Azure_CIS_131_6_3": "lacework-global-538", - "Azure_CIS_131_6_4": "lacework-global-633", - "Azure_CIS_131_6_5": "lacework-global-634", - "Azure_CIS_131_6_6": "lacework-global-570", - "Azure_CIS_131_7_1": "lacework-global-573", - "Azure_CIS_131_7_2": "lacework-global-635", - "Azure_CIS_131_7_3": "lacework-global-636", - "Azure_CIS_131_7_4": "lacework-global-574", - "Azure_CIS_131_7_5": "lacework-global-522", - "Azure_CIS_131_7_6": "lacework-global-637", - "Azure_CIS_131_7_7": "lacework-global-638", - "Azure_CIS_131_8_1": "lacework-global-575", - "Azure_CIS_131_8_2": "lacework-global-577", - "Azure_CIS_131_8_3": "lacework-global-645", - "Azure_CIS_131_8_4": "lacework-global-579", - //"Azure_CIS_131_8_5": "N/A", - "Azure_CIS_131_9_1": "lacework-global-642", - "Azure_CIS_131_9_2": "lacework-global-580", - "Azure_CIS_131_9_3": "lacework-global-581", - "Azure_CIS_131_9_4": "lacework-global-643", - "Azure_CIS_131_9_5": "lacework-global-582", - "Azure_CIS_131_9_6": "lacework-global-583", - "Azure_CIS_131_9_7": "lacework-global-584", - "Azure_CIS_131_9_8": "lacework-global-585", - "Azure_CIS_131_9_9": "lacework-global-586", - "Azure_CIS_131_9_10": "lacework-global-587", - "Azure_CIS_131_9_11": "lacework-global-644", - } - - // suppressionsMigrateAzureCmd represents the azure sub-command inside the suppressions migrate - //command - suppressionsMigrateAzureCmd = &cobra.Command{ - Use: "migrate", - Aliases: []string{"mig"}, - Short: "Migrate legacy suppressions for Azure to mapped policy exceptions", - RunE: suppressionsAzureMigrate, - } - - // suppressionsListAzureCmd represents the azure sub-command inside the suppressions list - //command - suppressionsListAzureCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List legacy suppressions for Azure", - RunE: suppressionsAzureList, - } -) - -func suppressionsAzureList(_ *cobra.Command, _ []string) error { - var ( - suppressions map[string]api.SuppressionV2 - err error - ) - - suppressions, err = cli.LwApi.V2.Suppressions.Azure.List() - if err != nil { - if strings.Contains(err.Error(), "No active Azure accounts") { - cli.OutputHuman("No active Azure accounts found. " + - "Unable to get legacy Azure suppressions\n") - return nil - } - return errors.Wrap(err, "Unable to get legacy Azure suppressions") - } - - if len(suppressions) == 0 { - cli.OutputHuman("No legacy Azure suppressions found.\n") - return nil - } - return cli.OutputJSON(suppressions) -} - -func suppressionsAzureMigrate(_ *cobra.Command, _ []string) error { - var ( - suppressionsMap map[string]api.SuppressionV2 - err error - - convertedPolicyExceptions []map[string]api.PolicyException - payloadsText []string - discardedSuppressions []map[string]api.SuppressionV2 - ) - suppressionsMap, err = cli.LwApi.V2.Suppressions.Azure.List() - if err != nil { - if strings.Contains(err.Error(), "No active Azure accounts") { - cli.OutputHuman("No active Azure accounts found. " + - "Unable to get legacy Azure suppressions\n") - return nil - } - return errors.Wrap(err, "Unable to get legacy Azure suppressions") - } - - if len(suppressionsMap) == 0 { - cli.OutputHuman("No legacy Azure suppressions found.\n") - return nil - } - - answer := "" - manualMigration := "Output translated legacy suppressions as policy exception commands to be" + - " run manually (Recommended)" - autoMigration := "Auto migrate legacy suppressions.\nDISCLAIMER: " + - "By selecting this option, you accept liability for the migration and " + - "any compliance violations missed as a result of the added exceptions" - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: "Select your legacy suppression migration approach?", - Options: []string{ - manualMigration, - autoMigration, - }, - }, - Response: &answer, - }); err != nil { - return err - } - - // get a list of all policies and parse the valid exception constraints and create a map of - // {"": []} - policyExceptionsConstraintsMap := getPoliciesExceptionConstraintsMap() - - switch answer { - case manualMigration: - _, payloadsText, discardedSuppressions = convertAzureSuppressions( - suppressionsMap, - policyExceptionsConstraintsMap, - ) - printPayloadsText(payloadsText) - printDiscardedSuppressions(discardedSuppressions) - case autoMigration: - convertedPolicyExceptions, _, discardedSuppressions = convertAzureSuppressions( - suppressionsMap, - policyExceptionsConstraintsMap, - ) - printConvertedSuppressions(convertedPolicyExceptions) - confirm := false - err := survey.AskOne(&survey.Confirm{ - Message: "Confirm the above exceptions have been reviewed and you wish to continue" + - " with the auto migration.", - }, &confirm) - if err != nil { - return err - } - if confirm { - autoConvertSuppressions(convertedPolicyExceptions) - printDiscardedSuppressions(discardedSuppressions) - cli.OutputHuman(color.GreenString("To view the newly created Exceptions, " + - "try running `lacework policy-exceptions list ")) - } else { - cli.OutputHuman("Cancelled Legacy Suppression to Exception migration!") - } - } - - return nil -} - -func convertAzureSuppressions( - suppressionsMap map[string]api.SuppressionV2, - policyExceptionsConstraintsMap map[string][]string, -) ([]map[string]api.PolicyException, - []string, []map[string]api.SuppressionV2) { - var ( - convertedPolicyExceptions []map[string]api.PolicyException - payloadsText []string - discardedSuppressions []map[string]api.SuppressionV2 - ) - - for id, suppressionInfo := range suppressionsMap { - // verify there is a mapped policy for this recommendation - // if the recommendation is not a key in the map we can assume this is not mapped and - // continue - mappedPolicyId, ok := azureEquivalencesMap[id] - if !ok { - // when we don't have a mapped policy, add the legacy suppression info - if suppressionInfo.SuppressionConditions != nil { - suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, - "Legacy suppression discarded as there is no equivalent policy") - discardedSuppressions = append( - discardedSuppressions, - map[string]api.SuppressionV2{id: suppressionInfo}, - ) - } - continue - } - - // get the supported policy exception fields for the mapped policy - // in order to ensure we have an up-to-date list of exception constraints we need to - // get the policy from the /api/v2/Policies/ api. - // We then parse this into a list of constraints - policyIdExceptionsTemplate := policyExceptionsConstraintsMap[mappedPolicyId] - if policyIdExceptionsTemplate == nil { - // Updating the suppression conditions comments to make it clear why these were - // discarded - if len(suppressionInfo.SuppressionConditions) >= 1 { - suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, - fmt.Sprintf("Legacy suppression discarded as the new policy: %s does not"+ - " support exception conditions", mappedPolicyId)) - - // if the list of supported constraints is empty for a policy, - // we should let the customers know that we have discarded this suppression - discardedSuppressions = append( - discardedSuppressions, - map[string]api.SuppressionV2{id: suppressionInfo}, - ) - } - continue - } - if len(suppressionInfo.SuppressionConditions) >= 1 { - for _, suppression := range suppressionInfo.SuppressionConditions { - // used to store the converted legacy suppressions - var convertedConstraints []api.PolicyExceptionConstraint - - resourceGroupNamesConstraint := convertSupCondition(suppression.ResourceGroupNames, - "azureResourceGroup", - policyIdExceptionsTemplate) - if resourceGroupNamesConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, resourceGroupNamesConstraint) - } - - regionNamesConstraint := convertSupCondition(suppression.RegionNames, - "regionNames", - policyIdExceptionsTemplate) - if regionNamesConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, regionNamesConstraint) - } - - tenantsConstraint := convertSupCondition(suppression.TenantIds, - "tenants", - policyIdExceptionsTemplate) - if tenantsConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, tenantsConstraint) - } - - subscriptionsConstraint := convertSupCondition(suppression.SubscriptionIds, - "subscriptions", - policyIdExceptionsTemplate) - if subscriptionsConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, subscriptionsConstraint) - } - - resourceNamesConstraint := convertSupCondition(suppression.ResourceNames, - "resourceName", - policyIdExceptionsTemplate) - if resourceNamesConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, resourceNamesConstraint) - } - - resourceTagsConstraint := convertSupConditionTags(suppression.ResourceTags, - "resourceTags", - policyIdExceptionsTemplate) - if resourceTagsConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, resourceTagsConstraint) - } - - description := fmt.Sprintf( - "Migrated exception from legacy compliance policy %s. ", id, - ) - if suppression.Comment != "" { - description = description + fmt.Sprintf( - "Legacy Policy comment: %s", suppression.Comment, - ) - } - if len(convertedConstraints) >= 1 { - convertedPolicyExceptions = append( - convertedPolicyExceptions, - map[string]api.PolicyException{ - mappedPolicyId: { - Description: description, - Constraints: convertedConstraints, - }, - }, - ) - - exception := api.PolicyException{ - Description: description, - Constraints: convertedConstraints, - } - jsonException, err := json.Marshal(exception) - if err != nil { - cli.Log.Error(err) - } - - payloadsText = append( - payloadsText, - fmt.Sprintf( - "lacework api post '/Exceptions?policyId=%s' -d '%s'", - mappedPolicyId, - jsonException, - ), - ) - } - } - } - } - - return convertedPolicyExceptions, payloadsText, discardedSuppressions -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_gcp.go b/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_gcp.go deleted file mode 100644 index 18836b0a8..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/suppressions_gcp.go +++ /dev/null @@ -1,368 +0,0 @@ -// -// Author:: Ross Moles () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/fatih/color" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - gcpEquivalencesMap = map[string]string{ - "GCP_CIS12_1_2": "lacework-global-233", - "GCP_CIS12_1_3": "lacework-global-293", - "GCP_CIS12_1_4": "lacework-global-234", - "GCP_CIS12_1_5": "lacework-global-235", - "GCP_CIS12_1_6": "lacework-global-236", - "GCP_CIS12_1_7": "lacework-global-237", - "GCP_CIS12_1_8": "lacework-global-294", - "GCP_CIS12_1_9": "lacework-global-238", - "GCP_CIS12_1_10": "lacework-global-239", - "GCP_CIS12_1_11": "lacework-global-295", - "GCP_CIS12_1_12": "lacework-global-296", - "GCP_CIS12_1_13": "lacework-global-240", - "GCP_CIS12_1_14": "lacework-global-241", - "GCP_CIS12_1_15": "lacework-global-242", - "GCP_CIS12_2_1": "lacework-global-245", - "GCP_CIS12_2_2": "lacework-global-246", - "GCP_CIS12_2_3": "lacework-global-298", - "GCP_CIS12_2_4": "lacework-global-247", - "GCP_CIS12_2_5": "lacework-global-248", - "GCP_CIS12_2_6": "lacework-global-249", - "GCP_CIS12_2_7": "lacework-global-250", - "GCP_CIS12_2_8": "lacework-global-251", - "GCP_CIS12_2_9": "lacework-global-252", - "GCP_CIS12_2_10": "lacework-global-253", - "GCP_CIS12_2_11": "lacework-global-254", - "GCP_CIS12_2_12": "lacework-global-255", - "GCP_CIS12_3_1": "lacework-global-300", - "GCP_CIS12_3_2": "lacework-global-258", - "GCP_CIS12_3_3": "lacework-global-259", - "GCP_CIS12_3_4": "lacework-global-260", - "GCP_CIS12_3_5": "lacework-global-261", - "GCP_CIS12_3_6": "lacework-global-301", - "GCP_CIS12_3_7": "lacework-global-302", - "GCP_CIS12_3_8": "lacework-global-262", - "GCP_CIS12_3_9": "lacework-global-263", - "GCP_CIS12_3_10": "lacework-global-303", - "GCP_CIS12_4_1": "lacework-global-264", - "GCP_CIS12_4_2": "lacework-global-265", - "GCP_CIS12_4_3": "lacework-global-266", - "GCP_CIS12_4_4": "lacework-global-267", - "GCP_CIS12_4_5": "lacework-global-268", - "GCP_CIS12_4_6": "lacework-global-269", - "GCP_CIS12_4_7": "lacework-global-304", - "GCP_CIS12_4_8": "lacework-global-305", - "GCP_CIS12_4_9": "lacework-global-306", - "GCP_CIS12_4_10": "lacework-global-307", - "GCP_CIS12_4_11": "lacework-global-308", - "GCP_CIS12_5_1": "lacework-global-270", - "GCP_CIS12_5_2": "lacework-global-310", - "GCP_CIS12_6_1_1": "lacework-global-274", - "GCP_CIS12_6_1_2": "lacework-global-275", - "GCP_CIS12_6_1_3": "lacework-global-276", - //"GCP_CIS12_6_2_1": "N/A", - "GCP_CIS12_6_2_2": "lacework-global-312", - "GCP_CIS12_6_2_3": "lacework-global-277", - "GCP_CIS12_6_2_4": "lacework-global-278", - //"GCP_CIS12_6_2_5": "N/A", - //"GCP_CIS12_6_2_6": "N/A", - "GCP_CIS12_6_2_7": "lacework-global-279", - "GCP_CIS12_6_2_8": "lacework-global-280", - //"GCP_CIS12_6_2_9": "N/A", - //"GCP_CIS12_6_2_10": "N/A", - //"GCP_CIS12_6_2_11": "N/A", - //"GCP_CIS12_6_2_12": "N/A", - "GCP_CIS12_6_2_13": "lacework-global-281", - "GCP_CIS12_6_2_14": "lacework-global-282", - //"GCP_CIS12_6_2_15": "N/A", - "GCP_CIS12_6_2_16": "lacework-global-283", - "GCP_CIS12_6_3_1": "lacework-global-285", - "GCP_CIS12_6_3_2": "lacework-global-286", - "GCP_CIS12_6_3_3": "lacework-global-287", - "GCP_CIS12_6_3_4": "lacework-global-288", - "GCP_CIS12_6_3_5": "lacework-global-289", - "GCP_CIS12_6_3_6": "lacework-global-290", - "GCP_CIS12_6_3_7": "lacework-global-291", - "GCP_CIS12_6_4": "lacework-global-271", - "GCP_CIS12_6_5": "lacework-global-272", - "GCP_CIS12_6_6": "lacework-global-311", - "GCP_CIS12_6_7": "lacework-global-273", - "GCP_CIS12_7_1": "lacework-global-292", - "GCP_CIS12_7_2": "lacework-global-313", - "GCP_CIS12_7_3": "lacework-global-314", - } - - // suppressionsMigrateGcpCmd represents the gcp sub-command inside the suppressions migrate command - suppressionsMigrateGcpCmd = &cobra.Command{ - Use: "migrate", - Aliases: []string{"mig"}, - Short: "Migrate legacy suppressions for Gcp to mapped policy exceptions", - RunE: suppressionsGcpMigrate, - } - - // suppressionsListGcpCmd represents the gcp sub-command inside the suppressions list command - suppressionsListGcpCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List legacy suppressions for GCP", - RunE: suppressionsGcpList, - } -) - -func suppressionsGcpList(_ *cobra.Command, _ []string) error { - var ( - suppressions map[string]api.SuppressionV2 - err error - ) - - suppressions, err = cli.LwApi.V2.Suppressions.Gcp.List() - if err != nil { - if strings.Contains(err.Error(), "No active GCP accounts") { - cli.OutputHuman("No active GCP accounts found. " + - "Unable to get legacy GCP suppressions\n") - return nil - } - return errors.Wrap(err, "Unable to get legacy GCP suppressions") - } - - if len(suppressions) == 0 { - cli.OutputHuman("No legacy GCP suppressions found.\n") - return nil - } - return cli.OutputJSON(suppressions) -} - -func suppressionsGcpMigrate(_ *cobra.Command, _ []string) error { - var ( - suppressionsMap map[string]api.SuppressionV2 - err error - - convertedPolicyExceptions []map[string]api.PolicyException - payloadsText []string - discardedSuppressions []map[string]api.SuppressionV2 - ) - suppressionsMap, err = cli.LwApi.V2.Suppressions.Gcp.List() - if err != nil { - if strings.Contains(err.Error(), "No active GCP accounts") { - cli.OutputHuman("No active GCP accounts found. " + - "Unable to get legacy gcp suppressions") - return nil - } - return errors.Wrap(err, "Unable to get legacy gcp suppressions") - } - - if len(suppressionsMap) == 0 { - cli.OutputHuman("No legacy GCP suppressions found.\n") - return nil - } - - answer := "" - manualMigration := "Output translated legacy suppressions as policy exception commands to be" + - " run manually (Recommended)" - autoMigration := "Auto migrate legacy suppressions.\nDISCLAIMER: " + - "By selecting this option, you accept liability for the migration and " + - "any compliance violations missed as a result of the added exceptions" - if err := SurveyQuestionInteractiveOnly(SurveyQuestionWithValidationArgs{ - Prompt: &survey.Select{ - Message: "Select your legacy suppression migration approach?", - Options: []string{ - manualMigration, - autoMigration, - }, - }, - Response: &answer, - }); err != nil { - return err - } - - // get a list of all policies and parse the valid exception constraints and create a map of - // {"": []} - policyExceptionsConstraintsMap := getPoliciesExceptionConstraintsMap() - - switch answer { - case manualMigration: - _, payloadsText, discardedSuppressions = convertGcpSuppressions( - suppressionsMap, - policyExceptionsConstraintsMap, - ) - printPayloadsText(payloadsText) - printDiscardedSuppressions(discardedSuppressions) - case autoMigration: - convertedPolicyExceptions, _, discardedSuppressions = convertGcpSuppressions( - suppressionsMap, - policyExceptionsConstraintsMap, - ) - printConvertedSuppressions(convertedPolicyExceptions) - confirm := false - err := survey.AskOne(&survey.Confirm{ - Message: "Confirm the above exceptions have been reviewed and you wish to continue" + - " with the auto migration.", - }, &confirm) - if err != nil { - return err - } - if confirm { - autoConvertSuppressions(convertedPolicyExceptions) - printDiscardedSuppressions(discardedSuppressions) - cli.OutputHuman(color.GreenString("To view the newly created Exceptions, " + - "try running `lacework policy-exceptions list ")) - } else { - cli.OutputHuman("Cancelled Legacy Suppression to Exception migration!\n") - } - } - - return nil -} - -func convertGcpSuppressions( - suppressionsMap map[string]api.SuppressionV2, - policyExceptionsConstraintsMap map[string][]string, -) ([]map[string]api.PolicyException, - []string, []map[string]api.SuppressionV2) { - var ( - convertedPolicyExceptions []map[string]api.PolicyException - payloadsText []string - discardedSuppressions []map[string]api.SuppressionV2 - ) - - for id, suppressionInfo := range suppressionsMap { - // verify there is a mapped policy for this recommendation - // if the recommendation is not a key in the map we can assume this is not mapped and - // continue - mappedPolicyId, ok := gcpEquivalencesMap[id] - if !ok { - // when we don't have a mapped policy, add the legacy suppression info - if suppressionInfo.SuppressionConditions != nil { - suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, - "Legacy suppression discarded as there is no equivalent policy") - discardedSuppressions = append( - discardedSuppressions, - map[string]api.SuppressionV2{id: suppressionInfo}, - ) - } - continue - } - - // get the supported policy exception fields for the mapped policy - // in order to ensure we have an up-to-date list of exception constraints we need to - // get the policy from the /api/v2/Policies/ api. - // We then parse this into a list of constraints - policyIdExceptionsTemplate := policyExceptionsConstraintsMap[mappedPolicyId] - if policyIdExceptionsTemplate == nil { - // Updating the suppression conditions comments to make it clear why these were - // discarded - if len(suppressionInfo.SuppressionConditions) >= 1 { - suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo, - fmt.Sprintf("Legacy suppression discarded as the new policy: %s does not"+ - " support exception conditions", mappedPolicyId)) - - // if the list of supported constraints is empty for a policy, - // we should let the customers know that we have discarded this suppression - discardedSuppressions = append( - discardedSuppressions, - map[string]api.SuppressionV2{id: suppressionInfo}, - ) - } - continue - } - if len(suppressionInfo.SuppressionConditions) >= 1 { - for _, suppression := range suppressionInfo.SuppressionConditions { - // used to store the converted legacy suppressions - var convertedConstraints []api.PolicyExceptionConstraint - - organizationIdsConstraint := convertSupCondition(suppression.OrganizationIds, - "organizations", - policyIdExceptionsTemplate) - if organizationIdsConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, organizationIdsConstraint) - } - - projectIdsConstraint := convertSupCondition(suppression.ProjectIds, - "projects", - policyIdExceptionsTemplate) - if projectIdsConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, projectIdsConstraint) - } - - resourceNamesConstraint := convertGcpResourceNameSupConditions(suppression.ResourceNames, - "resourceName", - policyIdExceptionsTemplate) - if resourceNamesConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, resourceNamesConstraint) - } - - resourceTagsConstraint := convertSupConditionTags(suppression.ResourceTags, - "resourceTags", - policyIdExceptionsTemplate) - if resourceTagsConstraint.FieldKey != "" { - convertedConstraints = append(convertedConstraints, resourceTagsConstraint) - } - - description := fmt.Sprintf( - "Migrated exception from legacy compliance policy %s. ", id, - ) - if suppression.Comment != "" { - description = description + fmt.Sprintf( - "Legacy Policy comment: %s", suppression.Comment, - ) - } - if len(convertedConstraints) >= 1 { - convertedPolicyExceptions = append( - convertedPolicyExceptions, - map[string]api.PolicyException{ - mappedPolicyId: { - Description: description, - Constraints: convertedConstraints, - }, - }, - ) - - exception := api.PolicyException{ - Description: description, - Constraints: convertedConstraints, - } - jsonException, err := json.Marshal(exception) - if err != nil { - cli.Log.Error(err) - } - - payloadsText = append( - payloadsText, - fmt.Sprintf( - "lacework api post '/Exceptions?policyId=%s' -d '%s'", - mappedPolicyId, - jsonException, - ), - ) - } - } - } - } - - return convertedPolicyExceptions, payloadsText, discardedSuppressions -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/table_render.go b/vendor/github.com/lacework/go-sdk/cli/cmd/table_render.go deleted file mode 100644 index 69916e97f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/table_render.go +++ /dev/null @@ -1,80 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strings" - - "github.com/olekukonko/tablewriter" -) - -// renderSimpleTable is used to render any simple table within the Lacework CLI, -// every command should leverage this function unless there are extra customizations -// required, if so, use instead renderCustomTable(). The benefit of this function -// is the ability to switch/update the look and feel of the human-readable format -// across the entire project -func renderSimpleTable(headers []string, data [][]string) string { - var ( - tblBldr = &strings.Builder{} - tbl = tablewriter.NewWriter(tblBldr) - ) - tbl.SetHeader(headers) - tbl.SetRowLine(false) - tbl.SetBorder(false) - tbl.SetAutoWrapText(true) - tbl.SetAlignment(tablewriter.ALIGN_LEFT) - tbl.SetColumnSeparator(" ") - tbl.AppendBulk(data) - tbl.Render() - return tblBldr.String() -} - -type tableOption interface { - apply(t *tablewriter.Table) -} - -type tableFunc func(t *tablewriter.Table) - -func (fn tableFunc) apply(t *tablewriter.Table) { - fn(t) -} - -// renderCustomTable should be used on special cases where we need to render a table -// with very specific settings, we normally should use renderSimpleTable() as much -// as possible to have consistency across the CLI -func renderCustomTable(headers []string, data [][]string, opts ...tableOption) string { - var ( - tblBldr = &strings.Builder{} - tbl = tablewriter.NewWriter(tblBldr) - ) - - for _, opt := range opts { - opt.apply(tbl) - } - - tbl.SetHeader(headers) - tbl.AppendBulk(data) - tbl.Render() - - return tblBldr.String() -} - -func renderOneLineCustomTable(title, content string, opts ...tableOption) string { - return renderCustomTable([]string{title}, [][]string{[]string{content}}, opts...) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/team_members.go b/vendor/github.com/lacework/go-sdk/cli/cmd/team_members.go deleted file mode 100644 index 9a5d79463..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/team_members.go +++ /dev/null @@ -1,405 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "regexp" - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/lacework/go-sdk/api" -) - -var ( - // team-members command is used to manage lacework team members - teamMembersCommand = &cobra.Command{ - Use: "team-member", - Aliases: []string{"team-members", "tm"}, - Short: "Manage team members", - Long: `Manage Team Members to grant or restrict access to multiple Lacework Accounts. - Team members can also be granted organization-level roles. -`, - } - - // list command is used to list all lacework team members - teamMembersListCommand = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all team members", - Long: "List all team members configured in your Lacework account.", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - cli.StartProgress(" Fetching team members...") - teamMembers, err := cli.LwApi.V2.TeamMembers.List() - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get team members") - } - if len(teamMembers.Data) == 0 { - msg := `There are no team members configured in your account. - -Get started by configuring your team members using the command: - - lacework team-member create - -If you prefer to configure team members via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Settings > Team Members. -` - cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) - return nil - } - if cli.JSONOutput() { - return cli.OutputJSON(teamMembers) - } - - var rows [][]string - for _, tm := range teamMembers.Data { - rows = append(rows, []string{tm.UserGuid, tm.UserName, enabled(tm.UserEnabled)}) - } - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "STATUS"}, rows)) - return nil - }, - } - // show command is used to retrieve a lacework team member by guid - teamMembersShowCommand = &cobra.Command{ - Use: "show ", - Short: "Show a team member by id", - Long: "Show a single team member by it's id.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var response api.TeamMemberResponse - cli.StartProgress(" Fetching team member...") - - err := cli.LwApi.V2.TeamMembers.Get(args[0], &response) - if err != nil { - cli.StopProgress() - return errors.Wrap(err, "unable to get team member") - } - cli.StopProgress() - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - - teamMember := response.Data - headers := [][]string{{teamMember.UserGuid, teamMember.UserName, enabled(teamMember.UserEnabled)}} - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "STATUS"}, headers)) - cli.OutputHuman("\n") - cli.OutputHuman(buildTeamMemberDetailsTable(teamMember)) - - return nil - }, - } - - // delete command is used to remove a lacework team member by id - teamMembersDeleteCommand = &cobra.Command{ - Use: "delete ", - Short: "Delete a team member", - Long: "Delete a single team member by it's ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress(" Deleting team member...") - err := cli.LwApi.V2.TeamMembers.Delete(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to delete team member") - } - cli.OutputHuman("The team member with GUID %s was deleted\n", args[0]) - return nil - }, - } - - // create command is used to create a new lacework team member - teamMembersCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a new team member", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, args []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - - userID, err := promptCreateTeamMember() - if err != nil { - return errors.Wrap(err, "unable to create team member") - } - - cli.OutputHuman("The team member was created with GUID %s\n", userID) - return nil - }, - } -) - -func init() { - // add the team-member command - rootCmd.AddCommand(teamMembersCommand) - - // add sub-commands to the team-member command - teamMembersCommand.AddCommand(teamMembersListCommand) - teamMembersCommand.AddCommand(teamMembersShowCommand) - teamMembersCommand.AddCommand(teamMembersCreateCommand) - teamMembersCommand.AddCommand(teamMembersDeleteCommand) -} - -func buildTeamMemberDetailsTable(tm api.TeamMember) string { - var ( - details [][]string - updatedTime string - ) - - if tm.Props.UpdatedTime != nil { - updatedTime = fmt.Sprintf("%v", tm.Props.UpdatedTime) - } - - details = append(details, []string{"FIRST NAME", tm.Props.FirstName}) - details = append(details, []string{"LAST NAME", tm.Props.LastName}) - details = append(details, []string{"COMPANY", tm.Props.Company}) - details = append(details, []string{"ACCOUNT ADMIN", strconv.FormatBool(tm.Props.AccountAdmin)}) - details = append(details, []string{"ORG ADMIN", strconv.FormatBool(tm.Props.OrgAdmin)}) - details = append(details, []string{"CREATED AT", tm.Props.CreatedTime}) - details = append(details, []string{"JIT CREATED", strconv.FormatBool(tm.Props.JitCreated)}) - details = append(details, []string{"UPDATED BY", tm.Props.UpdatedBy}) - details = append(details, []string{"UPDATED AT", updatedTime}) - - detailsTable := &strings.Builder{} - detailsTable.WriteString(renderOneLineCustomTable("TEAM MEMBER DETAILS", - renderCustomTable([]string{}, details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ), - ) - - detailsTable.WriteString("\n") - return detailsTable.String() -} - -func promptCreateTeamMember() (string, error) { - questions := []*survey.Question{ - { - Name: "email", - Prompt: &survey.Input{Message: "Email: "}, - Validate: validateEmail(), - }, - { - Name: "firstName", - Prompt: &survey.Input{Message: "First Name: "}, - Validate: survey.Required, - }, - { - Name: "lastName", - Prompt: &survey.Input{Message: "Last Name: "}, - Validate: survey.Required, - }, - { - Name: "company", - Prompt: &survey.Input{Message: "Company: "}, - Validate: survey.Required, - }, - { - Name: "orgTeamMemberPrompt", - Prompt: &survey.Confirm{Message: "Create at Organization Level?"}, - Validate: survey.Required, - }, - } - - answers := struct { - Email string `survey:"email"` - Description string `survey:"description"` - Company string `survey:"company"` - FirstName string `survey:"firstName"` - LastName string `survey:"lastName"` - OrgTeamMemberPrompt bool `survey:"orgTeamMemberPrompt"` - OrgAdminRole bool - UserRole bool - }{} - - err := survey.Ask(questions, &answers, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return "", err - } - - if answers.OrgTeamMemberPrompt { - orgConfig, err := askConfigureOrgTeamMemberPrompt() - if err != nil { - return "", err - } - - var ( - adminRoleAccounts []string - userRoleAccounts []string - ) - - switch orgConfig { - case "User Role": - answers.UserRole = true - case "Admin Role": - answers.OrgAdminRole = true - case "Manage Roles for Accounts": - adminRoleAccounts, userRoleAccounts, err = askManageOrgTeamMemberRolesPrompt() - if err != nil { - return "", err - } - } - - teamMember := api.NewTeamMemberOrg(answers.Email, api.TeamMemberProps{ - Company: answers.Company, - FirstName: answers.FirstName, - LastName: answers.LastName, - OrgAdmin: answers.OrgAdminRole, - OrgUser: answers.UserRole, - }) - - if len(adminRoleAccounts) > 0 { - teamMember.AdminRoleAccounts = sliceToUpper(adminRoleAccounts) - } - if len(userRoleAccounts) > 0 { - teamMember.UserRoleAccounts = sliceToUpper(userRoleAccounts) - } - - cli.StartProgress(" Creating team member...") - defer cli.StopProgress() - - res, err := cli.LwApi.V2.TeamMembers.CreateOrg(teamMember) - if err != nil { - return "", err - } - userID := res.Data.Accounts[0].UserGuid - return userID, nil - } - var accountAdmin bool - err = survey.AskOne(&survey.Confirm{Message: "Account Admin?"}, &accountAdmin) - if err != nil { - return "", err - } - - teamMember := api.NewTeamMember(answers.Email, api.TeamMemberProps{ - Company: answers.Company, - FirstName: answers.FirstName, - LastName: answers.LastName, - AccountAdmin: accountAdmin, - }) - - cli.StartProgress(" Creating team member...") - defer cli.StopProgress() - - res, err := cli.LwApi.V2.TeamMembers.Create(teamMember) - if err != nil { - return "", err - } - userID := res.Data.UserGuid - return userID, nil -} - -func askManageOrgTeamMemberRolesPrompt() ([]string, []string, error) { - res, err := cli.LwApi.V2.UserProfile.Get() - if err != nil { - return nil, nil, err - } - accountsList := res.Data[0].SubAccountNames() - - accounts := struct { - UserRoleAccounts []string `survey:"userRoleAccounts"` - AdminRoleAccounts []string `survey:"adminRoleAccounts"` - }{} - - questions := []*survey.Question{ - { - Name: "userRoleAccounts", - Prompt: &survey.MultiSelect{ - Message: "Select Accounts Team Member will have User role: ", - Options: accountsList}, - }, - { - Name: "adminRoleAccounts", - Prompt: &survey.MultiSelect{ - Message: "Select Accounts Team Member will have Admin role: ", - Options: accountsList}, - }, - } - - err = survey.Ask(questions, &accounts, - survey.WithIcons(promptIconsFunc), - ) - if err != nil { - return nil, nil, err - } - return accounts.AdminRoleAccounts, accounts.UserRoleAccounts, nil -} - -func askConfigureOrgTeamMemberPrompt() (string, error) { - var configureOrgMember string - - err := survey.AskOne(&survey.Select{ - Message: "Select a role for all accounts: ", - Options: []string{"User Role", "Admin Role", "Manage Roles for Accounts"}}, - &configureOrgMember, - survey.WithIcons(promptIconsFunc)) - - if err != nil { - return "", err - } - return configureOrgMember, nil -} - -func enabled(status int) string { - if status == 1 { - return "Enabled" - } - return "Disabled" -} - -func sliceToUpper(list []string) (upper []string) { - for _, item := range list { - upper = append(upper, strings.ToUpper(item)) - } - return -} - -func validateEmail() survey.Validator { - return func(val interface{}) error { - emailRegex, _ := regexp.Compile( - "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", //nolint - ) - if !emailRegex.MatchString(val.(string)) { - return fmt.Errorf("not a valid email %s", val.(string)) - } - return nil - } -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/telemetry.go b/vendor/github.com/lacework/go-sdk/cli/cmd/telemetry.go deleted file mode 100644 index b87aa10fe..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/telemetry.go +++ /dev/null @@ -1,96 +0,0 @@ -package cmd - -import ( - "context" - "encoding/json" - "errors" - "io" - "os" - - "github.com/spf13/cobra" - - cdk "github.com/lacework/go-sdk/cli/cdk/go/proto/v1" -) - -var ( - // telemetryUploadName is the name of the feature that telemetry is being uploaded for - telemetryUploadName string - - // telemetryUploadFile is a path to a JSON file containing key value pairs to upload - telemetryUploadFile string - - // telemetryCmd represents the telemetry command - telemetryCmd = &cobra.Command{ - Hidden: true, - Use: "telemetry", - Short: "Manage telemetry sent by the Lacework CLI", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - return runConfigureSetup() - }, - } - - telemetryUploadCmd = &cobra.Command{ - Use: "upload", - Short: "Manually send some telemetry back to Lacework", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - if telemetryUploadName == "" { - return errors.New("--name flag is required for this command") - } - if telemetryUploadFile == "" { - return errors.New("--data flag is required for this command") - } - return runUpload(telemetryUploadName, telemetryUploadFile) - }, - } -) - -func runUpload(name string, file string) error { - err := cli.NewClient() - if err != nil { - return err - } - request, err := prepareUpload(name, file) - if err != nil { - return err - } - _, err = cli.Honeyvent(context.Background(), request) - return err -} - -func prepareUpload(name string, file string) (request *cdk.HoneyventRequest, err error) { - jsonFile, err := os.Open(file) - if err != nil { - return nil, err - } - defer func(jsonFile *os.File) { - err = jsonFile.Close() - }(jsonFile) - telemetryBytes, err := io.ReadAll(jsonFile) - if err != nil { - return nil, err - } - var telemetryData map[string]string - err = json.Unmarshal(telemetryBytes, &telemetryData) - if err != nil { - return nil, err - } - request = &cdk.HoneyventRequest{ - Feature: name, - FeatureData: telemetryData, - } - return request, nil -} - -func init() { - rootCmd.AddCommand(telemetryCmd) - telemetryCmd.AddCommand(telemetryUploadCmd) - - telemetryUploadCmd.Flags().StringVar(&telemetryUploadName, - "name", "", "Name of the feature the telemetry upload is for", - ) - telemetryUploadCmd.Flags().StringVar(&telemetryUploadFile, - "data", "", "Path to JSON file containing key-value pairs to upload", - ) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/.version b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/.version deleted file mode 100644 index 6c6aa7cb0..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/.version +++ /dev/null @@ -1 +0,0 @@ -0.1.0 \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh deleted file mode 100644 index afe8ade3e..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -exit 1 diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh.sig b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh.sig deleted file mode 100644 index 4e566fd4f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-nonzero.sh.sig +++ /dev/null @@ -1 +0,0 @@ -dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpUy9XRjJUWjk4bUlNM1BYNmU5YjhMaXh6ajZiKytTK1MxcU1kTDE1NmN0bEZkbFI5ak1xZWx1N21tNWJlMGM0Z1pETmxSc1FiSStieTFWV1F1aGk5Zzg9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0KM1NIRVVBTG80RjZTeVo3SHhVbGNVZmtHQ0x6SzdRUmlGZWF1ZHJUK21uODQrUUJrMjhpcEpwNmhtckU3aEVpYUxobHlab2Z3WHplRTc2bWlObnBSQ0E9PQ== \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh deleted file mode 100644 index 67464fb16..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -echo "Hello World!" diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh.sig b/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh.sig deleted file mode 100644 index 27a910457..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/test_resources/content-library/content-library-noparse.sh.sig +++ /dev/null @@ -1 +0,0 @@ -dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpUmwrdm03ZE5wcDdKcnQrcUxBTnN1OW0rVGFIU2dWeUpPOUxkZW81WlB3L0dRSnUxZkpaQnVDcnNNVlc1S2J1QjZkRVB6UXZpOGN0MXpKZk1rZ1ZzZ3M9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0KY0p3aDdYRGN0R1I4ZHBjMU5FSm5QR1U1SVhOdk1OWGE2aGRNQTBCQlp2dm5zS1FIZVZWRWtNYzd6bzcyaUZzSEVLUnhlK0FtWGtyeU5nVmk3R2cwRFE9PQ== \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/version.go b/vendor/github.com/lacework/go-sdk/cli/cmd/version.go deleted file mode 100644 index b7ab861c3..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/version.go +++ /dev/null @@ -1,294 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "os" - "path" - "strings" - "time" - - "github.com/lacework/go-sdk/internal/cache" - "github.com/lacework/go-sdk/internal/file" - "github.com/lacework/go-sdk/lwcomponent" - "github.com/lacework/go-sdk/lwupdater" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // All the following "unknown" variables are being injected at - // build time via the cross-platform directive inside the Makefile - // - // Version is the semver coming from the VERSION file - Version = "unknown" - - // GitSHA is the git ref that the cli was built from - GitSHA = "unknown" - - // BuildTime is a human-readable time when the cli was built at - BuildTime = "unknown" - - // The name of the version cache file needed for daily version checks - VersionCacheFile = "version_cache" - - // versionCmd represents the version command - versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the Lacework CLI version", - Long: ` -Prints out the installed version of the Lacework CLI and checks for newer -versions available for update. - -Set the environment variable 'LW_UPDATES_DISABLE=1' to avoid checking for updates.`, - Args: cobra.NoArgs, - Run: func(_ *cobra.Command, _ []string) { - componentVersionsOutput := &strings.Builder{} - if cli.LwComponents != nil { - for _, component := range cli.LwComponents.Components { - if component.IsInstalled() { - v, err := component.CurrentVersion() - if err == nil { - componentVersionsOutput.WriteString( - fmt.Sprintf(" > %s v%s\n", component.Name, v.String()), - ) - continue - } - cli.Log.Errorw("unable to determine component version", - "error", err.Error(), "component", component.Name, - ) - } - } - } - - if cli.JSONOutput() { - vJSON := versionJSON{ - Version: fmt.Sprintf("v%s", Version), - GitSHA: GitSHA, - BuildTime: BuildTime, - } - - if componentVersionsOutput.String() != "" { - vJSON.CDK = cli.LwComponents - } - - errcheckEXIT(cli.OutputJSON(vJSON)) - return - } - - cli.OutputHuman("lacework v%s (sha:%s) (time:%s)\n", Version, GitSHA, BuildTime) - if componentVersionsOutput.String() != "" { - cli.OutputHuman("\nComponents:\n\n%s", componentVersionsOutput.String()) - } - - // check the latest version of the cli - if _, err := versionCheck(); err != nil { - cli.Log.Debugw("unable to perform lacework cli version check", - "error", err.Error()) - } - }, - } -) - -func init() { - rootCmd.AddCommand(versionCmd) -} - -// versionCheck checks if the user is running the latest version of the cli, -// if not, displays a friendly message about the new version available -func versionCheck() (*lwupdater.Version, error) { - cli.Log.Debugw("check version of the lacework-cli", "repository", "github.com/lacework/go-sdk") - sdk, err := lwupdater.Check("go-sdk", fmt.Sprintf("v%s", Version)) - if err != nil { - return nil, errors.Wrap(err, "unable to check updates") - } - - if sdk.Outdated { - cli.OutputHumanErr( - "\nA newer version of the Lacework CLI is available! The latest version is %s,\n"+ - "to update execute the following command:\n%s\n", - sdk.LatestVersion, cli.UpdateCommand()) - } - - return sdk, nil -} - -func isCheckEnabled() bool { - if disabled := os.Getenv(lwupdater.DisableEnv); disabled != "" { - return false - } - - if !cli.InteractiveMode() { - return false - } - return true -} - -// dailyComponentUpdateAvailable returns true if the cli should print that a new version of a component is available. -// It uses the file ~/.config/lacework/version_cache to track the last check time -func dailyComponentUpdateAvailable(componentName string) (bool, error) { - if cli.JSONOutput() || !isCheckEnabled() { - return false, nil - } - cacheDir, err := cache.CacheDir() - if err != nil { - return false, err - } - - cacheFile := path.Join(cacheDir, VersionCacheFile) - if !file.FileExists(cacheFile) { - // The file should have already been created by dailyVersionCheck - return false, err - } - - cli.Log.Debugw("verifying cached version", "cache_file", cacheFile) - versionCache, err := lwupdater.LoadCache(cacheFile) - if err != nil { - return false, err - } - - cli.Log.Debugw("component version cache", "content", versionCache.ComponentsLastCheck) - - // since our check is daily, substract one day from now and compare it - var ( - nowTime = time.Now() - checkTime = nowTime.AddDate(0, 0, -1) - ) - - if versionCache.CheckComponentBefore(componentName, checkTime) { - cli.Event.Feature = featDailyCompVerCheck - defer cli.SendHoneyvent() - - versionCache.ComponentsLastCheck[componentName] = nowTime - cli.Log.Debugw("storing new version cache", "content", versionCache) - err := versionCache.StoreCache(cacheFile) - - if err != nil { - cli.Event.FeatureData = map[string]interface{}{"silent_error": err.Error()} - return false, err - } - - cli.Event.DurationMs = time.Since(nowTime).Milliseconds() - cli.Event.FeatureData = versionCache - return true, nil - } else { - return false, nil - } -} - -func firstTimeVersionCheck(dir, file string) error { - var ( - err error - currentVersion *lwupdater.Version - ) - defer func() { - cli.Event.Feature = featDailyVerCheck - if err != nil { - cli.Event.FeatureData = map[string]interface{}{"silent_error": err.Error()} - } - cli.SendHoneyvent() - }() - - if err = os.MkdirAll(dir, 0755); err != nil { - return err - } - - currentVersion, err = versionCheck() - if err != nil { - return err - } - - cli.Log.Debugw("storing version cache", "content", currentVersion) - err = currentVersion.StoreCache(file) - return err -} - -// dailyVersionCheck will execute a version check on a daily basis, the function uses -// the file ~/.config/lacework/version_cache to track the last check time -func dailyVersionCheck() error { - if cli.JSONOutput() || !isCheckEnabled() { - return nil - } - - cacheDir, err := cache.CacheDir() - if err != nil { - return err - } - - cacheFile := path.Join(cacheDir, VersionCacheFile) - if !file.FileExists(cacheFile) { - // first time running the daily version check, create directory - return firstTimeVersionCheck(cacheDir, cacheFile) - } - - cli.Log.Debugw("verifying cached version", "cache_file", cacheFile) - versionCache, err := lwupdater.LoadCache(cacheFile) - if err != nil { - return err - } - - cli.Log.Debugw("version cache", "content", versionCache) - - // since our check is daily, substract one day from now and compare it - var ( - nowTime = time.Now() - checkTime = nowTime.AddDate(0, 0, -1) - ) - - if versionCache.LastCheckTime.Before(checkTime) { - cli.Event.Feature = featDailyVerCheck - defer cli.SendHoneyvent() - - versionCache.LastCheckTime = nowTime - cli.Log.Debugw("storing new version cache", "content", versionCache) - err := versionCache.StoreCache(cacheFile) - if err != nil { - cli.Event.FeatureData = map[string]interface{}{"silent_error": err.Error()} - return err - } - - lwv, err := versionCheck() - cli.Event.DurationMs = time.Since(nowTime).Milliseconds() - if err != nil { - cli.Event.FeatureData = map[string]interface{}{"silent_error": err.Error()} - return err - } - - cli.Event.FeatureData = lwv - return nil - } - - cli.Log.Debugw("threshold not yet met. skipping daily version check", - "threshold", "1d", - "current_version", versionCache.CurrentVersion, - "latest_version", versionCache.LatestVersion, - "version_outdated", versionCache.Outdated, - "last_check_time", versionCache.LastCheckTime, - "next_check_time", versionCache.LastCheckTime.AddDate(0, 0, 1)) - - return nil -} - -type versionJSON struct { - Version string `json:"version"` - GitSHA string `json:"git_sha"` - BuildTime string `json:"build_time"` - CDK *lwcomponent.State `json:"cdk,omitempty"` -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container.go deleted file mode 100644 index e0c855983..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container.go +++ /dev/null @@ -1,142 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/lacework/go-sdk/internal/array" - "github.com/pkg/errors" - flag "github.com/spf13/pflag" -) - -func init() { - // add sub-commands to the 'vulnerability container' command - vulContainerCmd.AddCommand(vulContainerScanCmd) - vulContainerCmd.AddCommand(vulContainerListAssessmentsCmd) - vulContainerCmd.AddCommand(vulContainerListRegistriesCmd) - vulContainerCmd.AddCommand(vulContainerShowAssessmentCmd) - - // add start flag to list-assessments command - vulContainerListAssessmentsCmd.Flags().StringVar(&vulCmdState.Start, - "start", "-24h", "start of the time range", - ) - // add end flag to list-assessments command - vulContainerListAssessmentsCmd.Flags().StringVar(&vulCmdState.End, - "end", "now", "end of the time range", - ) - // range time flag - vulContainerListAssessmentsCmd.Flags().StringVar(&vulCmdState.Range, - "range", "", "natural time range for query", - ) - // add repository flag to list-assessments command - vulContainerListAssessmentsCmd.Flags().StringSliceVarP(&vulCmdState.Repositories, - "repository", "r", []string{}, "filter assessments for specific repositories", - ) - - // add registry flag to list-assessments command - vulContainerListAssessmentsCmd.Flags().StringSliceVarP(&vulCmdState.Registries, - "registry", "", []string{}, "filter assessments for specific registries", - ) - - setPollFlag( - vulContainerScanCmd.Flags(), - ) - - setHtmlFlag( - vulContainerScanCmd.Flags(), - vulContainerShowAssessmentCmd.Flags(), - ) - - setDetailsFlag( - vulContainerScanCmd.Flags(), - vulContainerShowAssessmentCmd.Flags(), - ) - - setSeverityFlag( - vulContainerScanCmd.Flags(), - vulContainerShowAssessmentCmd.Flags(), - ) - - setFailOnSeverityFlag( - vulContainerScanCmd.Flags(), - vulContainerShowAssessmentCmd.Flags(), - ) - - setFailOnFixableFlag( - vulContainerScanCmd.Flags(), - vulContainerShowAssessmentCmd.Flags(), - ) - - setActiveFlag( - vulContainerListAssessmentsCmd.Flags(), - ) - - setFixableFlag( - vulContainerScanCmd.Flags(), - vulContainerShowAssessmentCmd.Flags(), - vulContainerListAssessmentsCmd.Flags(), - ) - - setPackagesFlag( - vulContainerScanCmd.Flags(), - vulContainerShowAssessmentCmd.Flags(), - ) - - setCsvFlag( - vulContainerShowAssessmentCmd.Flags(), - vulContainerListAssessmentsCmd.Flags(), - ) - - // DEPRECATED - vulContainerShowAssessmentCmd.Flags().BoolVar( - &vulCmdState.ImageID, "image_id", false, - "tread the provided sha256 hash as image id", - ) - errcheckWARN(vulContainerShowAssessmentCmd.Flags().MarkDeprecated( - "image_id", "by default we now look up both, image_id and image_digest at once.", - )) -} - -func setPollFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.BoolVar(&vulCmdState.Poll, "poll", false, "poll until the vulnerability scan completes") - } - } -} - -func getContainerRegistries() ([]string, error) { - var ( - registries = make([]string, 0) - regsIntegrations, err = cli.LwApi.V2.ContainerRegistries.List() - ) - if err != nil { - return registries, errors.Wrap(err, "unable to get container registry integrations") - } - - for _, i := range regsIntegrations.Data { - // avoid adding empty registries coming from the new local_scanner and avoid adding duplicate registries - if i.ContainerRegistryDomain() == "" || array.ContainsStr(registries, i.ContainerRegistryDomain()) { - continue - } - - registries = append(registries, i.ContainerRegistryDomain()) - } - - return registries, nil -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_assessments.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_assessments.go deleted file mode 100644 index be3a56ea6..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_assessments.go +++ /dev/null @@ -1,679 +0,0 @@ -package cmd - -import ( - "fmt" - "sort" - "strings" - "time" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/lwtime" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // vulContainerListAssessmentsCmd represents the list-assessments sub-command inside the container - // vulnerability command - vulContainerListAssessmentsCmd = &cobra.Command{ - Use: "list-assessments", - Aliases: []string{"list", "ls"}, - Short: "List container vulnerability assessments (default last 24 hours)", - Long: `List all container vulnerability assessments for the last 24 hours by default. - -To customize the time range use use '--start', '--end', or '--range'. - -The start and end times can be specified in one of the following formats: - - A. A relative time specifier - B. RFC3339 date and time - C. Epoch time in milliseconds - -Or use a natural time range like. - - lacework vuln container list --range yesterday - -The natural time range of 'yesterday' would represent a relative start time of '-1d@d' -and a relative end time of '@d'. - -You can also pass '--fixable' to filter on containers with vulnerabilities that have -fixes available, or '--active' to filter on container images actively running in your -environment.`, - Args: cobra.NoArgs, - PreRunE: func(cmd *cobra.Command, args []string) error { - if vulCmdState.Csv { - cli.EnableCSVOutput() - } - - return nil - }, - RunE: func(_ *cobra.Command, args []string) error { - var ( - // the cache key changes depending on some filters that - // will affect the data returned from our API's - cacheKey = generateContainerVulnListCacheKey() - assessments []vulnerabilityAssessmentSummary - filter api.SearchFilter - start time.Time - end time.Time - err error - ) - - expired := cli.ReadCachedAsset(cacheKey, &assessments) - if expired { - if vulCmdState.Range != "" { - cli.Log.Debugw("retrieving natural time range", "range", vulCmdState.Range) - start, end, err = lwtime.ParseNatural(vulCmdState.Range) - if err != nil { - return errors.Wrap(err, "unable to parse natural time range") - } - - } else { - cli.Log.Debugw("parsing start time", "start", vulCmdState.Start) - start, err = parseQueryTime(vulCmdState.Start) - if err != nil { - return errors.Wrap(err, "unable to parse start time") - } - - cli.Log.Debugw("parsing end time", "end", vulCmdState.End) - end, err = parseQueryTime(vulCmdState.End) - if err != nil { - return errors.Wrap(err, "unable to parse end time") - } - } - - // search for all active containers - cli.Log.Infow("using filter with", "start_time", start, "end_time", end) - filter.TimeFilter = &api.TimeFilter{ - StartTime: &start, - EndTime: &end, - } - - timeRangeMsg := fmt.Sprintf(" in time range (%s to %s)", - start.Format(time.RFC3339), end.Format(time.RFC3339)) - - cli.StartProgress(fmt.Sprintf("Searching for active containers%s...", timeRangeMsg)) - activeContainers, err := cli.LwApi.V2.Entities.ListAllContainersWithFilters( - api.SearchFilter{ - TimeFilter: filter.TimeFilter, - Returns: []string{"mid", "imageId", "startTime"}, - }) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to search for active containers") - } - - cli.Log.Infow("active containers found", - "active_count", activeContainers.Total(), - "entities_count", len(activeContainers.Data), - ) - - // get all container vulnerability assessments - cli.Log.Infow("requesting list of assessments", "start_time", start, "end_time", end) - cli.StartProgress(fmt.Sprintf("Fetching assessments%s...", timeRangeMsg)) - assessments, err = listVulnCtrAssessments(activeContainers, &filter) - cli.StopProgress() - if err != nil { - return err - } - - // write to cache if the request was successful - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), assessments) - } else { - cli.Log.Infow("assessments loaded from cache", "count", len(assessments)) - } - - // apply vuln ctr list-assessment filters (--active, --registries, --repositories, --fixable) - if vulnCtrListAssessmentFiltersEnabled() { - assessments = applyVulnCtrFilters(assessments) - } - - if cli.JSONOutput() { - return cli.OutputJSON(assessments) - } - - if len(assessments) == 0 { - cli.OutputHuman(buildContainerAssessmentsError()) - return nil - } - - // Build table output - assessmentOutput := assessmentSummaryToOutputFormat(assessments) - rows := vulAssessmentsToTable(assessmentOutput) - headers := []string{"Registry", "Repository", "Last Scan", - "Status", "Containers", "Vulnerabilities", "Image Digest"} - switch { - case len(rows) == 0: - cli.OutputHuman(buildContainerAssessmentsError()) - case cli.CSVOutput(): - if err := cli.OutputCSV(headers, rows); err != nil { - return errors.Wrap(err, "failed to create csv output") - } - default: - cli.OutputHuman(renderSimpleTable(headers, rows)) - if !vulCmdState.Active { - cli.OutputHuman( - "\nTry adding '--active' to only show assessments of active containers.\n", - ) - } else if !vulCmdState.Fixable { - cli.OutputHuman( - "\nTry adding '--fixable' to only show assessments with fixable vulnerabilities.\n", - ) - } - } - - return nil - }, - } -) - -func vulnCtrListAssessmentFiltersEnabled() bool { - return len(vulCmdState.Repositories) > 0 || - len(vulCmdState.Registries) > 0 || - vulCmdState.Fixable || - vulCmdState.Active -} - -func applyVulnCtrFilters(assessments []vulnerabilityAssessmentSummary) (filtered []vulnerabilityAssessmentSummary) { - for _, a := range assessments { - if len(vulCmdState.Repositories) > 0 { - if !array.ContainsStr(vulCmdState.Repositories, a.Repository) { - continue - } - } - if len(vulCmdState.Registries) > 0 { - if !array.ContainsStr(vulCmdState.Registries, a.Registry) { - continue - } - } - if vulCmdState.Active { - if a.ActiveContainers == 0 { - continue - } - } - if vulCmdState.Fixable { - if !a.HasFixableVulns() { - continue - } - } - - filtered = append(filtered, a) - } - return -} - -// The process to get the list of container assessments is -// -// 1. Check if the user provided a list of registries and repositories, -// if so, use those filters instead of fetching the entire data from -// all registries, repositories, local scanners, etc. (This is a memory -// utilization improvement) -// 2. If no filter by registries and/or repos, then fetch all data from all -// registries and all local scanners, we purposely split them in two search -// requests since there could be so much data that we get to the 500,000 rows -// if data and we could potentially miss some information -// 3. Either 1) or 2) will generate a tree of unique container vulnerability -// assessments (see the `treeCtrVuln` type), with this tree we will generate -// one last API request to unique evaluations per image (This is a memory -// utilization improvement) -// 4. Finally, if we get information from the queried assessments, we build a -// summary that will ultimately get stored in the cache for subsequent commands -func listVulnCtrAssessments( - activeContainers api.ContainersEntityResponse, filter *api.SearchFilter, -) (assessments []vulnerabilityAssessmentSummary, err error) { - - // Collect only the image ID and the start time to build a tree of - // images, the time they were evaluated, and the evaluation GUID. - // This will tell us all images and their latest evaluation - filter.Returns = []string{"imageId", "startTime", "evalGuid"} - filter.Filters = []api.Filter{} - treeOfContainerVuln := treeCtrVuln{} - - // if the user wants to only list assessments from a subset of registries, - // use that filter instead of fetching data from all registries - if len(vulCmdState.Registries) != 0 { - filter.Filters = append(filter.Filters, - api.Filter{ - Expression: "in", - Field: "evalCtx.image_info.registry", - Values: vulCmdState.Registries, - }) - } - - // if the user wants to only list assessments from a subset of repositories, - // use that filter instead of fetching data from all repositories - if len(vulCmdState.Repositories) != 0 { - filter.Filters = append(filter.Filters, - api.Filter{ - Expression: "in", - Field: "evalCtx.image_info.repo", - Values: vulCmdState.Repositories, - }) - } - - if len(filter.Filters) == 0 { - // if not, then we need to fetch information from 1) all - // container registries and 2) local scanners in two separate - // searches since platform scanners might have way too much - // data which may cause losing the local scanners data - // - // find all container registries - // cli.StartProgress("Fetching container registries...") - registries, err := getContainerRegistries() - // cli.StopProgress() - if err != nil { - return nil, err - } - cli.Log.Infow("container registries found", "count", len(registries)) - - if len(registries) != 0 { - // 1) search for all assessments from configured container registries - filter.Filters = []api.Filter{ - { - Expression: "in", - Field: "evalCtx.image_info.registry", - Values: registries, - }, - } - response, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(*filter) - if err != nil { - return assessments, errors.Wrap(err, "unable to search for container assessments") - } - - treeOfContainerVuln.ParseData(response.Data) - - // 2) search for assessments from local scanners, that is, non container registries - filter.Filters = []api.Filter{ - { - Expression: "not_in", - Field: "evalCtx.image_info.registry", - Values: registries, - }, - } - } else { - response, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(*filter) - if err != nil { - return assessments, errors.Wrap(err, "unable to search for container assessments") - } - - treeOfContainerVuln.ParseData(response.Data) - } - } else { - response, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(*filter) - if err != nil { - return assessments, errors.Wrap(err, "unable to search for container assessments") - } - - treeOfContainerVuln.ParseData(response.Data) - } - - evalGuids := treeOfContainerVuln.ListEvalGuid() - - cli.Log.Infow("evaluation guids", "count", len(evalGuids)) - if len(evalGuids) != 0 { - // Update the filter with the list of evaluation GUIDs and remove the "returns" - filter.Returns = nil - filter.Filters = []api.Filter{ - { - Expression: "in", - Field: "evalGuid", - Values: evalGuids, - }, - } - - response, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(*filter) - if err != nil { - return assessments, errors.Wrap(err, "unable to search for container assessments") - } - - assessments = buildVulnCtrAssessmentSummary(response.Data, activeContainers) - } - return -} - -// treeCtrVuln and ctrVuln are types that help us generate an tree of container -// vulnerability assessments that are unique per image ID, that is, there will -// never be duplicates of the same image with different evaluation guids (evalGuid) -type treeCtrVuln []ctrVuln -type ctrVuln struct { - EvalGUID string - ImageID string - StartTime time.Time -} - -func (v treeCtrVuln) Len() int { - return len(v) -} -func (v treeCtrVuln) Get(imageID string) (*ctrVuln, bool) { - for _, ctr := range v { - if ctr.ImageID == imageID { - return &ctr, true - } - } - return nil, false -} - -func (v treeCtrVuln) ListEvalGuid() (guids []string) { - for _, ctr := range v { - guids = append(guids, ctr.EvalGUID) - } - return -} -func (v treeCtrVuln) ListImageIDs() (ids []string) { - for _, ctr := range v { - ids = append(ids, ctr.ImageID) - } - return -} - -func (v *treeCtrVuln) ParseData(data []api.VulnerabilityContainer) { - for _, ctr := range data { - latestContainer, exist := v.Get(ctr.ImageID) - if exist { - if latestContainer.EvalGUID == ctr.EvalGUID { - continue - } - if ctr.StartTime.After(latestContainer.StartTime) { - v.Replace(*latestContainer, ctrVuln{ - EvalGUID: ctr.EvalGUID, - ImageID: ctr.ImageID, - StartTime: ctr.StartTime, - }) - } - } else { - // @afiune this is NOT thread safe!! But it is also not used in parallel executions - *v = append(*v, ctrVuln{ctr.EvalGUID, ctr.ImageID, ctr.StartTime}) - } - } -} - -// Replace updates an existing ctrVuln in the treeCtrVuln slice with a new ctrVuln -func (v treeCtrVuln) Replace(old ctrVuln, new ctrVuln) { - for i, ctrVuln := range v { - if ctrVuln.EvalGUID == old.EvalGUID { - v[i] = new - break - } - } -} - -type vulnerabilityAssessmentSummary struct { - ImageID string `json:"image_id"` - Repository string `json:"repository"` - Registry string `json:"registry"` - Digest string `json:"digest"` - ScanTime time.Time `json:"scan_time"` - Cves []vulnerabilityCtrSummary `json:"cves"` - ScanStatus string `json:"scan_status"` - ActiveContainers int `json:"active_containers"` - FixableCount int `json:"fixable_count"` - vulnerabilities []string - StatusList []string `json:"-"` -} - -type vulnerabilityCtrSummary struct { - Id string `json:"vuln_id"` - Pkg string `json:"package_name"` - Fixable int `json:"fixable"` - Severity string `json:"severity"` -} - -func (v vulnerabilityAssessmentSummary) Status() string { - if array.ContainsStr(v.StatusList, "VULNERABLE") { - return "VULNERABLE" - } - return "GOOD" -} - -func (v vulnerabilityAssessmentSummary) HasFixableVulns() bool { - for _, c := range v.Cves { - if c.Fixable != 0 { - return true - } - } - return false -} - -func buildVulnCtrAssessmentSummary( - assessments []api.VulnerabilityContainer, activeContainers api.ContainersEntityResponse, -) (uniqueAssessments []vulnerabilityAssessmentSummary) { - - imageMap := map[string]vulnerabilityAssessmentSummary{} - - // build a map for our assessments per image - for _, a := range assessments { - i := fmt.Sprintf("%s-%s-%s", a.EvalCtx.ImageInfo.Registry, a.EvalCtx.ImageInfo.Repo, a.EvalCtx.ImageInfo.ID) - if _, ok := imageMap[i]; ok { - // if the image id assessment has already been added, then append the vulnerabilities - summary := imageMap[i] - summary.StatusList = append(imageMap[i].StatusList, a.Status) - - // check duplicate cves - vulnKey := fmt.Sprintf("%s-%s", a.VulnID, a.FeatureKey.Name) - if !array.ContainsStr(imageMap[i].vulnerabilities, vulnKey) && a.VulnID != "" { - summary.vulnerabilities = append(imageMap[i].vulnerabilities, vulnKey) - summary.Cves = append(imageMap[i].Cves, - vulnerabilityCtrSummary{a.VulnID, a.FeatureKey.Name, a.FixInfo.FixAvailable, a.Severity}, - ) - if a.FixInfo.FixAvailable != 0 { - summary.FixableCount++ - } - } - - imageMap[i] = summary - continue - } - - fixableCount := 0 - if a.FixInfo.FixAvailable != 0 { - fixableCount = 1 - } - - // search for active containers - imageMap[i] = vulnerabilityAssessmentSummary{ - ImageID: a.ImageID, - Repository: a.EvalCtx.ImageInfo.Repo, - Registry: a.EvalCtx.ImageInfo.Registry, - Digest: a.EvalCtx.ImageInfo.Digest, - ScanTime: a.StartTime, - Cves: []vulnerabilityCtrSummary{{a.VulnID, a.FeatureKey.Name, a.FixInfo.FixAvailable, a.Severity}}, - ScanStatus: a.EvalCtx.ImageInfo.Status, - ActiveContainers: activeContainers.Count(a.ImageID), - vulnerabilities: []string{fmt.Sprintf("%s-%s", a.VulnID, a.FeatureKey.Name)}, - StatusList: []string{a.Status}, - FixableCount: fixableCount, - } - } - - // Loop over image map and build result - for _, v := range imageMap { - uniqueAssessments = append(uniqueAssessments, v) - } - return -} - -func buildContainerAssessmentsError() string { - msg := "There are no" - if vulCmdState.Active { - msg = fmt.Sprintf("%s active containers", msg) - } else { - msg = fmt.Sprintf("%s assessments", msg) - } - - if len(vulCmdState.Repositories) != 0 { - msg = fmt.Sprintf("%s for the specified", msg) - if len(vulCmdState.Repositories) == 1 { - msg = fmt.Sprintf("%s repository", msg) - } else { - msg = fmt.Sprintf("%s repositories", msg) - } - } - - if len(vulCmdState.Registries) != 0 { - msg = fmt.Sprintf("%s for the specified", msg) - if len(vulCmdState.Registries) == 1 { - msg = fmt.Sprintf("%s registry", msg) - } else { - msg = fmt.Sprintf("%s registries", msg) - } - } - - if vulCmdState.Fixable { - msg = fmt.Sprintf("%s with fixable vulnerabilities", msg) - } - - return fmt.Sprintf("%s in your environment.\n", msg) -} - -// assessmentSummaryToOutputFormat builds assessmentOutput from the raw response -func assessmentSummaryToOutputFormat(assessments []vulnerabilityAssessmentSummary) []assessmentOutput { - var out []assessmentOutput - - // sort by active containers - sort.Slice(assessments, func(i, j int) bool { - return assessments[i].ActiveContainers > assessments[j].ActiveContainers - }) - - for _, ctr := range assessments { - severities := []string{} - fixableCount := 0 - for _, cve := range ctr.Cves { - severities = append(severities, cve.Severity) - if cve.Fixable != 0 { - fixableCount++ - } - } - - summaryString := severityCtrSummary(severities, ctr.FixableCount) - - out = append(out, assessmentOutput{ - imageRegistry: ctr.Registry, - imageRepo: ctr.Repository, - startTime: ctr.ScanTime.UTC().Format(time.RFC3339), - imageScanStatus: ctr.ScanStatus, - ndvContainers: fmt.Sprintf("%d", ctr.ActiveContainers), - assessmentSummary: summaryString, - imageDigest: ctr.Digest, - }) - } - return out -} - -func severityCtrSummary(severities []string, fixable int) string { - summary := &strings.Builder{} - sevSummaries := make(map[string]int) - for _, s := range severities { - switch s { - case "Critical": - if v, ok := sevSummaries["Critical"]; ok { - sevSummaries["Critical"] = v + 1 - continue - } - sevSummaries["Critical"] = 1 - case "High": - if v, ok := sevSummaries["High"]; ok { - sevSummaries["High"] = v + 1 - continue - } - sevSummaries["High"] = 1 - case "Medium": - if v, ok := sevSummaries["Medium"]; ok { - sevSummaries["Medium"] = v + 1 - continue - } - sevSummaries["Medium"] = 1 - case "Low": - if v, ok := sevSummaries["Low"]; ok { - sevSummaries["Low"] = v + 1 - continue - } - sevSummaries["Low"] = 1 - case "Info": - if v, ok := sevSummaries["Info"]; ok { - sevSummaries["Info"] = v + 1 - continue - } - sevSummaries["Info"] = 1 - } - } - - var keys []string - for k := range sevSummaries { - keys = append(keys, k) - } - - sort.Slice(keys, func(i, j int) bool { - return api.SeverityOrder(keys[i]) < api.SeverityOrder(keys[j]) - }) - - // only output the highest severity - if len(keys) != 0 { - v := sevSummaries[keys[0]] - summary.WriteString(fmt.Sprintf("%d %s", v, keys[0])) - } - - if fixable != 0 { - summary.WriteString(fmt.Sprintf(" %d Fixable", fixable)) - } - - if len(keys) == 0 && fixable == 0 { - summary.WriteString(fmt.Sprintf("None! Time for %s", randomEmoji())) - } - return summary.String() -} - -// vulAssessmentsToTable returns assessments in format compatible with table output -func vulAssessmentsToTable(assessments []assessmentOutput) [][]string { - var out [][]string - for _, assessment := range assessments { - out = append(out, []string{ - assessment.imageRegistry, - assessment.imageRepo, - assessment.startTime, - assessment.imageScanStatus, - assessment.ndvContainers, - assessment.assessmentSummary, - assessment.imageDigest, - }) - } - return out -} - -type assessmentOutput struct { - imageRegistry string - imageRepo string - startTime string - imageScanStatus string - ndvContainers string - assessmentSummary string - imageDigest string -} - -// generateContainerVulnListCacheKey returns the cache key where the CLI will store vulnerability -// assessments for the next few minutes so that consecutive commands run faster and we avoid sending -// duplicate requests to our APIs. -// -// The criteria to generate this cache key is to use the prefix 'vulnerability/container/v2_{HASH}' -// with a hash value at the end of the prefix. The hash is generated based of filters that, if changed -// by the user, will change the data returned from our APIs -func generateContainerVulnListCacheKey() string { - var cacheFiltersHash = cacheFiltersToBuildVulnContainerHash{ - Start: vulCmdState.Start, // mapped to '--start' - End: vulCmdState.End, // mapped to '--end' - Range: vulCmdState.Range, // mapped to '--range' - Repositories: vulCmdState.Repositories, // mapped to '--repository' (multi-flag) - Registries: vulCmdState.Registries, // mapped to '--registry' (multi-flag) - } - return fmt.Sprintf("vulnerability/container/v2_%d", hash(cacheFiltersHash)) -} - -// struct that defines the filters we care about, that is, filters that -// when changed, will generate a different hash -type cacheFiltersToBuildVulnContainerHash struct { - Start string - End string - Range string - Repositories []string - Registries []string -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_registries.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_registries.go deleted file mode 100644 index bc8af7db6..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_list_registries.go +++ /dev/null @@ -1,55 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -var ( - // vulContainerListRegistriesCmd represents the list-registries sub-command inside the container - // vulnerability command - vulContainerListRegistriesCmd = &cobra.Command{ - Use: "list-registries", - Aliases: []string{"list-reg", "registries"}, - Short: "List all container registries configured", - Long: `List all container registries configured in your account.`, - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, args []string) error { - cli.StartProgress("Fetching container registries...") - registries, err := getContainerRegistries() - cli.StopProgress() - if err != nil { - return err - } - if len(registries) == 0 { - msg := `There are no container registries configured in your account. - -Get started by integrating your container registry using the command: - - lacework container-registry create - -If you prefer to configure the integration via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Settings > Integrations > Container Registry. -` - cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) - return nil - } - - if cli.JSONOutput() { - return cli.OutputJSON(registries) - } - - var rows [][]string - for _, acc := range registries { - rows = append(rows, []string{acc}) - } - - cli.OutputHuman(renderSimpleTable([]string{"Container Registries"}, rows)) - return nil - }, - } -) diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_scan.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_scan.go deleted file mode 100644 index d3298b3bc..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_scan.go +++ /dev/null @@ -1,332 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strings" - "time" - - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // vulContainerScanCmd represents the scan sub-command inside the container vulnerability command - vulContainerScanCmd = &cobra.Command{ - Use: "scan ", - Short: "Request an on-demand container vulnerability assessment", - Long: `Request on-demand container vulnerability assessments and view the generated results. - -To list all container registries configured in your account: - - lacework vulnerability container list-registries - -**NOTE:** Scans can take up to 15 minutes to return results. - -Arguments: - container registry where the container image has been published - repository name that contains the container image - either a tag or an image digest to scan (digest format: sha256:1ee...1d3b) - `, - Args: cobra.ExactArgs(3), - RunE: func(c *cobra.Command, args []string) error { - if err := validateSeverityFlags(); err != nil { - return err - } - - err := requestOnDemandContainerVulnerabilityScan(args) - var e *vulnerabilityPolicyError - if errors.As(err, &e) { - c.SilenceUsage = true - } - - return err - }, - } -) - -func requestOnDemandContainerVulnerabilityScan(args []string) error { - cli.Log.Debugw("requesting vulnerability scan", - "registry", args[0], - "repository", args[1], - "tag_or_digest", args[2], - ) - scan, err := cli.LwApi.V2.Vulnerabilities.Containers.Scan(args[0], args[1], args[2]) - if err != nil { - return userFriendlyErrorForOnDemandCtrVulnScan(err, args[0], args[1], args[2]) - } - - cli.Log.Debugw("vulnerability scan", "details", scan) - if scan.Data.RequestID == "" { - return errors.Errorf( - "there is a problem with the vulnerability scan: %s", - scan.Message, - ) - } - - cli.OutputHuman( - "A new vulnerability scan has been requested. (request_id: %s)\n\n", - scan.Data.RequestID, - ) - - if vulCmdState.Poll { - cli.Log.Infow("tracking scan progress", - "param", "--poll", - "request_id", scan.Data.RequestID, - ) - return pollScanStatus(scan.Data.RequestID, args) - } - - if cli.JSONOutput() { - return cli.OutputJSON(scan.Data) - } - return nil -} - -// Creates a user-friendly error message -func userFriendlyErrorForOnDemandCtrVulnScan(err error, registry, repo, tag string) error { - if strings.Contains(err.Error(), - "Could not find integration matching the registry provided", - ) || strings.Contains(err.Error(), - "Could not find vulnerability integrations", - ) { - - registries, errReg := getContainerRegistries() - if errReg != nil { - cli.Log.Debugw("error trying to retrieve configured registries", "error", errReg) - return errors.Errorf("container registry '%s' not found", registry) - } - - if len(registries) == 0 { - msg := `there are no container registries configured in your account. - -Get started by integrating your container registry using the command: - - lacework container-registry create - -If you prefer to configure the integration via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Settings > Integrations > Container Registry. -` - return errors.New(fmt.Sprintf(msg, cli.Account)) - } - - msg := `container registry '%s' not found - -Your account has the following container registries configured: - - > %s - -To integrate a new container registry use the command: - - lacework container-registry create -` - return errors.New(fmt.Sprintf(msg, registry, strings.Join(registries, "\n > "))) - } - - if strings.Contains( - err.Error(), - "Could not successfully send scan request to available integrations for given repo and label", - ) { - - msg := `container image '%s@%s' not found in registry '%s'. - -This error is likely due to a problem with the container registry integration -configured in your account. Verify that the integration was configured with -Lacework using the correct permissions, and that the repository belongs -to the provided registry. - -To view all container registries configured in your account use the command: - - lacework vulnerability container list-registries -` - return errors.Errorf(msg, repo, tag, registry) - } - - return errors.Wrap(err, "unable to request on-demand vulnerability scan") -} - -func pollScanStatus(requestID string, args []string) error { - cli.StartProgress(" Scan running...") - time.Sleep(time.Second * 40) - var ( - retries = 0 - start = time.Now().UTC() - durationTime = start - expPollTime = time.Second - params = map[string]interface{}{"request_id": requestID} - ) - - for { - retries++ - params["retries"] = retries - - cli.Event.DurationMs = time.Since(durationTime).Milliseconds() - durationTime = time.Now() - - cli.Event.Feature = featPollCtrScan - cli.Event.FeatureData = params - - evalGUID, err := checkScanStatus(requestID) - if err != nil { - cli.Event.Error = err.Error() - cli.SendHoneyvent() - return err - } - - if evalGUID == "" { - cli.Log.Debugw("waiting for a retry", "request_id", requestID, "sleep", expPollTime) - cli.SendHoneyvent() - time.Sleep(expPollTime) - expPollTime = time.Duration(retries*retries) * time.Second - continue - } - - cli.Event.DurationMs = time.Since(durationTime).Milliseconds() - params["total_duration_ms"] = time.Since(start).Milliseconds() - params["eval_guid"] = evalGUID - cli.Event.FeatureData = params - cli.SendHoneyvent() - - // reset event fields - cli.Event.DurationMs = 0 - cli.Event.FeatureData = nil - - cli.StopProgress() - - // scan is completed, fetch results using the Search() API but avoid - // using a time range of 7 days and instead just pass the last 24 hours - now := time.Now().UTC() - before := now.AddDate(0, 0, -1) // 1 day from now - filter := api.SearchFilter{ - TimeFilter: &api.TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - Filters: []api.Filter{ - { - Expression: "eq", - Field: "evalCtx.image_info.registry", - Value: args[0], - }, - { - Expression: "eq", - Field: "evalGuid", - Value: evalGUID, - }, - { - Expression: "eq", - Field: "evalCtx.image_info.repo", - Value: args[1], - }, - { - Expression: "eq", - Field: "evalCtx.is_reeval", - Value: "false", - }, - { - Expression: "eq", - Field: "evalCtx.scan_request_props.reqId", - Value: requestID, - }, - { - Expression: "eq", - Field: getTagOrDigestField(args[2]), - Value: args[2], - }, - }, - } - - cli.Log.Debugw("retrieve assessment", "filters", filter.Filters) - - cli.StartProgress("Fetching assessment results...") - assessment, err := cli.LwApi.V2.Vulnerabilities.Containers.Search(filter) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to fetch assessment results") - } - - if len(assessment.Data) == 0 { - return errors.Errorf( - "unable to fetch assessment results from evaluation with id '%s'", evalGUID, - ) - } - - cli.Log.Infow("raw assessment", "data_points", len(assessment.Data)) - filterContainerAssessmentByVulnerable(&assessment) - cli.Log.Infow("filtered assessment (status = vulnerable)", "data_points", len(assessment.Data)) - - if err := outputContainerVulnerabilityAssessment(assessment); err != nil { - return err - } - - if vulFailureFlagsEnabled() { - cli.Log.Infow("failure flags enabled", - "fail_on_severity", vulCmdState.FailOnSeverity, - "fail_on_fixable", vulCmdState.FailOnFixable, - ) - vulnPolicy := NewVulnerabilityPolicyErrorV2( - assessment, - vulCmdState.FailOnSeverity, - vulCmdState.FailOnFixable, - ) - if vulnPolicy.NonCompliant() { - return vulnPolicy - } - } - - return nil - } -} - -func getTagOrDigestField(arg string) string { - // Check if we need to search for a digest or a tag id - if strings.HasPrefix(arg, "sha256:") { - return "evalCtx.image_info.digest" - } - return "evalCtx.image_info.tags[0]" -} - -// checkScanStatus returns the evaluation GUID once the scan is completed, -// if it is not completed, it returns an empty string -func checkScanStatus(requestID string) (string, error) { - cli.Log.Infow("verifying status of vulnerability scan", "request_id", requestID) - scan, err := cli.LwApi.V2.Vulnerabilities.Containers.ScanStatus(requestID) - if err != nil { - return "", errors.Wrap(err, "unable to verify status of the vulnerability scan") - } - - cli.Log.Debugw("vulnerability scan", "details", scan) - status := scan.CheckStatus() - switch status { - case "completed": - cli.Log.Infow("vulnerability scan completed", - "request_id", requestID, "eval_guid", scan.Data.EvalGuid) - return scan.Data.EvalGuid, nil - case "scanning": - return "", nil - default: - return "", errors.Errorf( - "unable to get status: '%s' from vulnerability scan. Use '--debug' to troubleshoot.", status) - } -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_show_assessments.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_show_assessments.go deleted file mode 100644 index 8d09b74e5..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_container_show_assessments.go +++ /dev/null @@ -1,783 +0,0 @@ -package cmd - -import ( - "fmt" - "regexp" - "sort" - "strconv" - "strings" - "time" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/lwseverity" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -const regexAllTabs = "(\\t){1,}" - -var ( - // vulContainerShowAssessmentCmd represents the show-assessment sub-command inside the container - // vulnerability command - vulContainerShowAssessmentCmd = &cobra.Command{ - Use: "show-assessment [cve_id]", - Aliases: []string{"show"}, - Short: "Show results of a container vulnerability assessment", - Long: `Show the vulnerability assessment results of the specified container. - -Arguments: - a sha256 hash of a container image (format: sha256:1ee...1d3b) - -Note that the provided SHA is treated first as the image digest, but if no results -are found, this commands tries to use the SHA as the image id. - -To request an on-demand vulnerability scan: - - lacework vulnerability container scan - -To see details for a single cve result in an assessment: - - lacework vulnerability show-assessment [cve_id] -`, - Args: cobra.RangeArgs(1, 2), - PreRunE: func(cmd *cobra.Command, args []string) error { - if vulCmdState.Csv { - cli.EnableCSVOutput() - - // If --details or --packages is not passed, csv outputs nothing; defaulting to --details - if !vulCmdState.Details && !vulCmdState.Packages { - vulCmdState.Details = true - } - } - - if len(args) > 1 { - vulCmdState.Cve = args[1] - } - - return nil - }, - RunE: func(c *cobra.Command, args []string) error { - if err := validateSeverityFlags(); err != nil { - return err - } - err := showContainerAssessmentsWithSha256(args[0]) - var e *vulnerabilityPolicyError - if errors.As(err, &e) { - c.SilenceUsage = true - } - - return err - }, - } -) - -func searchLastestEvaluationGuid(sha string) (string, error) { - var ( - now = time.Now().UTC() - before = now.AddDate(0, 0, -7) // 7 days from ago - filter = api.SearchFilter{ - TimeFilter: &api.TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - Returns: []string{"evalGuid", "startTime"}, - } - ) - - // By default, we display the image digest in the command 'list-assessments', - // so we start by fetching the image using the digest - cli.Log.Infow("retrieve image assessment", "image_digest", sha) - assessment, err := cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(api.SearchFilter{ - Returns: filter.Returns, - TimeFilter: filter.TimeFilter, - Filters: []api.Filter{{ - Expression: "eq", - Field: "evalCtx.image_info.digest", - Value: sha, - }}, - }) - if err != nil { - return "", err - } - - if len(assessment.Data) == 0 { - // provided sha was not an image digest, try using it as an image id instead - cli.Log.Infow("retrieve image assessment", "image_id", sha) - assessment, err = cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(api.SearchFilter{ - Returns: filter.Returns, - TimeFilter: filter.TimeFilter, - Filters: []api.Filter{{ - Expression: "eq", - Field: "evalCtx.image_info.id", - Value: sha, - }}, - }) - if err != nil { - return "", err - } - - if len(assessment.Data) == 0 { - return "", errors.New("no data found") - } - } - - return getUniqueEvalGUID(assessment), nil -} - -func getUniqueEvalGUID(assessment api.VulnerabilitiesContainersResponse) string { - var ( - guid string - startTime time.Time - ) - for _, ctr := range assessment.Data { - if ctr.EvalGUID != guid { - if ctr.StartTime.After(startTime) { - startTime = ctr.StartTime - guid = ctr.EvalGUID - } - } - } - return guid -} - -func showContainerAssessmentsWithSha256(sha string) error { - var ( - cacheKey = fmt.Sprintf("vulnerability/container/%s", sha) - assessment api.VulnerabilitiesContainersResponse - ) - expired := cli.ReadCachedAsset(cacheKey, &assessment) - if expired { - // search for the latest evaluation guid - cli.StartProgress("Searching for latest container evaluation...") - evalGUID, err := searchLastestEvaluationGuid(sha) - cli.StopProgress() - if err != nil { - return errors.Wrapf(err, "unable to find assessment information of image %s", sha) - } - - cli.Log.Infow("latest assessment found", "eval_guid", evalGUID) - - var ( - now = time.Now().UTC() - before = now.AddDate(0, 0, -7) // 7 days from ago - ) - - cli.StartProgress( - fmt.Sprintf("Fetching assessment from container evaluation '%s'...", evalGUID), - ) - assessment, err = cli.LwApi.V2.Vulnerabilities.Containers.SearchAllPages(api.SearchFilter{ - TimeFilter: &api.TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - Filters: []api.Filter{{ - Expression: "eq", - Field: "evalGuid", - Value: evalGUID, - }}, - }) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to fetch assessment data") - } - - // write to cache if the request was successful - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Minute*30), assessment) - } else { - cli.Log.Infow("assessment loaded from cache", "data_points", len(assessment.Data)) - } - - filterContainerAssessmentByVulnerable(&assessment) - cli.Log.Infow("filtered assessment (status = vulnerable)", "data_points", len(assessment.Data)) - - if err := outputContainerVulnerabilityAssessment(assessment); err != nil { - return err - } - - if vulFailureFlagsEnabled() { - cli.Log.Infow("failure flags enabled", - "fail_on_severity", vulCmdState.FailOnSeverity, - "fail_on_fixable", vulCmdState.FailOnFixable, - ) - vulnPolicy := NewVulnerabilityPolicyErrorV2( - assessment, - vulCmdState.FailOnSeverity, - vulCmdState.FailOnFixable, - ) - if vulnPolicy.NonCompliant() { - return vulnPolicy - } - } - - return nil -} - -func buildVulnContainerAssessmentCve(assessment api.VulnerabilitiesContainersResponse) error { - assessment.FilterSingleVulnIDData(vulCmdState.Cve) - - if cli.JSONOutput() { - return cli.OutputJSON(assessment.Data) - } - - if len(assessment.Data) == 0 { - cli.OutputHuman("Unable to find results for '%s'\n", vulCmdState.Cve) - return nil - } - - var details vulnerabilityDetailsReport - details.VulnerabilityDetails = filterVulnerabilityContainer(assessment.Data) - - cli.OutputHuman(buildVulnerabilitySingleCveReportTable(details)) - return nil -} - -func filterContainerAssessmentByVulnerable(assessment *api.VulnerabilitiesContainersResponse) { - var vulnerabilities []api.VulnerabilityContainer - for _, a := range assessment.Data { - if a.Status == "VULNERABLE" { - vulnerabilities = append(vulnerabilities, a) - } - } - assessment.Data = vulnerabilities -} - -func outputContainerVulnerabilityAssessment(assessment api.VulnerabilitiesContainersResponse) error { - if len(assessment.Data) == 0 { - if cli.JSONOutput() { - // if no assessments are found return empty array - return cli.OutputJSON([]any{}) - } - cli.OutputHuman( - "Great news! This container image has no vulnerabilities... (time for %s)\n", - randomEmoji(), - ) - return nil - } - - if vulCmdState.Cve != "" { - if err := buildVulnContainerAssessmentCve(assessment); err != nil { - return err - } - } else { - if err := buildVulnContainerAssessmentReports(assessment); err != nil { - return err - } - } - - return nil -} - -// Build the cli output for vuln ctr 'show-assessment' command -func buildVulnContainerAssessmentReports(assessment api.VulnerabilitiesContainersResponse) error { - var ( - filteredAssessment = assessment - summaryReport = buildVulnerabilitySummaryReportTable(assessment) - details vulnerabilityDetailsReport - ) - - details.VulnerabilityDetails = filterVulnerabilityContainer(assessment.Data) - filteredAssessment.Data = details.VulnerabilityDetails.Filtered - details.Packages = filterVulnContainerImagePackages(details.VulnerabilityDetails.Filtered) - details.Packages.totalUnfiltered = countVulnContainerImagePackages(assessment.Data) - - switch { - case cli.JSONOutput(): - return cli.OutputJSON(filteredAssessment.Data) - case cli.CSVOutput(): - if !(vulCmdState.Details || vulCmdState.Packages || vulFiltersEnabled()) { - return nil - } - - if vulCmdState.Packages { - return cli.OutputCSV([]string{ - "CVE Count", "Severity", "Package", "Current Version", "Fix Version"}, - vulContainerImagePackagesToTable(details.Packages)) - } - - return cli.OutputCSV([]string{ - "CVE ID", "Severity", "CVSSv2", "CVSSv3", "Package", "Current Version", - "Fix Version", "Version Format", "Feed", "Src", "Start Time", "Status", - "Namespace", "Image Digest", "Image ID", "Image Repo", "Image Registry", - "Image Size", "Introduced in Layer"}, - vulContainerImageLayersToCSV(filteredAssessment.Data)) - default: - if len(filteredAssessment.Data) == 0 { - if vulCmdState.Severity != "" { - cli.OutputHuman("There are no vulnerabilities found for this severity\n") - return nil - } - - cli.OutputHuman( - "Great news! This container image has no vulnerabilities... (time for %s)\n", - randomEmoji(), - ) - return nil - } - - detailsReport := buildVulnerabilityDetailsReportTable(details) - cli.OutputHuman(buildVulnContainerAssessmentReportTable(summaryReport, detailsReport)) - if vulCmdState.Html { - if err := generateVulnAssessmentHTML(filteredAssessment); err != nil { - return err - } - } - } - - return nil -} - -func buildVulnerabilitySingleCveReportTable(details vulnerabilityDetailsReport) string { - var singleCveTable = strings.Builder{} - var singleCveTableContent = strings.Builder{} - - // sort by highest severity - sort.Slice(details.VulnerabilityDetails.Vulnerabilities, func(i, j int) bool { - sev1 := lwseverity.NewSeverity(details.VulnerabilityDetails.Vulnerabilities[i].Severity) - sev2 := lwseverity.NewSeverity(details.VulnerabilityDetails.Vulnerabilities[j].Severity) - return sev1 < sev2 - }) - - for _, cveData := range details.VulnerabilityDetails.Vulnerabilities { - detailsTable := renderCustomTable([]string{}, - [][]string{ - {"PACKAGE", cveData.PackageName}, - {"CURRENT VERSION", cveData.CurrentVersion}, - {"NAMESPACE", cveData.Namespace}, - {"SEVERITY", cveData.Severity}, - {"FEED", cveData.Feed}, - {"SRC", cveData.Src}, - {"START TIME", cveData.StartTime}, - {"VERSION FORMAT", cveData.VersionFormat}, - {"FIX VERSION", cveData.FixVersion}, - {"STATUS", cveData.Status}, - }, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetColWidth(150) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ) - - layerTable := renderCustomTable([]string{"INTRODUCED IN LAYERS"}, - introducedInLayerToTable(cveData), - tableFunc(func(t *tablewriter.Table) { - t.SetRowLine(true) - t.SetBorder(false) - t.SetAutoWrapText(true) - t.SetAlignment(tablewriter.ALIGN_LEFT) - t.SetColumnSeparator(" ") - }), - ) - - singleCveTableContent.WriteString(fmt.Sprintf("%s\n", detailsTable)) - singleCveTableContent.WriteString(layerTable) - } - - cveSummaryTable := renderOneLineCustomTable(details.VulnerabilityDetails.Vulnerabilities[0].Name, - singleCveTableContent.String(), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ) - - singleCveTable.WriteString(cveSummaryTable) - return singleCveTable.String() -} - -func introducedInLayerToTable(vuln vulnTable) (resourceTable [][]string) { - for _, layer := range vuln.CreatedBy { - resourceTable = append(resourceTable, []string{layer}) - } - return -} - -func buildVulnerabilityDetailsReportTable(details vulnerabilityDetailsReport) string { - report := &strings.Builder{} - - if vulCmdState.Details || vulCmdState.Packages || vulFiltersEnabled() || vulCmdState.Cve != "" { - if vulCmdState.Packages { - vulnPackagesTable := vulContainerImagePackagesToTable(details.Packages) - - report.WriteString( - renderSimpleTable( - []string{"CVE Count", "Severity", "Package", "Current Version", "Fix Version"}, - vulnPackagesTable, - ), - ) - - if vulFiltersEnabled() { - filteredOutput := fmt.Sprintf("%v of %v packages showing \n", - details.Packages.totalPackages, details.Packages.totalUnfiltered) - report.WriteString(filteredOutput) - } - } else { - vulnImageTable := vulContainerImageLayersToTable(details.VulnerabilityDetails) - - report.WriteString( - renderCustomTable( - []string{"CVE ID", "Severity", "Package", "Current Version", - "Fix Version", "Introduced in Layer", "Status"}, - vulnImageTable, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetRowLine(true) - t.SetColumnSeparator(" ") - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - ) - - if vulFiltersEnabled() { - filteredOutput := fmt.Sprintf("%v of %v vulnerabilities showing \n", - details.VulnerabilityDetails.TotalVulnerabilitiesShowing, details.VulnerabilityDetails.TotalVulnerabilities) - report.WriteString(filteredOutput) - } - if !vulCmdState.Html { - report.WriteString("\nTry adding '--packages' to show a list of packages with CVE count.\n") - } - } - } - - return report.String() -} - -func buildVulnerabilitySummaryReportTable(response api.VulnerabilitiesContainersResponse) string { - assessment := response.Data - mainReport := &strings.Builder{} - mainReport.WriteString( - renderCustomTable( - []string{ - "Container Image Details", - "Vulnerabilities", - }, - [][]string{{ - renderCustomTable([]string{}, - vulContainerImageToTable(assessment[0].EvalCtx.ImageInfo), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator("") - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - renderCustomTable([]string{"Severity", "Count", "Fixable"}, - vulContainerAssessmentToCountsTable(response), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - }}, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - t.SetColumnSeparator(" ") - }), - ), - ) - - return mainReport.String() -} - -func filterVulnContainerImagePackages(image []api.VulnerabilityContainer) filteredPackageTable { - var filteredPackages []packageTable - var aggregatedPackages []packageTable - var ( - vulnKey string - vulnKeys = []string{} - ) - - for _, i := range image { - vulnKey = fmt.Sprintf("%s-%s-%s", i.VulnID, i.FeatureKey.Name, i.FeatureKey.Version) - if array.ContainsStr(vulnKeys, vulnKey) { - continue - } - vulnKeys = append(vulnKeys, vulnKey) - pack := packageTable{ - cveCount: 1, - severity: cases.Title(language.English).String(i.Severity), - packageName: i.FeatureKey.Name, - currentVersion: i.FeatureKey.Version, - fixVersion: i.FixInfo.FixedVersion, - } - - // filter fixable - if vulCmdState.Fixable && i.FixInfo.FixedVersion == "" { - filteredPackages = aggregatePackages(filteredPackages, pack) - continue - } - - // filter severity - if vulCmdState.Severity != "" { - if filterSeverity(i.Severity, vulCmdState.Severity) { - filteredPackages = aggregatePackages(filteredPackages, pack) - continue - } - } - aggregatedPackages = aggregatePackages(aggregatedPackages, pack) - } - - return filteredPackageTable{packages: aggregatedPackages, totalPackages: len(aggregatedPackages)} -} - -func countVulnContainerImagePackages(image []api.VulnerabilityContainer) int { - var aggregatedPackages []packageTable - - for _, i := range image { - pack := packageTable{ - cveCount: 1, - severity: cases.Title(language.English).String(i.Severity), - packageName: i.FeatureKey.Name, - currentVersion: i.FeatureKey.Version, - fixVersion: i.FixInfo.FixedVersion, - } - aggregatedPackages = aggregatePackages(aggregatedPackages, pack) - } - - return len(aggregatedPackages) -} - -func vulContainerImagePackagesToTable(packageTable filteredPackageTable) [][]string { - var out [][]string - - for _, p := range packageTable.packages { - out = append(out, []string{ - strconv.Itoa(p.cveCount), - p.severity, - p.packageName, - p.currentVersion, - p.fixVersion, - }) - } - - // order by severity and package name - sort.Slice(out, func(i, j int) bool { - if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { - return out[i][2] < out[j][2] - } - return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) - }) - - return out -} - -func filterVulnerabilityContainer(image []api.VulnerabilityContainer) filteredImageTable { - var ( - vulns = make(map[string]vulnTable) - introducedInMap = make(map[string][]string) - vulnIDs []string - vulnsCount int - vulnList []vulnTable - filtered []api.VulnerabilityContainer - ) - - for _, i := range image { - vulnKey := fmt.Sprintf("%s-%s-%s", i.VulnID, i.FeatureKey.Name, i.FeatureKey.Version) - vulnIDs = append(vulnIDs, vulnKey) - // filter: severity - if vulCmdState.Severity != "" { - if filterSeverity(i.Severity, vulCmdState.Severity) { - continue - } - } - // filter: fixable - if vulCmdState.Fixable && i.FixInfo.FixedVersion == "" { - continue - } - - // Format IntroducedIn Field. In v2 response this field is not formatted with new lines. - regex := regexp.MustCompile(regexAllTabs) - introducedIn := regex.ReplaceAllString(i.FeatureProps.IntroducedIn, "\n") - - introducedInMap[vulnKey] = append(introducedInMap[vulnKey], introducedIn) - - if _, ok := vulns[vulnKey]; !ok { - vulns[vulnKey] = vulnTable{ - Name: i.VulnID, - Severity: i.Severity, - PackageName: i.FeatureKey.Name, - CurrentVersion: i.FeatureKey.Version, - FixVersion: i.FixInfo.FixedVersion, - Namespace: i.FeatureKey.Namespace, - Feed: i.FeatureProps.Feed, - StartTime: i.StartTime.Format(time.RFC3339), - VersionFormat: i.FeatureProps.VersionFormat, - Src: i.FeatureProps.Src, - // Todo(v2): CVSSv3Score is missing from V2 - CVSSv3Score: 0, - // Todo(v2): CVSSv2Score is missing from V2 - CVSSv2Score: 0, - Status: i.Status, - } - } - - filtered = append(filtered, i) - } - - // Set the aggregated introduced by layers for each vuln - for k, v := range introducedInMap { - vulnTable := vulns[k] - vulnTable.CreatedBy = v - vulns[k] = vulnTable - } - - var uniqueIDs []string = array.Unique(vulnIDs) - vulnsCount = len(uniqueIDs) - - for _, v := range vulns { - vulnList = append(vulnList, v) - } - - return filteredImageTable{ - Vulnerabilities: vulnList, - TotalVulnerabilitiesShowing: len(vulns), - TotalVulnerabilities: vulnsCount, - Filtered: filtered, - } -} - -func vulContainerImageLayersToCSV(assessment []api.VulnerabilityContainer) [][]string { - var out [][]string - for _, vuln := range assessment { - out = append(out, []string{ - vuln.VulnID, - vuln.Severity, - strconv.FormatFloat(0.0, 'f', 1, 64), - strconv.FormatFloat(0.0, 'f', 1, 64), - vuln.FeatureKey.Name, - vuln.FeatureKey.Version, - vuln.FixInfo.FixedVersion, - vuln.FeatureProps.VersionFormat, - vuln.FeatureProps.Feed, - vuln.FeatureProps.Src, - vuln.StartTime.Format(time.RFC3339), - vuln.FeatureKey.Namespace, - vuln.EvalCtx.ImageInfo.ID, - vuln.EvalCtx.ImageInfo.Digest, - vuln.EvalCtx.ImageInfo.Repo, - vuln.EvalCtx.ImageInfo.Registry, - strconv.Itoa(vuln.EvalCtx.ImageInfo.Size), - vuln.FeatureProps.IntroducedIn, - }) - } - - sort.Slice(out, func(i, j int) bool { - if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { - return out[i][4] < out[j][4] - } - return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) - }) - - return out -} - -func vulContainerImageLayersToTable(imageTable filteredImageTable) [][]string { - var out [][]string - var createdByKeys = make(map[string]bool) - - for _, vuln := range imageTable.Vulnerabilities { - introducedBy := strings.Join(vuln.CreatedBy, ",") - // if the same vuln is introduced in more than 1 layer, only display the number of layers - if len(vuln.CreatedBy) > 1 { - introducedBy = fmt.Sprintf("introduced in %d layers...", len(vuln.CreatedBy)) - } - - if !createdByKeys[fmt.Sprintf("%s-%s", vuln.Name, vuln.CurrentVersion)] { - out = append(out, []string{ - vuln.Name, - vuln.Severity, - vuln.PackageName, - vuln.CurrentVersion, - vuln.FixVersion, - introducedBy, - vuln.Status, - }) - } - - createdByKeys[fmt.Sprintf("%s-%s", vuln.Name, vuln.CurrentVersion)] = true - } - - sort.Slice(out, func(i, j int) bool { - if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { - return out[i][2] < out[j][2] - } - return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) - }) - - return out -} - -func vulContainerAssessmentToCountsTable(assessment api.VulnerabilitiesContainersResponse) [][]string { - return [][]string{ - {"Critical", fmt.Sprint(assessment.CriticalVulnerabilities()), - fmt.Sprint(assessment.VulnFixableCount("critical"))}, - {"High", fmt.Sprint(assessment.HighVulnerabilities()), - fmt.Sprint(assessment.VulnFixableCount("high"))}, - {"Medium", fmt.Sprint(assessment.MediumVulnerabilities()), - fmt.Sprint(assessment.VulnFixableCount("medium"))}, - {"Low", fmt.Sprint(assessment.LowVulnerabilities()), - fmt.Sprint(assessment.VulnFixableCount("low"))}, - {"Info", fmt.Sprint(assessment.InfoVulnerabilities()), - fmt.Sprint(assessment.VulnFixableCount("info"))}, - } -} - -func vulContainerImageToTable(image api.ImageInfo) [][]string { - return [][]string{ - {"ID", image.ID}, - {"Digest", image.Digest}, - {"Registry", image.Registry}, - {"Repository", image.Repo}, - {"Size", byteCountBinary(image.Size)}, - {"Created At", time.UnixMilli(image.CreatedTime).UTC().Format(time.RFC3339)}, - {"Tags", strings.Join(image.Tags, ",")}, - } -} - -func aggregatePackages(slice []packageTable, s packageTable) []packageTable { - for i, item := range slice { - if item.packageName == s.packageName && - item.currentVersion == s.currentVersion && - item.severity == s.severity && - item.fixVersion == s.fixVersion { - slice[i].cveCount++ - return slice - } - } - return append(slice, s) -} - -type vulnerabilityDetailsReport struct { - VulnerabilityDetails filteredImageTable - Packages filteredPackageTable -} - -type filteredImageTable struct { - Vulnerabilities []vulnTable - TotalVulnerabilitiesShowing int - TotalVulnerabilities int - Filtered []api.VulnerabilityContainer -} - -type vulnTable struct { - Name string - Severity string - PackageName string - CurrentVersion string - FixVersion string - CreatedBy []string - CVSSv2Score float64 - CVSSv3Score float64 - Status string - Namespace string - Feed string - Src string - StartTime string - VersionFormat string -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host.go deleted file mode 100644 index f5f290d4b..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host.go +++ /dev/null @@ -1,247 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" -) - -var ( - // the maximum number of packages per scan request - manifestPkgsCap = 1000 - - // the package manifest file - pkgManifestFile string - - // automatically generate the package manifest from the local host - pkgManifestLocal bool -) - -func init() { - // add sub-commands to the 'vulnerability host' command - vulHostCmd.AddCommand(vulHostScanPkgManifestCmd) - vulHostCmd.AddCommand(vulHostGenPkgManifestCmd) - vulHostCmd.AddCommand(vulHostListCvesCmd) - vulHostCmd.AddCommand(vulHostListHostsCmd) - vulHostCmd.AddCommand(vulHostShowAssessmentCmd) - - setFixableFlag( - vulHostListCvesCmd.Flags(), - vulHostShowAssessmentCmd.Flags(), - vulHostScanPkgManifestCmd.Flags(), - ) - - setPackagesFlag( - vulHostListCvesCmd.Flags(), - vulHostShowAssessmentCmd.Flags(), - vulHostScanPkgManifestCmd.Flags(), - ) - - setDetailsFlag( - vulHostShowAssessmentCmd.Flags(), - ) - - setSeverityFlag( - vulHostListCvesCmd.Flags(), - vulHostShowAssessmentCmd.Flags(), - ) - - setFailOnSeverityFlag( - vulHostShowAssessmentCmd.Flags(), - vulHostScanPkgManifestCmd.Flags(), - ) - - setFailOnFixableFlag( - vulHostShowAssessmentCmd.Flags(), - vulHostScanPkgManifestCmd.Flags(), - ) - - setActiveFlag( - vulHostShowAssessmentCmd.Flags(), - vulHostListCvesCmd.Flags(), - ) - - setCsvFlag( - vulHostListCvesCmd.Flags(), - vulHostListHostsCmd.Flags(), - vulHostShowAssessmentCmd.Flags(), - ) - - setTimeRangeFlags( - vulHostListCvesCmd.Flags(), - vulHostListHostsCmd.Flags(), - ) - - // the package manifest file - vulHostScanPkgManifestCmd.Flags().StringVarP(&pkgManifestFile, - "file", "f", "", - "path to a package manifest to scan", - ) - - // automatically generate the package manifest from the local host - vulHostScanPkgManifestCmd.Flags().BoolVarP(&pkgManifestLocal, - "local", "l", false, - "automatically generate the package manifest from the local host", - ) - - // the collector_type of the assessment - vulHostShowAssessmentCmd.Flags().StringVar(&vulCmdState.CollectorType, - "collector_type", vulnHostCollectorTypeAgentless, - "filter assessments by collector type (Agent or Agentless)", - ) -} - -func cvesSummary(hosts []api.VulnerabilityHost) map[string]VulnCveSummary { - uniqueCves := make(map[string]VulnCveSummary) - for _, host := range hosts { - if host.VulnID == "" { - continue - } - - if v, ok := uniqueCves[host.VulnID]; ok { - if array.ContainsStr(v.Hostnames, host.EvalCtx.Hostname) { - continue - } - - v.Count++ - v.Hostnames = append(v.Hostnames, host.EvalCtx.Hostname) - uniqueCves[host.VulnID] = v - continue - } - sum := VulnCveSummary{Host: host, Count: 1} - sum.Hostnames = append(sum.Hostnames, sum.Host.EvalCtx.Hostname) - uniqueCves[host.VulnID] = sum - } - return uniqueCves -} - -func aggregatePackagesWithHosts(slice []packageTable, s packageTable, host bool, hasFix bool) []packageTable { - for i, item := range slice { - // if packages are the same, group together - if item.equals(s) { - slice[i].cveCount++ - if host { - slice[i].hostCount++ - } - if hasFix { - slice[i].fixes++ - } - return slice - } - } - return append(slice, s) -} - -func filterHostCVEsTable(cves map[string]VulnCveSummary) (map[string]VulnCveSummary, string) { - var out map[string]VulnCveSummary - var filteredCves = 0 - var totalCves = 0 - - out, filtered, total := filterHostVulnCVEs(cves) - filteredCves += filtered - totalCves += total - - if filteredCves > 0 { - showing := totalCves - filteredCves - return out, fmt.Sprintf("\n%d of %d cve(s) showing\n", showing, totalCves) - } - - return out, "" -} - -func filterHostVulnCVEs(cves map[string]VulnCveSummary) (map[string]VulnCveSummary, int, int) { - var ( - filtered = 0 - total = 0 - out = make(map[string]VulnCveSummary) - ) - - for _, c := range cves { - cve := c.Host - total++ - // if the user wants to show only vulnerabilities of active packages - if vulCmdState.Active && cve.PackageActive() == "" { - filtered++ - continue - } - if vulCmdState.Fixable && (cve.FixInfo.FixAvailable == "" || cve.FixInfo.FixAvailable == "0") { - filtered++ - continue - } - - if vulCmdState.Severity != "" { - if filterSeverity(cve.Severity, vulCmdState.Severity) { - filtered++ - continue - } - } - out[cve.VulnID] = c - } - - return out, filtered, total -} - -func hostVulnAssessmentToCountsTable(counts api.HostVulnCounts) [][]string { - return [][]string{ - {"Critical", fmt.Sprint(counts.Critical), - fmt.Sprint(counts.CritFixable)}, - {"High", fmt.Sprint(counts.High), - fmt.Sprint(counts.HighFixable)}, - {"Medium", fmt.Sprint(counts.Medium), - fmt.Sprint(counts.MedFixable)}, - {"Low", fmt.Sprint(counts.Low), - fmt.Sprint(counts.LowFixable)}, - {"Info", fmt.Sprint(counts.Info), - fmt.Sprint(counts.InfoFixable)}, - } -} - -func buildHostVulnCVEsToTableError() string { - msg := "There are no" - if vulCmdState.Fixable { - msg = fmt.Sprintf("%s fixable", msg) - } - - if vulCmdState.Severity != "" { - msg = fmt.Sprintf("%s %s", msg, vulCmdState.Severity) - } - - msg = fmt.Sprintf("%s vulnerabilities", msg) - - if vulCmdState.Active { - msg = fmt.Sprintf("%s of packages actively running", msg) - } - return fmt.Sprintf("%s in your environment.\n", msg) -} - -func summaryToHostList(sum map[string]VulnCveSummary) (hosts []api.VulnerabilityHost) { - for _, v := range sum { - hosts = append(hosts, v.Host) - } - return -} - -type VulnCveSummary struct { - Host api.VulnerabilityHost - Count int - Hostnames []string -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_gen_package_manifest.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_gen_package_manifest.go deleted file mode 100644 index 3be5b4acb..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_gen_package_manifest.go +++ /dev/null @@ -1,48 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // vulHostGenPkgManifestCmd represents the 'lacework vuln host generate-pkg-manifest' command - vulHostGenPkgManifestCmd = &cobra.Command{ - Use: "generate-pkg-manifest", - Args: cobra.NoArgs, - Short: "Generates a package-manifest from the local host", - Long: `Generates a package-manifest formatted for usage with the Lacework -scan package-manifest API. - -Additionally, you can automatically generate a package-manifest from -the local host and send it directly to the Lacework API with the command: - - lacework vulnerability host scan-pkg-manifest --local`, - RunE: func(_ *cobra.Command, _ []string) error { - manifest, err := cli.GeneratePackageManifest() - if err != nil { - return errors.Wrap(err, "unable to generate package manifest") - } - - return cli.OutputJSON(manifest) - }, - } -) diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_cves.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_cves.go deleted file mode 100644 index 5fc55eaba..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_cves.go +++ /dev/null @@ -1,352 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sort" - "strconv" - "time" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/array" - "github.com/lacework/go-sdk/lwseverity" - "github.com/lacework/go-sdk/lwtime" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -var ( - // vulHostListCvesCmd represents the 'lacework vuln host list-cves' command - vulHostListCvesCmd = &cobra.Command{ - Use: "list-cves", - Args: cobra.NoArgs, - PreRunE: func(_ *cobra.Command, _ []string) error { - if vulCmdState.Csv { - cli.EnableCSVOutput() - } - return nil - }, - Short: "List the CVEs found in the hosts in your environment", - Long: `List the CVEs found in the hosts in your environment. - -Filter results to only show vulnerabilities actively running in your environment -with fixes: - - lacework vulnerability host list-cves --active --fixable`, - RunE: func(_ *cobra.Command, args []string) error { - if err := validateSeverityFlags(); err != nil { - return err - } - var ( - filter api.SearchFilter - start time.Time - end time.Time - err error - ) - - if vulCmdState.Range != "" { - cli.Log.Debugw("retrieving natural time range", "range", vulCmdState.Range) - start, end, err = lwtime.ParseNatural(vulCmdState.Range) - if err != nil { - return errors.Wrap(err, "unable to parse natural time range") - } - - } else { - cli.Log.Debugw("parsing start time", "start", vulCmdState.Start) - start, err = parseQueryTime(vulCmdState.Start) - if err != nil { - return errors.Wrap(err, "unable to parse start time") - } - - cli.Log.Debugw("parsing end time", "end", vulCmdState.End) - end, err = parseQueryTime(vulCmdState.End) - if err != nil { - return errors.Wrap(err, "unable to parse end time") - } - } - - filter.TimeFilter = &api.TimeFilter{ - StartTime: &start, - EndTime: &end, - } - - cli.StartProgress("Fetching CVEs in your environment...") - response, err := cli.LwApi.V2.Vulnerabilities.Hosts.SearchAllPages(filter) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get CVEs from hosts") - } - - if err := buildListCVEReports(response.Data); err != nil { - return err - } - return nil - }, - } -) - -// Build the cli output for vuln host list-cves -func buildListCVEReports(cves []api.VulnerabilityHost) error { - uniqueCves := cvesSummary(cves) - filteredCves, filtered := filterHostCVEsTable(uniqueCves) - - if cli.JSONOutput() { - if filteredCves == nil { - if err := cli.OutputJSON(buildHostVulnCVEsToTableError()); err != nil { - return err - } - } else { - // fix here too - if err := cli.OutputJSON(summaryToHostList(filteredCves)); err != nil { - return err - } - } - return nil - } - - if len(cves) == 0 { - // @afiune add a helpful message, possible things are: - // 1) host vuln feature is not enabled on the account - // 2) user doesn't have agents deployed - // 3) there are actually NO vulnerabilities on any host - cli.OutputHuman("There are no vulnerabilities on any host in your environment.\n") - return nil - } - - // packages output - if vulCmdState.Packages { - packages, filteredPackages := hostVulnListCvesPackagesTable(cves) - if cli.CSVOutput() { - - // order by cve count - - return cli.OutputCSV( - []string{"CVE Count", "Highest", "Package", "Current Version", "Fix Version", "Pkg Status", "Hosts"}, - packages, - ) - } - vulnListCvesPackagesOutput(packages, filteredPackages) - return nil - } - - rows := hostVulnCVEsTable(filteredCves) - if len(rows) == 0 { - cli.OutputHuman(buildHostVulnCVEsToTableError()) - return nil - } - - if cli.CSVOutput() { - return cli.OutputCSV( - []string{"CVE ID", "Severity", "CvssV2", "CvssV3", "Package", "Current Version", - "Fix Version", "OS Version", "Hosts", "Pkg Status", "Vuln Status"}, - rows, - ) - } - - cli.OutputHuman( - renderSimpleTable( - []string{"CVE ID", "Severity", "CvssV2", "CvssV3", "Package", "Current Version", - "Fix Version", "OS Version", "Hosts", "Pkg Status", "Vuln Status"}, - rows, - ), - ) - - if filtered != "" { - cli.OutputHuman(filtered) - } - - if !vulCmdState.Active { - cli.OutputHuman( - "\nTry adding '--active' to only show vulnerabilities of packages actively running.\n", - ) - } else if !vulCmdState.Fixable { - cli.OutputHuman( - "\nTry adding '--fixable' to only show fixable vulnerabilities.\n", - ) - } - return nil -} - -func vulnListCvesPackagesOutput(packages [][]string, filteredPackagesMsg string) { - // sort by highest cve count - sort.Slice(packages, func(i, j int) bool { - return stringToInt(packages[i][0]) > stringToInt(packages[j][0]) - }) - - cli.OutputHuman( - renderSimpleTable( - []string{ - "CVE Count", - "Highest Severity", - "Package", - "Current Version", - "Fix Version", - "Pkg Status", - "Hosts Impacted", - }, - packages, - ), - ) - if filteredPackagesMsg != "" { - cli.OutputHuman(filteredPackagesMsg) - } -} - -func hostVulnListCvesPackagesTable(cves []api.VulnerabilityHost) ([][]string, string) { - var ( - out [][]string - filteredPackages []string - aggregatedPackages []packageTable - ) - - // Get all unique package names - var packageNames []string - for _, c := range cves { - if c.VulnID != "" { - packageNames = append(packageNames, c.FeatureKey.Name) - } - } - - var uniquePackageNames []string = array.Unique(packageNames) - var added []string - - for _, u := range uniquePackageNames { - var ( - pack packageTable - cveIDs []string - hosts []string - severities []lwseverity.Severity - active string - packageIdentifier string - ) - for _, host := range cves { - packageIdentifier = fmt.Sprintf("%s-%s", host.VulnID, host.FeatureKey.VersionInstalled) - if host.FeatureKey.Name == u { - if host.PackageActive() == "ACTIVE" { - active = "ACTIVE" - } - - if array.ContainsStr(added, host.FeatureKey.Name) { - if host.Severity != "" { - cveIDs = append(cveIDs, packageIdentifier) - severities = append(severities, lwseverity.NewSeverity(host.Severity)) - hosts = append(hosts, host.EvalCtx.Hostname) - } - continue - } - - pack = packageTable{ - severity: cases.Title(language.English).String(host.Severity), - packageName: host.FeatureKey.Name, - currentVersion: host.FeatureKey.VersionInstalled, - fixVersion: host.FixInfo.FixedVersion, - } - - added = append(added, host.FeatureKey.Name) - cveIDs = append(cveIDs, fmt.Sprintf("%s-%s", host.VulnID, host.FeatureKey.VersionInstalled)) - severities = append(severities, lwseverity.NewSeverity(host.Severity)) - hosts = append(hosts, host.EvalCtx.Hostname) - } - } - - // Exclude filtered packages and packages without vulns - if !array.ContainsStr(filteredPackages, packageIdentifier) && len(cveIDs) > 0 { - var unqCves []string = array.Unique(cveIDs) - var unqHosts []string = array.Unique(hosts) - pack.packageStatus = active - pack.cveCount = len(unqCves) - pack.hostCount = len(unqHosts) - - // set highest known severity of the package - if len(severities) > 0 { - lwseverity.SortSlice(severities) - pack.severity = severities[0].GetSeverity() - } - aggregatedPackages = append(aggregatedPackages, pack) - } - } - - for _, p := range aggregatedPackages { - // apply package filters - if vulCmdState.Active && p.packageStatus == "" { - filteredPackages = append(filteredPackages, p.packageName) - continue - } - - if vulCmdState.Fixable && p.fixVersion == "" { - filteredPackages = append(filteredPackages, p.packageName) - continue - } - - if vulCmdState.Severity != "" { - if p.severity == "Unknown" { - continue - } - if lwseverity.ShouldFilter(p.severity, vulCmdState.Severity) { - filteredPackages = append(filteredPackages, p.packageName) - continue - } - } - - output := []string{ - strconv.Itoa(p.cveCount), - p.severity, - p.packageName, - p.currentVersion, - p.fixVersion, - p.packageStatus} - if p.hostCount > 0 { - output = append(output, strconv.Itoa(p.hostCount)) - } - out = append(out, output) - } - - filteredOutput := fmt.Sprintf("%d of %d package(s) showing\n", len(out), len(uniquePackageNames)) - return out, filteredOutput -} - -func hostVulnCVEsTable(hostSummary map[string]VulnCveSummary) [][]string { - var out [][]string - for _, sum := range hostSummary { - host := sum.Host - out = append(out, []string{ - host.VulnID, - host.Severity, - host.CvssV2(), - host.CvssV3(), - host.FeatureKey.Name, - host.FeatureKey.VersionInstalled, - host.FixInfo.FixedVersion, - host.FeatureKey.Namespace, - strconv.Itoa(sum.Count), - host.PackageActive(), - host.Status, - }) - } - - // order by the total number of host - sort.Slice(out, func(i, j int) bool { - return stringToInt(out[i][8]) > stringToInt(out[j][8]) - }) - - return out -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_hosts.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_hosts.go deleted file mode 100644 index d61440bc8..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_list_hosts.go +++ /dev/null @@ -1,237 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sort" - "strconv" - "strings" - "time" - - "github.com/lacework/go-sdk/lwtime" - - "github.com/lacework/go-sdk/api" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -var ( - // vulHostListHostsCmd represents the 'lacework vuln host list-hosts ' command - vulHostListHostsCmd = &cobra.Command{ - Use: "list-hosts ", - Args: cobra.ExactArgs(1), - PreRunE: func(_ *cobra.Command, _ []string) error { - if vulCmdState.Csv { - cli.EnableCSVOutput() - } - return nil - }, - Short: "List the hosts that contain a specified CVE ID in your environment", - Long: `List the hosts that contain a specified CVE ID in your environment. - -To list the CVEs found in the hosts of your environment run: - - lacework vulnerability host list-cves`, - RunE: func(_ *cobra.Command, args []string) error { - var ( - filter api.SearchFilter - start time.Time - end time.Time - err error - ) - - if vulCmdState.Range != "" { - cli.Log.Debugw("retrieving natural time range", "range", vulCmdState.Range) - start, end, err = lwtime.ParseNatural(vulCmdState.Range) - if err != nil { - return errors.Wrap(err, "unable to parse natural time range") - } - - } else { - cli.Log.Debugw("parsing start time", "start", vulCmdState.Start) - start, err = parseQueryTime(vulCmdState.Start) - if err != nil { - return errors.Wrap(err, "unable to parse start time") - } - - cli.Log.Debugw("parsing end time", "end", vulCmdState.End) - end, err = parseQueryTime(vulCmdState.End) - if err != nil { - return errors.Wrap(err, "unable to parse end time") - } - } - - filter.TimeFilter = &api.TimeFilter{ - StartTime: &start, - EndTime: &end, - } - - filter.Filters = []api.Filter{ - {Expression: "eq", - Field: "vulnId", - Value: args[0]}, - {Expression: "ne", - Field: "status", - Value: "Fixed"}, - } - - cli.StartProgress("Fetching Hosts...") - response, err := cli.LwApi.V2.Vulnerabilities.Hosts.SearchAllPages(filter) - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to get hosts with CVE "+args[0]) - } - - if cli.JSONOutput() { - return cli.OutputJSON(response.Data) - } - - if len(response.Data) == 0 { - cli.OutputHuman("There are no hosts in your environment with the CVE id '%s'\n", args[0]) - return nil - } - - rows := hostVulnHostsTable(response.Data) - if cli.CSVOutput() { - return cli.OutputCSV( - []string{"Machine ID", "Hostname", "External IP", "Internal IP", - "Os/Arch", "Provider", "Instance ID", "Vulnerabilities", "Status"}, - rows, - ) - } - - cli.OutputHuman( - renderSimpleTable( - []string{"Machine ID", "Hostname", "External IP", "Internal IP", - "Os/Arch", "Provider", "Instance ID", "Vulnerabilities", "Status"}, - rows, - ), - ) - return nil - }, - } -) - -func hostVulnHostsTable(hosts []api.VulnerabilityHost) [][]string { - var out [][]string - hostSummary := hostsSummary(hosts) - for _, sum := range hostSummary { - host := sum.host - summary := severitySummary(sum.severity, sum.fixable) - machineTags, err := host.GetMachineTags() - if err != nil { - cli.Log.Debug("failed to parse machine tags") - } - out = append(out, []string{ - strconv.Itoa(host.Mid), - host.EvalCtx.Hostname, - machineTags.ExternalIP, - machineTags.InternalIP, - fmt.Sprintf("%s/%s", machineTags.Os, machineTags.Arch), - machineTags.VMProvider, - machineTags.InstanceID, - summary, - host.Status, - }) - } - - return out -} - -func severitySummary(severities []string, fixable int) string { - summary := &strings.Builder{} - sevSummaries := make(map[string]int) - for _, s := range severities { - switch s { - case "Critical": - if v, ok := sevSummaries["Critical"]; ok { - sevSummaries["Critical"] = v + 1 - } - sevSummaries["Critical"] = 1 - case "High": - if v, ok := sevSummaries["High"]; ok { - sevSummaries["High"] = v + 1 - } - sevSummaries["High"] = 1 - case "Medium": - if v, ok := sevSummaries["Medium"]; ok { - sevSummaries["Medium"] = v + 1 - } - sevSummaries["Medium"] = 1 - case "Low": - if v, ok := sevSummaries["Low"]; ok { - sevSummaries["Low"] = v + 1 - } - sevSummaries["Low"] = 1 - case "Info": - if v, ok := sevSummaries["Info"]; ok { - sevSummaries["Info"] = v + 1 - } - sevSummaries["Info"] = 1 - } - } - - var keys []string - for k := range sevSummaries { - keys = append(keys, k) - } - - sort.Slice(keys, func(i, j int) bool { - return api.SeverityOrder(keys[i]) < api.SeverityOrder(keys[j]) - }) - - for _, k := range keys { - v := sevSummaries[k] - summary.WriteString(fmt.Sprintf(" %d %s", v, k)) - } - - if fixable != 0 { - summary.WriteString(fmt.Sprintf(" %d Fixable", fixable)) - } - return summary.String() -} - -func hostsSummary(hosts []api.VulnerabilityHost) map[int]vulnSummary { - uniqueHosts := make(map[int]vulnSummary) - for _, host := range hosts { - if v, ok := uniqueHosts[host.Mid]; ok { - v.severity = append(v.severity, host.Severity) - if host.FixInfo.FixAvailable != "" && host.FixInfo.FixAvailable != "0" { - v.fixable++ - } - uniqueHosts[host.Mid] = v - continue - } - - sum := vulnSummary{host: host} - sum.severity = append(sum.severity, host.Severity) - if host.FixInfo.FixAvailable != "" && host.FixInfo.FixAvailable != "0" { - sum.fixable++ - } - uniqueHosts[host.Mid] = sum - } - return uniqueHosts -} - -type vulnSummary struct { - host api.VulnerabilityHost - severity []string - fixable int -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_scan_package_manifest.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_scan_package_manifest.go deleted file mode 100644 index 7e3ab057b..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_scan_package_manifest.go +++ /dev/null @@ -1,329 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "encoding/json" - "fmt" - "os" - "sort" - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/lacework/go-sdk/api" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -var ( - // vulHostScanPkgManifestCmd represents the 'lacework vuln host scan-pkg-manifest' command - vulHostScanPkgManifestCmd = &cobra.Command{ - Use: "scan-pkg-manifest ", - Args: cobra.MaximumNArgs(1), - Short: "Request an on-demand host vulnerability assessment from a package-manifest", - Long: `Request an on-demand host vulnerability assessment of your software packages to -determine if the packages contain any common vulnerabilities and exposures. - -Simple usage: - - lacework vulnerability host scan-pkg-manifest '{ - "osPkgInfoList": [ - { - "os":"Ubuntu", - "osVer":"18.04", - "pkg": "openssl", - "pkgVer": "1.1.1-1ubuntu2.1~18.04.5" - } - ] - }' - -To generate a package-manifest from the local host and scan it automatically: - - lacework vulnerability host scan-pkg-manifest --local - -**NOTE:** - - Only packages managed by a package manager for supported OS's are reported. - - Calls to this operation are rate limited to 10 calls per hour, per access key. - - This operation is limited to 10k packages per command execution.`, - RunE: func(c *cobra.Command, args []string) error { - if err := validateSeverityFlags(); err != nil { - return err - } - - var ( - pkgManifest = new(api.VulnerabilitiesPackageManifest) - pkgManifestBytes []byte - err error - ) - - if len(args) != 0 && args[0] != "" { - pkgManifestBytes = []byte(args[0]) - cli.Log.Debugw("package manifest loaded from arguments", "raw", args[0]) - } else if pkgManifestFile != "" { - pkgManifestBytes, err = os.ReadFile(pkgManifestFile) - if err != nil { - return errors.Wrap(err, "unable to read file") - } - cli.Log.Debugw("package manifest loaded from file", "raw", string(pkgManifestBytes)) - } else if pkgManifestLocal { - pkgManifest, err = cli.GeneratePackageManifest() - if err != nil { - return errors.Wrap(err, "unable to generate package manifest") - } - cli.Log.Debugw("package manifest generated from localhost", "raw", pkgManifest) - } else { - // avoid asking for a confirmation before launching the editor - var content string - prompt := &survey.Editor{ - Message: "Provide a package manifest to scan", - FileName: "package-manifest*.json", - } - err = survey.AskOne(prompt, &content) - if err != nil { - return errors.Wrap(err, "unable to load package manifest from editor") - } - pkgManifestBytes = []byte(content) - cli.Log.Debugw("package manifest loaded via editor", "raw", content) - } - - if len(pkgManifestBytes) != 0 { - err = json.Unmarshal(pkgManifestBytes, pkgManifest) - if err != nil { - return errors.Wrap(err, "invalid package manifest json file") - } - } - - totalPkgs := len(pkgManifest.OsPkgInfoList) - cli.StartProgress(" Scanning packages...") - cli.Log.Infow("manifest", "total_packages", totalPkgs) - var response api.VulnerabilitySoftwarePackagesResponse - // check if the package manifest has more than the maximum - // number of packages, if so, make multiple API requests - if totalPkgs >= manifestPkgsCap { - cli.Log.Infow("manifest over the limit, splitting up") - cli.Event.Feature = featSplitPkgManifest - cli.Event.AddFeatureField("total_packages", totalPkgs) - response, err = fanOutHostScans( - splitPackageManifest(pkgManifest, manifestPkgsCap)..., - ) - } else { - response, err = cli.LwApi.V2.Vulnerabilities.SoftwarePackages.Scan(*pkgManifest) - } - cli.StopProgress() - if err != nil { - return errors.Wrap(err, "unable to request an on-demand host vulnerability scan") - } - - if err := buildVulnHostScanPkgManifestReports(&response); err != nil { - return err - } - - if vulFailureFlagsEnabled() { - cli.Log.Infow("failure flags enabled", - "fail_on_severity", vulCmdState.FailOnSeverity, - "fail_on_fixable", vulCmdState.FailOnFixable, - ) - assessmentCounts := response.VulnerabilityCounts() - vulnPolicy := NewVulnerabilityPolicyError( - &assessmentCounts, - vulCmdState.FailOnSeverity, - vulCmdState.FailOnFixable, - ) - if vulnPolicy.NonCompliant() { - c.SilenceUsage = true - return vulnPolicy - } - } - return nil - }, - } -) - -// Build the cli output for vuln host scan-package-manifest -func buildVulnHostScanPkgManifestReports(response *api.VulnerabilitySoftwarePackagesResponse) error { - response.Data = filterHostScanPackagesVulnDetails(response.Data) - - if cli.JSONOutput() { - return cli.OutputJSON(response) - } - - if len(response.Data) == 0 { - cli.OutputHuman(fmt.Sprintf("There are no vulnerabilities found! Time for %s\n", randomEmoji())) - } else { - cli.OutputHuman(hostScanPackagesVulnToTable(response)) - } - - return nil -} - -func hostScanPackagesVulnToTable(scan *api.VulnerabilitySoftwarePackagesResponse) string { - var ( - mainBldr = &strings.Builder{} - rows [][]string - headers []string - ) - - if vulCmdState.Packages { - rows = hostScanPackagesVulnPackagesTable(filterHostScanPackagesVulnPackages(scan.Data)) - headers = []string{ - "CVE Count", - "Severity", - "Package", - "Version", - "Fixes Available", - } - } else { - rows = hostScanPackagesVulnDetailsTable(scan.Data) - headers = []string{ - "CVE ID", - "Severity", - "Score", - "Package", - "Version", - "Fix Version", - } - } - - if len(rows) == 0 { - if vulCmdState.Fixable { - return "There are no fixable vulnerabilities.\n" - } - scannedVia := "package manifest" - if pkgManifestLocal { - scannedVia = "localhost" - } - return fmt.Sprintf( - "Great news! The %s has no vulnerabilities... (time for %s)\n", - scannedVia, randomEmoji(), - ) - } - - mainBldr.WriteString( - renderOneLineCustomTable("Vulnerabilities", - renderCustomTable( - []string{"Severity", "Count", "Fixable"}, - hostVulnAssessmentToCountsTable(scan.VulnerabilityCounts()), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - ), - ) - - mainBldr.WriteString(renderSimpleTable(headers, rows)) - - return mainBldr.String() -} - -func filterHostScanPackagesVulnDetails(vulns []api.VulnerabilitySoftwarePackage) []api.VulnerabilitySoftwarePackage { - out := make([]api.VulnerabilitySoftwarePackage, 0) - - for _, vuln := range vulns { - if !vuln.IsVulnerable() { - continue - } - - if vulCmdState.Fixable && !vuln.HasFix() { - continue - } - - out = append(out, vuln) - } - - return out -} - -func hostScanPackagesVulnDetailsTable(vulns []api.VulnerabilitySoftwarePackage) [][]string { - var out [][]string - for _, vuln := range vulns { - out = append(out, []string{ - vuln.VulnID, - vuln.Severity, - vuln.ScoreString(), - vuln.OsPkgInfo.Pkg, - vuln.OsPkgInfo.PkgVer, - vuln.FixInfo.FixedVersion, - }) - } - - // order by severity - sort.Slice(out, func(i, j int) bool { - return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) - }) - - return out -} - -func filterHostScanPackagesVulnPackages(vulns []api.VulnerabilitySoftwarePackage) filteredPackageTable { - var ( - filteredPackages []packageTable - aggregatedPackages []packageTable - ) - - for _, vuln := range vulns { - pack := packageTable{ - cveCount: 1, - severity: cases.Title(language.English).String(vuln.Severity), - packageName: vuln.OsPkgInfo.Pkg, - currentVersion: vuln.OsPkgInfo.PkgVer, - } - - if vulCmdState.Fixable && !vuln.HasFix() { - filteredPackages = aggregatePackagesWithHosts(aggregatedPackages, pack, false, false) - continue - } - - aggregatedPackages = aggregatePackagesWithHosts(aggregatedPackages, pack, false, vuln.HasFix()) - } - - return filteredPackageTable{ - packages: aggregatedPackages, - totalPackages: len(aggregatedPackages), - totalUnfiltered: len(filteredPackages) + len(aggregatedPackages), - } -} - -func hostScanPackagesVulnPackagesTable(pkgs filteredPackageTable) [][]string { - var out [][]string - for _, pkg := range pkgs.packages { - out = append(out, []string{ - "1", - pkg.severity, - pkg.packageName, - pkg.currentVersion, - strconv.Itoa(pkg.fixes), - }) - } - - // order by severity - sort.Slice(out, func(i, j int) bool { - return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) - }) - - return out -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_show_assessment.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_show_assessment.go deleted file mode 100644 index 2c08221cc..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_host_show_assessment.go +++ /dev/null @@ -1,625 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "sort" - "strconv" - "strings" - "time" - - "github.com/lacework/go-sdk/api" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -var ( - // vulHostListCvesCmd represents the 'lacework vuln host list-cves' command - vulHostShowAssessmentCmd = &cobra.Command{ - Use: "show-assessment ", - Aliases: []string{"show"}, - Args: cobra.ExactArgs(1), - Short: "Show results of a host vulnerability assessment", - Long: `Show results of a host vulnerability assessment. - -To find the machine id from hosts in your environment, use the command: - - lacework vulnerability host list-cves - -Grab a CVE id and feed it to the command: - - lacework vulnerability host list-hosts my_cve_id`, - PreRunE: func(cmd *cobra.Command, _ []string) error { - if vulCmdState.Csv { - cli.EnableCSVOutput() - - // when rendering csv output, default to details since there is no output with less verbosity - if !vulCmdState.Details && !vulCmdState.Packages { - vulCmdState.Details = true - } - } - - // validate collector_type flag - switch vulCmdState.CollectorType { - case vulnHostCollectorTypeAgentless, vulnHostCollectorTypeAgent: - default: - return errors.Errorf( - "collector_type must be either %s or %s", - vulnHostCollectorTypeAgent, vulnHostCollectorTypeAgentless, - ) - } - - if vulCmdState.CollectorType == vulnHostCollectorTypeAgentless { - // check for agentless cloud integrations - if !checkAgentlessCloudAccount() { - // if the user has set the flag '--collector_type Agentless' - if cmd.Flags().Changed("collector_type") { - return errors.New("No agentless integrations configured.\n" + - "See https://docs.lacework.net/onboarding/category/aws-agentless-workload-scanning-integrations") - } - // if the flag was not set by the user and no agentless integrations exist - vulCmdState.CollectorType = vulnHostCollectorTypeAgent - } - } - - return nil - }, - RunE: func(c *cobra.Command, args []string) error { - if err := validateSeverityFlags(); err != nil { - return err - } - - var ( - assessment api.VulnerabilitiesHostResponse - cacheKey = fmt.Sprintf("host/assessment/v2/%s", args[0]) - ) - - expired := cli.ReadCachedAsset(cacheKey, &assessment) - if expired { - // check machine exists - var machinesResponse api.MachinesEntityResponse - filter := api.SearchFilter{Filters: []api.Filter{{ - Expression: "eq", - Field: "mid", - Value: args[0], - }}} - - cli.StartProgress(fmt.Sprintf("Searching for machine with id '%s'...", args[0])) - err := cli.LwApi.V2.Entities.Search(&machinesResponse, filter) - cli.StopProgress() - - if err != nil { - return errors.Wrapf(err, "unable to get machine details id %s", args[0]) - } - - if len(machinesResponse.Data) == 0 { - return errors.Errorf("no hosts found with id %s\n", args[0]) - } - - machineDetails := machinesResponse.Data[0] - - cli.StartProgress( - fmt.Sprintf("Searching for latest host evaluation for machine %s (%d)...", - machineDetails.Hostname, machineDetails.Mid, - )) - evalGUID, err := searchLatestHostEvaluationGuid(args[0]) - cli.StopProgress() - if err != nil { - return errors.Wrapf(err, "unable to find information of host '%s'", args[0]) - } - - cli.Log.Infow("latest assessment found", "eval_guid", evalGUID, "collector_type", vulCmdState.CollectorType) - - var ( - now = time.Now().UTC() - before = now.AddDate(0, 0, -7) // 7 days from ago - ) - - filter.TimeFilter = &api.TimeFilter{ - StartTime: &before, - EndTime: &now, - } - filter.Filters = append(filter.Filters, api.Filter{ - Expression: "eq", - Field: "evalGuid", - Value: evalGUID, - }) - - // filter by collector_type - filter.Filters = append(filter.Filters, api.Filter{ - Expression: "eq", - Field: "evalCtx.collector_type", - Value: vulCmdState.CollectorType, - }) - - cli.StartProgress( - fmt.Sprintf("Fetching vulnerabilities from host evaluation '%s' (collector_type: %s) ...", - evalGUID, vulCmdState.CollectorType), - ) - assessment, err = cli.LwApi.V2.Vulnerabilities.Hosts.SearchAllPages(filter) - if err != nil { - return errors.Wrapf(err, "unable to get host assessment with id %s", args[0]) - } - cli.StopProgress() - - cli.WriteAssetToCache(cacheKey, time.Now().Add(time.Hour*1), assessment) - } - - if err := buildVulnHostReports(assessment); err != nil { - return err - } - - if vulFailureFlagsEnabled() { - cli.Log.Infow("failure flags enabled", - "fail_on_severity", vulCmdState.FailOnSeverity, - "fail_on_fixable", vulCmdState.FailOnFixable, - ) - assessmentCounts := assessment.VulnerabilityCounts() - vulnPolicy := NewVulnerabilityPolicyError( - &assessmentCounts, - vulCmdState.FailOnSeverity, - vulCmdState.FailOnFixable, - ) - if vulnPolicy.NonCompliant() { - c.SilenceUsage = true - return vulnPolicy - } - } - return nil - }, - } -) - -// Build the cli output for vuln host show-assessment -func buildVulnHostReports(response api.VulnerabilitiesHostResponse) error { - // @afiune the UI today doesn't display any vulnerability that has been fixed - // but the APIs return them, this is causing confusion, to fix this issue we - // are filtering all of those "Fixed" vulnerabilities here - response.Data = removeFixedVulnerabilitiesFromAssessment(response.Data) - - hostVulnCounts := response.VulnerabilityCounts() - if hostVulnCounts.Total == 0 { - if cli.JSONOutput() { - return cli.OutputJSON(response.Data) - } - cli.OutputHuman("Great news! This host has no vulnerabilities... (time for %s)\n", randomEmoji()) - return nil - } - - var ( - mainReport = hostVulnHostDetailsMainReportTable(response) - filteredCves, filtered = filterHostCVEsTable(cvesSummary(response.Data)) - detailsReport = buildVulnHostsDetailsTable(filteredCves) - csvHeader, csvDetailsReport = buildVulnHostsDetailsTableCSV(filteredCves) - ) - - switch { - case cli.JSONOutput(): - return cli.OutputJSON(summaryToHostList(filteredCves)) - case cli.CSVOutput(): - return cli.OutputCSV(csvHeader, csvDetailsReport) - default: - cli.OutputHuman(mainReport) - cli.OutputHuman(detailsReport) - if filtered != "" { - cli.OutputHuman(filtered) - } - return nil - } -} - -func searchLatestHostEvaluationGuid(mid string) (string, error) { - var ( - now = time.Now().UTC() - before = now.AddDate(0, 0, -7) // 7 days from ago - filter = api.SearchFilter{ - TimeFilter: &api.TimeFilter{ - StartTime: &before, - EndTime: &now, - }, - Filters: []api.Filter{{ - Expression: "eq", - Field: "mid", - Value: mid, - }, - { - Expression: "eq", - Field: "evalCtx.collector_type", - Value: vulCmdState.CollectorType, - }, - }, - Returns: []string{"evalGuid", "startTime"}, - } - ) - - cli.Log.Infow("retrieve host evaluation information", "mid", mid) - response, err := cli.LwApi.V2.Vulnerabilities.Hosts.SearchAllPages(filter) - if err != nil { - return "", err - } - - if len(response.Data) == 0 { - cli.Log.Infow("no data found", "collector_type", vulCmdState.CollectorType) - if vulCmdState.CollectorType == vulnHostCollectorTypeAgentless { - vulCmdState.CollectorType = vulnHostCollectorTypeAgent - return searchLatestHostEvaluationGuid(mid) - } - - return "", errors.Errorf("no data found with %s collector\n", vulCmdState.CollectorType) - } - - return getUniqueHostEvalGUID(response), nil -} - -func getUniqueHostEvalGUID(host api.VulnerabilitiesHostResponse) string { - var ( - guid string - startTime time.Time - ) - for _, ctr := range host.Data { - if ctr.EvalGUID != guid { - if ctr.StartTime.After(startTime) { - startTime = ctr.StartTime - guid = ctr.EvalGUID - } - } - } - return guid -} - -func buildVulnHostsDetailsTableCSV(filteredCves map[string]VulnCveSummary) ([]string, [][]string) { - if !showPackages() { - return nil, nil - } - - if vulCmdState.Packages { - packages, _ := hostVulnPackagesTable(filteredCves, false) - return []string{"CVE Count", "Severity", "Package", "Current Version", "Fix Version", "Pkg Status"}, packages - } - - rows := hostVulnCVEsTableForHostViewCSV(filteredCves) - return []string{"CVE ID", "Severity", "Score", "Package", "Package Namespace", "Current Version", - "Fix Version", "Pkg Status", "First Seen", "Last Status Update", "Vuln Status"}, rows -} - -func buildVulnHostsDetailsTable(filteredCves map[string]VulnCveSummary) string { - mainBldr := &strings.Builder{} - - if showPackages() { - if vulCmdState.Packages { - packages, filtered := hostVulnPackagesTable(filteredCves, false) - // if the user wants to show only vulnerabilities of active packages - // and we don't have any, show a friendly message - if len(packages) == 0 { - mainBldr.WriteString(buildHostVulnCVEsToTableError()) - } else { - mainBldr.WriteString( - renderSimpleTable( - []string{"CVE Count", "Severity", "Package", "Current Version", "Fix Version", "Pkg Status"}, - packages, - ), - ) - if filtered != "" { - mainBldr.WriteString(filtered) - } - } - } else { - rows := hostVulnCVEsTableForHostView(filteredCves) - // if the user wants to show only vulnerabilities of active packages - // and we don't have any, show a friendly message - if len(rows) == 0 { - mainBldr.WriteString(buildHostVulnCVEsToTableError()) - } else { - mainBldr.WriteString(renderSimpleTable([]string{ - "CVE ID", "Severity", "CvssV2", "CvssV3", "Package", "Current Version", - "Fix Version", "Pkg Status", "Vuln Status"}, - rows, - )) - } - } - } - - if !vulCmdState.Details && !vulCmdState.Active && !vulCmdState.Fixable && !vulCmdState.Packages && - vulCmdState.Severity == "" && - vulCmdState.Cve == "" { - mainBldr.WriteString( - "\nTry adding '--details' to increase details shown about the vulnerability assessment.\n", - ) - } else if !vulCmdState.Active { - mainBldr.WriteString( - "\nTry adding '--active' to only show vulnerabilities of packages actively running.\n", - ) - } else if !vulCmdState.Fixable { - mainBldr.WriteString( - "\nTry adding '--fixable' to only show fixable vulnerabilities.\n", - ) - } - - return mainBldr.String() -} - -func hostVulnHostDetailsMainReportTable(assessment api.VulnerabilitiesHostResponse) string { - host := assessment.Data[0] - machineTags, err := host.GetMachineTags() - if err != nil { - cli.Log.Debug("failed to parse machine tags") - } - mainBldr := &strings.Builder{} - mainBldr.WriteString( - renderCustomTable([]string{"Host Details", "Vulnerabilities"}, - [][]string{{ - renderCustomTable([]string{}, - [][]string{ - {"Machine ID", strconv.Itoa(host.Mid)}, - {"Hostname", host.EvalCtx.Hostname}, - {"External IP", machineTags.ExternalIP}, - {"Internal IP", machineTags.InternalIP}, - {"Os", machineTags.Os}, - {"Arch", machineTags.Arch}, - {"Namespace", host.FeatureKey.Namespace}, - {"Provider", machineTags.VMProvider}, - {"Instance ID", machineTags.InstanceID}, - {"AMI", machineTags.AmiID}, - {"Collector Type", host.EvalCtx.CollectorType}, - }, - tableFunc(func(t *tablewriter.Table) { - t.SetColumnSeparator("") - t.SetBorder(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - - }), - ), - renderCustomTable( - []string{"Severity", "Count", "Fixable"}, - hostVulnAssessmentToCountsTable(assessment.VulnerabilityCounts()), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - }), - ), - }}, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - t.SetColumnSeparator(" ") - }), - ), - ) - - return mainBldr.String() -} - -func showPackages() bool { - return vulCmdState.Details || - vulCmdState.Fixable || - vulCmdState.Packages || - vulCmdState.Active || - vulCmdState.Severity != "" -} - -func hostVulnCVEsTableForHostViewCSV(cves map[string]VulnCveSummary) [][]string { - var ( - out [][]string - cveSlice []api.VulnerabilityHost - ) - - for _, cve := range cves { - cveSlice = append(cveSlice, cve.Host) - } - sortVulnHosts(cveSlice) - - for _, host := range cveSlice { - var ( - firstSeen string - lastUpdated string - ) - - if host.Props.FirstTimeSeen != nil { - firstSeen = host.Props.FirstTimeSeen.UTC().String() - } - - if host.Props.FirstTimeSeen != nil { - lastUpdated = host.Props.LastUpdatedTime.UTC().String() - } - - out = append(out, []string{ - host.VulnID, - host.Severity, - host.CvssV2(), - host.CvssV3(), - host.FeatureKey.Name, - host.FeatureKey.Namespace, - host.FeatureKey.VersionInstalled, - host.FixInfo.FixedVersion, - host.PackageActive(), - firstSeen, - lastUpdated, - host.PackageActive(), - }) - } - - // order by severity and package name - sort.Slice(out, func(i, j int) bool { - if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { - return out[i][4] < out[j][4] - } - return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) - }) - - return out -} - -func hostVulnCVEsTableForHostView(summary map[string]VulnCveSummary) [][]string { - var ( - out [][]string - cveSlice []api.VulnerabilityHost - ) - - for _, cve := range summary { - cveSlice = append(cveSlice, cve.Host) - } - sortVulnHosts(cveSlice) - - for _, host := range cveSlice { - out = append(out, []string{ - host.VulnID, - host.Severity, - host.CvssV2(), - host.CvssV3(), - host.FeatureKey.Name, - host.FeatureKey.VersionInstalled, - host.FixInfo.FixedVersion, - host.PackageActive(), - host.Status, - }) - } - - // order by severity and by package name - sort.Slice(out, func(i, j int) bool { - if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { - return api.SeverityOrder(out[i][4]) < api.SeverityOrder(out[j][4]) - } - return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) - }) - - return out -} - -func removeFixedVulnerabilitiesFromAssessment(assessment []api.VulnerabilityHost) []api.VulnerabilityHost { - var filteredCves []api.VulnerabilityHost - for _, cve := range assessment { - if cve.Status != "Fixed" { - filteredCves = append(filteredCves, cve) - } - } - return filteredCves -} - -func hostVulnPackagesTable(cves map[string]VulnCveSummary, withHosts bool) ([][]string, string) { - var ( - out [][]string - filteredPackages []packageTable - aggregatedPackages []packageTable - cveSlice []api.VulnerabilityHost - ) - - for _, cve := range cves { - cveSlice = append(cveSlice, cve.Host) - } - sortVulnHosts(cveSlice) - - for _, host := range cveSlice { - pack := packageTable{ - cveCount: 1, - severity: cases.Title(language.English).String(host.Severity), - packageName: host.FeatureKey.Name, - currentVersion: host.FeatureKey.VersionInstalled, - fixVersion: host.FixInfo.FixedVersion, - packageStatus: host.PackageActive(), - } - if withHosts { - pack.hostCount = 1 - } - - if vulCmdState.Active && host.PackageActive() == "" { - filteredPackages = aggregatePackagesWithHosts(filteredPackages, pack, withHosts, false) - continue - } - - if vulCmdState.Fixable && host.FixInfo.FixedVersion == "" { - filteredPackages = aggregatePackagesWithHosts(filteredPackages, pack, withHosts, false) - continue - } - - if vulCmdState.Severity != "" { - if filterSeverity(host.Severity, vulCmdState.Severity) { - filteredPackages = aggregatePackagesWithHosts(filteredPackages, pack, withHosts, false) - continue - } - } - // add all packages that have not been filtered - aggregatedPackages = aggregatePackagesWithHosts(aggregatedPackages, pack, withHosts, false) - } - - for _, p := range aggregatedPackages { - output := []string{ - strconv.Itoa(p.cveCount), - p.severity, - p.packageName, - p.currentVersion, - p.fixVersion, - p.packageStatus} - if p.hostCount > 0 { - output = append(output, strconv.Itoa(p.hostCount)) - } - out = append(out, output) - } - - // order by severity and by package name - sort.Slice(out, func(i, j int) bool { - if api.SeverityOrder(out[i][1]) == api.SeverityOrder(out[j][1]) { - return out[i][2] < out[j][2] - } - return api.SeverityOrder(out[i][1]) < api.SeverityOrder(out[j][1]) - }) - - if len(filteredPackages) > 0 { - filteredOutput := fmt.Sprintf( - "%d of %d package(s) showing\n", - len(out), len(aggregatedPackages)+len(filteredPackages), - ) - return out, filteredOutput - } - - return out, "" -} - -func sortVulnHosts(slice []api.VulnerabilityHost) { - for range slice { - sort.Slice(slice[:], func(i, j int) bool { - switch strings.Compare(slice[i].VulnID, slice[j].VulnID) { - case -1: - return true - case 1: - return false - default: - return false - } - }) - } -} - -func checkAgentlessCloudAccount() bool { - resp, err := cli.LwApi.V2.CloudAccounts.List() - if err != nil { - return false - } - for _, ca := range resp.Data { - switch ca.Type { - case api.AwsSidekickCloudAccount.String(), api.AwsSidekickOrgCloudAccount.String(), - api.GcpSidekickCloudAccount.String(): - return true - } - } - return false -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_html.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_html.go deleted file mode 100644 index d3a4c96d8..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vuln_html.go +++ /dev/null @@ -1,370 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "bytes" - "fmt" - "html/template" - "os" - "regexp" - "sort" - "strings" - "time" - - "github.com/pkg/errors" - "golang.org/x/text/cases" - "golang.org/x/text/language" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/databox" -) - -const ( - magicBarChartAdjustment float32 = 5 - magicTableInitialHeight int32 = 130 // width 1524px can we make it magic? - magicTableHeightMultiplier int32 = 40 -) - -// arrays can't be constants -var magicLayerInstructions = []string{"ADD"} - -// struct used to render the HTML assessment -type vulnImageAssessmentHtml struct { - Account string - ID string - Digest string - Repository string - CreatedTime string - Size string - Tags []string - - TotalVulnerabilities int32 - CriticalVulnerabilities int32 - HighVulnerabilities int32 - MediumVulnerabilities int32 - LowVulnerabilities int32 - InfoVulnerabilities int32 - FixableVulnerabilities int32 - - TableHeight int32 - Vulnerabilities []htmlVuln -} - -type htmlVuln struct { - RowHeight int32 - CVE string - Severity string - SeverityHTMLClass string - Layer string - PkgName string - PkgVersion string - PkgFixed string - V3Score float64 - UseV3Score bool - V2Score float64 - UseV2Score bool - UseNoScore bool -} - -func (a *vulnImageAssessmentHtml) CriticalVulnPercent() float32 { - if a.CriticalVulnerabilities == 0 { - return magicBarChartAdjustment - } - percent := (float32(a.CriticalVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 - if a.SeverityToBeAdjusted() == "critical" { - return percent - a.Adjustment() - } - return percent -} -func (a *vulnImageAssessmentHtml) HighVulnPercent() float32 { - if a.HighVulnerabilities == 0 { - return magicBarChartAdjustment - } - percent := (float32(a.HighVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 - if a.SeverityToBeAdjusted() == "high" { - return percent - a.Adjustment() - } - return percent -} -func (a *vulnImageAssessmentHtml) MediumVulnPercent() float32 { - if a.MediumVulnerabilities == 0 { - return magicBarChartAdjustment - } - percent := (float32(a.MediumVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 - if a.SeverityToBeAdjusted() == "medium" { - return percent - a.Adjustment() - } - return percent -} -func (a *vulnImageAssessmentHtml) LowVulnPercent() float32 { - if a.LowVulnerabilities == 0 { - return magicBarChartAdjustment - } - percent := (float32(a.LowVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 - if a.SeverityToBeAdjusted() == "low" { - return percent - a.Adjustment() - } - return percent -} -func (a *vulnImageAssessmentHtml) InfoVulnPercent() float32 { - if a.InfoVulnerabilities == 0 { - return magicBarChartAdjustment - } - percent := (float32(a.InfoVulnerabilities) / float32(a.TotalVulnerabilities)) * 100 - if a.SeverityToBeAdjusted() == "info" { - return percent - a.Adjustment() - } - return percent -} -func (a *vulnImageAssessmentHtml) Adjustment() float32 { - var x float32 - if a.CriticalVulnerabilities == 0 { - x += magicBarChartAdjustment - } - if a.HighVulnerabilities == 0 { - x += magicBarChartAdjustment - } - if a.MediumVulnerabilities == 0 { - x += magicBarChartAdjustment - } - if a.LowVulnerabilities == 0 { - x += magicBarChartAdjustment - } - if a.InfoVulnerabilities == 0 { - x += magicBarChartAdjustment - } - return x -} -func (a *vulnImageAssessmentHtml) SeverityToBeAdjusted() string { - severity := "critical" - highest := a.CriticalVulnerabilities - - if highest < a.HighVulnerabilities { - severity = "high" - highest = a.HighVulnerabilities - } - if highest < a.MediumVulnerabilities { - severity = "medium" - highest = a.MediumVulnerabilities - } - if highest < a.LowVulnerabilities { - severity = "low" - highest = a.LowVulnerabilities - } - if highest < a.InfoVulnerabilities { - severity = "info" - } - - return severity -} - -func calcHtmlBarChartWidth(severity string, htmlData vulnImageAssessmentHtml) string { - switch severity { - case "critical": - return fmt.Sprintf("%f%%", htmlData.CriticalVulnPercent()) - case "high": - return fmt.Sprintf("%f%%", htmlData.HighVulnPercent()) - case "medium": - return fmt.Sprintf("%f%%", htmlData.MediumVulnPercent()) - case "low": - return fmt.Sprintf("%f%%", htmlData.LowVulnPercent()) - case "info": - return fmt.Sprintf("%f%%", htmlData.InfoVulnPercent()) - default: - return "0" - } -} - -func calcHtmlBarChartX(severity string, htmlData vulnImageAssessmentHtml) string { - var x float32 - switch severity { - case "critical": - x = 0 - case "high": - x = htmlData.CriticalVulnPercent() - case "medium": - x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() - case "low": - x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + - htmlData.MediumVulnPercent() - case "info": - x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + - htmlData.MediumVulnPercent() + htmlData.LowVulnPercent() - } - return fmt.Sprintf("%f%%", x) -} - -func isLayerInstruction(inst string) bool { - for _, mInst := range magicLayerInstructions { - if mInst == inst { - return true - } - } - return false -} -func htmlLayerInstruction(layer string) string { - if len(layer) == 0 { - return "" - } - - words := strings.Split(layer, " ") - if isLayerInstruction(words[0]) { - return words[0] - } - - return "RUN" -} - -func htmlLayerPrint(layer string) string { - if len(layer) == 0 { - return "" - } - - words := strings.Split(layer, " ") - if isLayerInstruction(words[0]) { - return strings.Join(words[1:], " ") - } - - return layer -} - -func calcHtmlBarChartTextX(severity string, htmlData vulnImageAssessmentHtml) string { - var x float32 - switch severity { - case "critical": - x = htmlData.CriticalVulnPercent() / 2 - case "high": - x = htmlData.CriticalVulnPercent() + (htmlData.HighVulnPercent() / 2) - case "medium": - x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + - (htmlData.MediumVulnPercent() / 2) - case "low": - x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + - htmlData.MediumVulnPercent() + (htmlData.LowVulnPercent() / 2) - case "info": - x = htmlData.CriticalVulnPercent() + htmlData.HighVulnPercent() + - htmlData.MediumVulnPercent() + htmlData.LowVulnPercent() + (htmlData.InfoVulnPercent() / 2) - } - return fmt.Sprintf("%f%%", x) -} - -func generateVulnAssessmentHTML(response api.VulnerabilitiesContainersResponse) error { - assessment := response.Data - htmlTemplate, ok := databox.Get("vuln_assessment.html") - if !ok { - return errors.New( - "html template not found, this is most likely a mistake on us, please report it to support.lacework.com.", - ) - } - - headerInfo := assessment[0] - - var ( - buff = &bytes.Buffer{} - funcMap = template.FuncMap{ - "calcBarChartWidth": calcHtmlBarChartWidth, - "calcBarChartX": calcHtmlBarChartX, - "calcBarChartTextX": calcHtmlBarChartTextX, - "layerInstruction": htmlLayerInstruction, - "layerPrint": htmlLayerPrint, - } - tmpl = template.Must(template.New("vuln_assessment").Funcs(funcMap).Parse(string(htmlTemplate))) - htmlData = vulnImageAssessmentHtml{ - Account: cli.Account, - Repository: headerInfo.EvalCtx.ImageInfo.Repo, - ID: headerInfo.EvalCtx.ImageInfo.ID, - Digest: headerInfo.EvalCtx.ImageInfo.Digest, - CreatedTime: time.UnixMilli(headerInfo.EvalCtx.ImageInfo.CreatedTime).Format(time.RFC3339), - Size: byteCountBinary(headerInfo.EvalCtx.ImageInfo.Size), - Tags: headerInfo.EvalCtx.ImageInfo.Tags, - - TotalVulnerabilities: int32(response.TotalVulnerabilities()), - CriticalVulnerabilities: response.CriticalVulnerabilities(), - HighVulnerabilities: response.HighVulnerabilities(), - MediumVulnerabilities: response.MediumVulnerabilities(), - LowVulnerabilities: response.LowVulnerabilities(), - InfoVulnerabilities: response.InfoVulnerabilities(), - FixableVulnerabilities: response.TotalFixableVulnerabilities(), - - TableHeight: magicTableInitialHeight + (int32(response.TotalVulnerabilities()) * magicTableHeightMultiplier), - Vulnerabilities: vulContainerImageLayersToHTML(assessment), - } - outputHTML = fmt.Sprintf("%s-%s.html", - strings.ReplaceAll(htmlData.Repository, "/", "-"), - htmlData.Digest) - ) - - if err := tmpl.Execute(buff, htmlData); err != nil { - return errors.Wrap(err, "unable to execute template") - } - - if err := os.WriteFile(outputHTML, buff.Bytes(), os.ModePerm); err != nil { - return errors.Wrap(err, "unable to write html file") - } - - cli.OutputHuman("The container vulnerability assessment was stored at '%s'\n", outputHTML) - return nil -} - -func vulContainerImageLayersToHTML(image []api.VulnerabilityContainer) []htmlVuln { - var vulns = []htmlVuln{} - for _, i := range image { - space := regexp.MustCompile(`\s+`) - layerCreatedBy := space.ReplaceAllString(i.FeatureProps.IntroducedIn, " ") - - newHtmlVuln := htmlVuln{ - CVE: i.VulnID, - Severity: cases.Title(language.English).String(i.Severity), - SeverityHTMLClass: strings.ToLower(i.Severity), - PkgName: i.FeatureKey.Name, - PkgVersion: i.FeatureKey.Version, - PkgFixed: i.FixInfo.FixedVersion, - Layer: layerCreatedBy, - } - - // Todo(v2): CVSSv3Score does not exist in v2 container response - // if score := vul.CVSSv3Score(); score != 0 { - // // CVSSv3 - // newHtmlVuln.V3Score = score - // newHtmlVuln.UseV3Score = true - // } else if score = vul.CVSSv2Score(); score != 0 { - // // CVSSv2 - // newHtmlVuln.V2Score = score - // newHtmlVuln.UseV2Score = true - // } else { - // // N/A - newHtmlVuln.UseNoScore = true - // } - - vulns = append(vulns, newHtmlVuln) - } - - // order by severity - sort.Slice(vulns, func(i, j int) bool { - return api.SeverityOrder(vulns[i].Severity) < api.SeverityOrder(vulns[j].Severity) - }) - - // add the row height after ordering - for row := range vulns { - vulns[row].RowHeight = (int32(row) * magicTableHeightMultiplier) + magicTableHeightMultiplier - } - - return vulns -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability.go deleted file mode 100644 index fb3117196..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability.go +++ /dev/null @@ -1,362 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "strconv" - "strings" - - "github.com/lacework/go-sdk/lwseverity" - "github.com/pkg/errors" - "github.com/spf13/cobra" - flag "github.com/spf13/pflag" -) - -var ( - vulCmdState = struct { - // enable poll mechanism for scans status - Poll bool - - // store the vulnerability assessment in HTML format on disk - Html bool - - // output vulnerability assessment in CSV format - Csv bool - - // DEPRECATED - // when enabled we tread the provided sha256 hash as image id - ImageID bool - - // display extended details about a vulnerability assessment - Details bool - - // Filter the vulnerability assessment table by severity - Severity string - - // Exit the CLI application with an error given a specified severity is met - FailOnSeverity string - - // Exit the CLI application with an error given fixable vulnerabilities - FailOnFixable bool - - // display only fixable vulnerabilities - Fixable bool - - // show a list of packages by number of CVEs - Packages bool - - // start time for listing assessments - Start string - - // end time for listing assessments - End string - - // natural time range for listing assessments - Range string - - // active flag to filter container vulnerability assessments and - // show only assessments of containers actively running - Active bool - - // show only hosts that are online - Online bool - - // show only hosts that are offline - Offline bool - - // filter assessments for specific repositories - Repositories []string - - // filter assessments for specific registries - Registries []string - - // filter assessments by a single CVE - Cve string - - // filter assessments by collector type(Agent/Agentless) - CollectorType string - }{ - // Default collector_type is agentless - CollectorType: vulnHostCollectorTypeAgentless} - - // vulnerability represents the vulnerability command that holds both, the host - // and container sub-commands - vulnerabilityCmd = &cobra.Command{ - Use: "vulnerability", - Aliases: []string{"vuln", "vul"}, - Short: "Container and host vulnerability assessments", - Long: "Container and host vulnerability assessments.", - } - - // vulContainerCmd represents the vulnerability container command - vulContainerCmd = &cobra.Command{ - Use: "container", - Aliases: []string{"ctr"}, - Short: "Vulnerability assessment for containers", - Long: `Request on-demand container vulnerability scans and show previous assessments -from published images. - -**PREREQUISITE:** Your Lacework account should already be configured -with a Container Registry Integration of the container images you are -trying to scan or show. - -To create a new integration use the following command: - - lacework container-registry create - -If you prefer to configure the integration via the WebUI, log in to your account at: - - https://.lacework.net - -Then navigate to Settings > Integrations > Container Registry.`, - } - - // vulHostCmd represents the vulnerability host command - vulHostCmd = &cobra.Command{ - Use: "host", - Short: "Vulnerability assessment for hosts", - Long: `Request on-demand host vulnerability scans and show previous assessments -from hosts with the Lacework datacollector agent installed. -`, - } -) - -const ( - vulnHostCollectorTypeAgent string = "Agent" - vulnHostCollectorTypeAgentless string = "Agentless" -) - -func init() { - // add the vulnerability command - rootCmd.AddCommand(vulnerabilityCmd) - - // add sub-commands to the vulnerability command - vulnerabilityCmd.AddCommand(vulContainerCmd) - vulnerabilityCmd.AddCommand(vulHostCmd) -} - -func setPackagesFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.BoolVar(&vulCmdState.Packages, "packages", false, - "show a list of packages with CVE count", - ) - } - } -} - -func setFixableFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.BoolVar(&vulCmdState.Fixable, "fixable", false, - "only show fixable vulnerabilities", - ) - } - } -} - -func setHtmlFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.BoolVar(&vulCmdState.Html, "html", false, - "generate a vulnerability assessment in HTML format", - ) - } - } -} - -func setCsvFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.BoolVar(&vulCmdState.Csv, "csv", false, - "output vulnerability assessment in CSV format", - ) - } - } -} - -func setDetailsFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.BoolVar(&vulCmdState.Details, "details", false, - "increase details of a vulnerability assessment", - ) - } - } -} - -func setFailOnSeverityFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.StringVar(&vulCmdState.FailOnSeverity, "fail_on_severity", "", - fmt.Sprintf("specify a severity threshold to fail if vulnerabilities are found (%s)", - lwseverity.ValidSeverities.String()), - ) - } - } -} - -func setFailOnFixableFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.BoolVar(&vulCmdState.FailOnFixable, "fail_on_fixable", false, - "fail if the assessed container has fixable vulnerabilities", - ) - } - } -} - -func setSeverityFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.StringVar(&vulCmdState.Severity, "severity", "", - fmt.Sprintf("filter vulnerability assessment by severity threshold (%s)", - lwseverity.ValidSeverities.String()), - ) - } - } -} - -func setActiveFlag(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - cmd.BoolVar(&vulCmdState.Active, "active", false, - "only show vulnerabilities of packages actively running in your environment", - ) - } - } -} - -func setTimeRangeFlags(cmds ...*flag.FlagSet) { - for _, cmd := range cmds { - if cmd != nil { - - cmd.StringVar(&vulCmdState.Start, - "start", "-24h", "start of the time range", - ) - cmd.StringVar(&vulCmdState.End, - "end", "now", "end of the time range", - ) - cmd.StringVar(&vulCmdState.Range, - "range", "", "natural time range for query", - ) - } - } -} - -func buildVulnContainerAssessmentReportTable(summary string, details string) string { - report := &strings.Builder{} - - report.WriteString(summary) - if vulCmdState.Details || vulCmdState.Packages || vulFiltersEnabled() { - report.WriteString(details) - } else { - if !vulCmdState.Html { - report.WriteString( - "Try adding '--details' to increase details shown about the vulnerability assessment.\n", - ) - } - } - - return report.String() -} - -func byteCountBinary(b int) string { - const unit = 1024 - if b < unit { - return fmt.Sprintf("%d B", b) - } - div, exp := int64(unit), 0 - for n := b / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp]) -} - -func stringToInt(s string) int { - i, err := strconv.Atoi(s) - if err != nil { - cli.Log.Debugw("unable to convert string to int", - "string", s, "error", err.Error(), "func", "stringToInt", - ) - return 0 - } - return i -} - -func validateSeverityFlags() error { - if vulCmdState.Severity != "" { - if !lwseverity.IsValid(vulCmdState.Severity) { - return errors.Errorf("the severity %s is not valid, use one of %s", - vulCmdState.Severity, lwseverity.ValidSeverities.String(), - ) - } - } - - if vulCmdState.FailOnSeverity != "" { - if !lwseverity.IsValid(vulCmdState.FailOnSeverity) { - return errors.Errorf("the severity %s is not valid, use one of %s", - vulCmdState.FailOnSeverity, lwseverity.ValidSeverities.String(), - ) - } - } - - return nil -} - -func vulFailureFlagsEnabled() bool { - return vulCmdState.FailOnSeverity != "" || vulCmdState.FailOnFixable -} - -func vulFiltersEnabled() bool { - return vulCmdState.Severity != "" || vulCmdState.Fixable -} - -func filterSeverity(severity string, threshold string) bool { - thresholdValue, _ := lwseverity.Normalize(threshold) - severityValue, _ := lwseverity.Normalize(severity) - return severityValue > thresholdValue -} - -// Used to store data for --package output vuln ctr/host -type packageTable struct { - cveCount int - severity string - packageName string - packageNamespace string - currentVersion string - fixVersion string - packageStatus string - hostCount int - fixes int -} - -type filteredPackageTable struct { - packages []packageTable - totalPackages int - totalUnfiltered int -} - -func (pt *packageTable) equals(p packageTable) bool { - return pt.packageName == p.packageName && - pt.packageNamespace == p.packageNamespace && - pt.currentVersion == p.currentVersion -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_container.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_container.go deleted file mode 100644 index 46e692137..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_container.go +++ /dev/null @@ -1,188 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createContainerVulnerabilityException() (string, error) { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "description", - Prompt: &survey.Input{Message: "Description: "}, - Validate: survey.Required, - }, - { - Name: "reason", - Prompt: &survey.Select{ - Message: "Exception Reason: ", - Options: []string{"Accepted Risk", "False Positive", "Compensating Controls", "Fix Pending", "Other"}, - }, - Validate: survey.Required, - }, - { - Name: "includeCriteria", - Prompt: &survey.MultiSelect{ - Message: "Select Vulnerability Criteria to set: ", - Options: []string{"CVEs", "Packages", "Severities", "Fixable"}}, - Validate: survey.MinItems(1), - }, - } - - answers := struct { - Name string - Description string `survey:"description"` - Reason string `survey:"reason"` - Severities []string `survey:"severities"` - Cves string `survey:"cves"` - Packages string `survey:"packages"` - Fixable bool `survey:"fixable"` - ImageID string `survey:"imageId"` - ImageTag string `survey:"imageTag"` - Registry string `survey:"registry"` - Repository string `survey:"repository"` - Namespace string `survey:"namespace"` - IncludeCriteria []string `survey:"includeCriteria"` - }{} - - err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)) - if err != nil { - return "", err - } - - err = askVulnerabilityExceptionCriteria(&answers, answers.IncludeCriteria) - if err != nil { - return "", err - } - - fixable := answers.Fixable - vulnExCfg := api.VulnerabilityExceptionConfig{ - Description: answers.Description, - Type: api.VulnerabilityExceptionTypeContainer, - ExceptionReason: api.NewVulnerabilityExceptionReason(answers.Reason), - Severities: api.NewVulnerabilityExceptionSeverities(answers.Severities), - Cve: strings.Split(answers.Cves, "\n"), - Package: transformVulnerabilityExceptionPackages(answers.Packages), - Fixable: &fixable, - } - - // ask the user if they would like to configure a Resource Scope, or many - scope := false - err = survey.AskOne(&survey.Confirm{ - Message: "Configure one or more Resource Scope(s)?", - }, &scope) - if err != nil { - return "", err - } - - if scope { - err = askVulnerabilityExceptionContainerResourceScope(&answers) - if err != nil { - return "", err - } - vulnExCfg.ResourceScope = api.VulnerabilityExceptionContainerResourceScope{ - ImageID: strings.Split(answers.ImageID, "\n"), - ImageTag: strings.Split(answers.ImageTag, "\n"), - Registry: strings.Split(answers.Registry, "\n"), - Repository: strings.Split(answers.Repository, "\n"), - Namespace: strings.Split(answers.Namespace, "\n"), - } - } - - vuln := api.NewVulnerabilityException(answers.Name, vulnExCfg) - - cli.StartProgress("Creating container vulnerability exception...") - vulnResp, err := cli.LwApi.V2.VulnerabilityExceptions.CreateVulnerabilityExceptionsHost(vuln) - cli.StopProgress() - return vulnResp.Data.Guid, err -} - -func askVulnerabilityExceptionContainerResourceScope(answers interface{}) error { - criteria := struct { - Scope []string `survey:"scope"` - }{} - err := survey.Ask([]*survey.Question{ - { - Name: "scope", - Prompt: &survey.MultiSelect{ - Message: "Select Resource Scope criteria to set: ", - Options: []string{"Namespaces", "Image IDs", "Image Tags", "Registries", "Repositories"}}, - Validate: survey.MinItems(1), - }, - }, &criteria, survey.WithIcons(promptIconsFunc)) - if err != nil { - return err - } - - var questions []*survey.Question - for _, c := range criteria.Scope { - if c == "Image IDs" { - questions = append(questions, - &survey.Question{Name: "imageId", - Prompt: &survey.Multiline{Message: "List of Image IDs:"}, - }) - continue - } - - if c == "Image Tags" { - questions = append(questions, - &survey.Question{Name: "imageTag", - Prompt: &survey.Multiline{Message: "List of Image Tags:"}, - }) - continue - } - - if c == "Registries" { - questions = append(questions, - &survey.Question{Name: "registry", - Prompt: &survey.Multiline{Message: "List of Registries:"}}) - continue - } - - if c == "Repositories" { - questions = append(questions, - &survey.Question{ - Name: "repository", - Prompt: &survey.Multiline{Message: "List of Repositories:"}, - }) - continue - } - - if c == "Namespaces" { - questions = append(questions, - &survey.Question{ - Name: "namespace", - Prompt: &survey.Multiline{Message: "List of Namespaces:"}, - }) - continue - } - } - - return survey.Ask(questions, answers, survey.WithIcons(promptIconsFunc)) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_host.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_host.go deleted file mode 100644 index 4011fbc5c..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerability_exception_host.go +++ /dev/null @@ -1,177 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "strings" - - "github.com/AlecAivazis/survey/v2" - - "github.com/lacework/go-sdk/api" -) - -func createHostVulnerabilityException() (string, error) { - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name: "}, - Validate: survey.Required, - }, - { - Name: "description", - Prompt: &survey.Input{Message: "Description: "}, - Validate: survey.Required, - }, - { - Name: "reason", - Prompt: &survey.Select{ - Message: "Exception Reason: ", - Options: []string{"Accepted Risk", "False Positive", "Compensating Controls", "Fix Pending", "Other"}, - }, - Validate: survey.Required, - }, - { - Name: "includeCriteria", - Prompt: &survey.MultiSelect{ - Message: "Select Vulnerability Criteria to set: ", - Options: []string{"CVEs", "Packages", "Severities", "Fixable"}}, - Validate: survey.MinItems(1), - }, - } - - answers := struct { - Name string - Description string `survey:"description"` - Reason string `survey:"reason"` - Severities []string `survey:"severities"` - Cves string `survey:"cves"` - Packages string `survey:"packages"` - Fixable bool `survey:"fixable"` - Hostname string `survey:"hostname"` - ExternalIP string `survey:"externalIp"` - ClusterName string `survey:"clusterName"` - Namespace string `survey:"namespace"` - IncludeCriteria []string `survey:"includeCriteria"` - }{} - - err := survey.Ask(questions, &answers, survey.WithIcons(promptIconsFunc)) - if err != nil { - return "", err - } - - err = askVulnerabilityExceptionCriteria(&answers, answers.IncludeCriteria) - if err != nil { - return "", err - } - - fixable := answers.Fixable - vulnExCfg := api.VulnerabilityExceptionConfig{ - Description: answers.Description, - Type: api.VulnerabilityExceptionTypeHost, - ExceptionReason: api.NewVulnerabilityExceptionReason(answers.Reason), - Severities: api.NewVulnerabilityExceptionSeverities(answers.Severities), - Cve: strings.Split(answers.Cves, "\n"), - Package: transformVulnerabilityExceptionPackages(answers.Packages), - Fixable: &fixable, - } - - // ask the user if they would like to configure a Resource Scope, or many - scope := false - err = survey.AskOne(&survey.Confirm{ - Message: "Configure one or more Resource Scope(s)?", - }, &scope) - if err != nil { - return "", err - } - - if scope { - err = askVulnerabilityExceptionHostResourceScope(&answers) - if err != nil { - return "", err - } - vulnExCfg.ResourceScope = api.VulnerabilityExceptionHostResourceScope{ - Hostname: strings.Split(answers.Hostname, "\n"), - ExternalIP: strings.Split(answers.ExternalIP, "\n"), - ClusterName: strings.Split(answers.ClusterName, "\n"), - Namespace: strings.Split(answers.Namespace, "\n"), - } - } - - vuln := api.NewVulnerabilityException(answers.Name, vulnExCfg) - - cli.StartProgress("Creating host vulnerability exception...") - vulnResp, err := cli.LwApi.V2.VulnerabilityExceptions.CreateVulnerabilityExceptionsHost(vuln) - cli.StopProgress() - return vulnResp.Data.Guid, err -} - -func askVulnerabilityExceptionHostResourceScope(answers interface{}) error { - criteria := struct { - Scope []string `survey:"scope"` - }{} - err := survey.Ask([]*survey.Question{ - { - Name: "scope", - Prompt: &survey.MultiSelect{ - Message: "Select Resource Scope criteria to set: ", - Options: []string{"Namespaces", "Hostnames", "External IPs", "Cluster Names"}}, - Validate: survey.MinItems(1), - }, - }, &criteria, survey.WithIcons(promptIconsFunc)) - if err != nil { - return err - } - - var questions []*survey.Question - for _, c := range criteria.Scope { - if c == "Namespaces" { - questions = append(questions, - &survey.Question{Name: "namespace", - Prompt: &survey.Multiline{Message: "List of Namespaces:"}, - }) - continue - } - - if c == "Hostnames" { - questions = append(questions, - &survey.Question{Name: "hostname", - Prompt: &survey.Multiline{Message: "List of Hostnames:"}, - }) - continue - } - - if c == "External IPs" { - questions = append(questions, - &survey.Question{Name: "externalIp", - Prompt: &survey.Multiline{Message: "List of External IPs:"}}) - continue - } - - if c == "Cluster Names" { - questions = append(questions, - &survey.Question{ - Name: "clusterName", - Prompt: &survey.Multiline{Message: "List of ClusterNames:"}, - }) - continue - } - } - - return survey.Ask(questions, answers, survey.WithIcons(promptIconsFunc)) -} diff --git a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerabilty_exceptions.go b/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerabilty_exceptions.go deleted file mode 100644 index 5ac4a8b1f..000000000 --- a/vendor/github.com/lacework/go-sdk/cli/cmd/vulnerabilty_exceptions.go +++ /dev/null @@ -1,391 +0,0 @@ -// -// Author:: Darren Murray() -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cmd - -import ( - "fmt" - "regexp" - "strconv" - "strings" - - "github.com/AlecAivazis/survey/v2" - "github.com/AlecAivazis/survey/v2/core" - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/lwseverity" - "github.com/olekukonko/tablewriter" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -const CveRegex = `(?i)CVE-\d{4}-\d{4,7}` -const AlasRegex = `(?i)ALAS(2?)-\d{4}-\d{3,7}` - -var ( - // vulnerability-exceptions command is used to manage lacework vulnerability exceptions - vulnerabilityExceptionCommand = &cobra.Command{ - Use: "vulnerability-exception", - Aliases: []string{"vulnerability-exceptions", "ve", "vuln-exception", "vuln-exceptions"}, - Short: "Manage vulnerability exceptions", - Long: "Manage vulnerability exceptions to control and customize your alert profile for hosts and containers.", - } - - // list command is used to list all lacework vulnerability exceptions - vulnerabilityExceptionListCommand = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all vulnerability exceptions", - Long: "List all vulnerability exceptions configured in your Lacework account.", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - vulnerabilityExceptions, err := cli.LwApi.V2.VulnerabilityExceptions.List() - if err != nil { - return errors.Wrap(err, "unable to get vulnerability exceptions") - } - if len(vulnerabilityExceptions.Data) == 0 { - msg := `There are no vulnerability exceptions configured in your account. - -Get started by integrating your vulnerability exceptions to manage alerting using the command: - - lacework vulnerability-exception create - -If you prefer to configure vulnerability exceptions via the WebUI, log in to your account at: - - https://%s.lacework.net - -Then navigate to Vulnerabilities > Exceptions. -` - cli.OutputHuman(fmt.Sprintf(msg, cli.Account)) - return nil - } - - if cli.JSONOutput() { - return cli.OutputJSON(vulnerabilityExceptions) - } - - var rows [][]string - for _, vuln := range vulnerabilityExceptions.Data { - rows = append(rows, []string{vuln.Guid, vuln.ExceptionName, vuln.ExceptionType, vuln.Status()}) - } - - cli.OutputHuman(renderCustomTable([]string{"GUID", "NAME", "TYPE", "STATE"}, rows, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }))) - return nil - }, - } - // show command is used to retrieve a lacework vulnerability exception by id - vulnerabilityExceptionShowCommand = &cobra.Command{ - Use: "show ", - Short: "Get vulnerability exception by ID", - Long: "Get a single vulnerability exception by it's vulnerability exception ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - var response api.VulnerabilityExceptionResponse - err := cli.LwApi.V2.VulnerabilityExceptions.Get(args[0], &response) - if err != nil { - return errors.Wrap(err, "unable to get vulnerability exception") - } - vuln := response.Data - - if cli.JSONOutput() { - return cli.OutputJSON(vuln) - } - - var groupCommon [][]string - groupCommon = append(groupCommon, []string{vuln.Guid, vuln.ExceptionName, vuln.ExceptionType, vuln.Status()}) - - cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "TYPE", "STATUS"}, groupCommon)) - cli.OutputHuman("\n") - cli.OutputHuman(buildVulnerabilityExceptionsPropsTable(vuln)) - return nil - }, - } - - // delete command is used to remove a lacework vulnerability exception by id - vulnerabilityExceptionDeleteCommand = &cobra.Command{ - Use: "delete ", - Short: "Delete a vulnerability exception", - Long: "Delete a single vulnerability exception by it's vulnerability exception ID.", - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - err := cli.LwApi.V2.VulnerabilityExceptions.Delete(args[0]) - if err != nil { - return errors.Wrap(err, "unable to delete vulnerability exception") - } - return nil - }, - } - - // create command is used to create a new lacework vulnerability exception - vulnerabilityExceptionCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a new vulnerability exception", - Long: "Creates a new single vulnerability exception.", - RunE: func(_ *cobra.Command, args []string) error { - if !cli.InteractiveMode() { - return errors.New("interactive mode is disabled") - } - - vulnID, err := promptCreateVulnerabilityException() - if err != nil { - return errors.Wrap(err, "unable to create vulnerability exception") - } - - cli.OutputHuman("The vulnerability exception created with GUID %s.\n", vulnID) - return nil - }, - } -) - -func init() { - // add the vulnerability-exception command - rootCmd.AddCommand(vulnerabilityExceptionCommand) - - // add sub-commands to the vulnerability-exception command - vulnerabilityExceptionCommand.AddCommand(vulnerabilityExceptionListCommand) - vulnerabilityExceptionCommand.AddCommand(vulnerabilityExceptionShowCommand) - vulnerabilityExceptionCommand.AddCommand(vulnerabilityExceptionCreateCommand) - vulnerabilityExceptionCommand.AddCommand(vulnerabilityExceptionDeleteCommand) -} - -func buildVulnerabilityExceptionsPropsTable(vuln api.VulnerabilityException) string { - var sb strings.Builder - props := setProps(vuln) - - sb.WriteString(renderOneLineCustomTable("VULNERABILITY EXCEPTION PROPS", - renderCustomTable([]string{}, props, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - t.SetAlignment(tablewriter.ALIGN_LEFT) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetAutoWrapText(false) - }), - )) - if vuln.VulnerabilityCriteria.Package != nil { - sb.WriteString(buildPackagesTable(vuln.VulnerabilityCriteria.Package)) - } - return sb.String() -} - -func buildPackagesTable(packages []map[string][]string) string { - var ( - details [][]string - ) - for _, p := range packages { - for k, v := range p { - details = append(details, []string{k, strings.Join(v, ", ")}) - } - } - - return renderOneLineCustomTable("PACKAGES", - renderCustomTable( - []string{"NAME", "VERSIONS"}, - details, - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - }), - ), - tableFunc(func(t *tablewriter.Table) { - t.SetBorder(false) - t.SetColumnSeparator(" ") - t.SetAutoWrapText(false) - }), - ) -} - -func setProps(vuln api.VulnerabilityException) [][]string { - var details [][]string - details = append(details, []string{"DESCRIPTION", vuln.Props.Description}) - details = append(details, []string{"UPDATED BY", vuln.Props.UpdatedBy}) - details = append(details, []string{"LAST UPDATED", vuln.UpdatedTime}) - details = append(details, []string{"CREATED", vuln.CreatedTime}) - details = append(details, []string{"REASON", vuln.ExceptionReason}) - - if vuln.ResourceScope != nil { - if vuln.ExceptionType == api.VulnerabilityExceptionTypeHost.String() { - details = append(details, []string{"NAMESPACES", strings.Join(vuln.ResourceScope.Namespace, ", ")}) - details = append(details, []string{"HOSTNAMES", strings.Join(vuln.ResourceScope.Hostname, ", ")}) - details = append(details, []string{"EXTERNAL IPS", strings.Join(vuln.ResourceScope.ExternalIP, ",")}) - details = append(details, []string{"CLUSTER NAMES", strings.Join(vuln.ResourceScope.ClusterName, ", ")}) - } else if vuln.ExceptionType == api.VulnerabilityExceptionTypeContainer.String() { - details = append(details, []string{"NAMESPACES", strings.Join(vuln.ResourceScope.Namespace, ", ")}) - details = append(details, []string{"IMAGE IDS", strings.Join(vuln.ResourceScope.ImageID, ", ")}) - details = append(details, []string{"IMAGE TAGS", strings.Join(vuln.ResourceScope.ImageTag, ", ")}) - details = append(details, []string{"REGISTRIES", strings.Join(vuln.ResourceScope.Registry, ", ")}) - details = append(details, []string{"REPOSITORIES", strings.Join(vuln.ResourceScope.Repository, ", ")}) - } - } - - details = append(details, []string{"FIXABLE", - vulnerabilityExceptionFixableEnabled(vuln.VulnerabilityCriteria.Fixable)}) - details = append(details, []string{"CVES", strings.Join(vuln.VulnerabilityCriteria.Cve, ", ")}) - details = append(details, []string{"SEVERITIES", strings.Join(vuln.VulnerabilityCriteria.Severity, ", ")}) - - return details -} - -func promptCreateVulnerabilityException() (string, error) { - var ( - group = "" - prompt = &survey.Select{ - Message: "Choose a vulnerability exception type to create: ", - Options: []string{ - "Host", - "Container", - }, - } - err = survey.AskOne(prompt, &group) - ) - if err != nil { - return "", err - } - - switch group { - case "Host": - return createHostVulnerabilityException() - case "Container": - return createContainerVulnerabilityException() - default: - return "", errors.New("unknown vulnerability exception type") - } -} - -func vulnerabilityExceptionFixableEnabled(fixable []int) string { - if len(fixable) == 0 { - return "false" - } - return strconv.FormatBool(fixable[0] == 1) -} - -func askVulnerabilityExceptionCriteria(answers interface{}, criteria []string) error { - var questions []*survey.Question - for _, c := range criteria { - if c == "CVEs" { - questions = append(questions, - &survey.Question{ - Name: "cves", - Prompt: &survey.Multiline{Message: "List of CVE IDs:"}, - Validate: validateCveFormat(), - }) - continue - } - - if c == "Severities" { - questions = append(questions, - &survey.Question{ - Name: "severities", - Prompt: &survey.MultiSelect{ - Message: "Select severities:", - Options: []string{"Critical", "High", "Medium", "Low", "Info"}, - }, - Validate: validateSeverities(), - }) - continue - } - if c == "Packages" { - questions = append(questions, - &survey.Question{ - Name: "packages", - Prompt: &survey.Multiline{Message: "List of 'package:version' packages to include:"}, - }) - continue - } - - if c == "Packages" { - questions = append(questions, - &survey.Question{ - Name: "fixable", - Prompt: &survey.Confirm{Message: "Include Fixable:"}, - }) - continue - } - } - - err := survey.Ask(questions, answers, survey.WithIcons(promptIconsFunc)) - if err != nil { - return err - } - return nil -} - -func transformVulnerabilityExceptionPackages(packages string) []api.VulnerabilityExceptionPackage { - if packages == "" { - return []api.VulnerabilityExceptionPackage{} - } - var vulnPackages []api.VulnerabilityExceptionPackage - packageList := strings.Split(packages, "\n") - for _, pack := range packageList { - vulnPackage := strings.Split(pack, ":") - vulnPackages = append(vulnPackages, - api.VulnerabilityExceptionPackage{Name: vulnPackage[0], Version: vulnPackage[1]}, - ) - } - return vulnPackages -} - -func validateCveFormat() survey.Validator { - return func(val interface{}) error { - cveRegEx, _ := regexp.Compile(CveRegex) - alasRegEx, _ := regexp.Compile(AlasRegex) - if list, ok := val.([]core.OptionAnswer); ok { - for _, i := range list { - if !cveRegEx.MatchString(i.Value) && !alasRegEx.MatchString(i.Value) { - return fmt.Errorf("CVE format is invalid. Please format corretly eg: CVE-2014-0001, ALAS2-2022-1788") - } - } - } else { - value := val.(string) - if !cveRegEx.MatchString(value) && !alasRegEx.MatchString(value) { - return fmt.Errorf("CVE format is invalid. Please format corretly eg: CVE-2014-0001, ALAS2-2022-1788") - } - } - return nil - } -} - -func validateSeverities() survey.Validator { - return func(val interface{}) error { - if list, ok := val.([]core.OptionAnswer); ok { - for _, i := range list { - match := strings.Contains(lwseverity.ValidSeverities.String(), strings.ToLower(i.Value)) - if !match { - return fmt.Errorf( - "severity '%s' is invalid. Must be one of 'Critical', 'High', 'Medium', 'Low', 'Info'", i.Value, - ) - } - } - } else { - value := val.(core.OptionAnswer).Value - match := strings.Contains(lwseverity.ValidSeverities.String(), strings.ToLower(value)) - if !match { - return fmt.Errorf("severity '%s' is invalid. Must be one of 'Critical', 'High', 'Medium', 'Low', 'Info'", value) - } - } - return nil - } -} diff --git a/vendor/github.com/lacework/go-sdk/internal/archive/detect.go b/vendor/github.com/lacework/go-sdk/internal/archive/detect.go deleted file mode 100644 index e60c3b56c..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/archive/detect.go +++ /dev/null @@ -1,89 +0,0 @@ -package archive - -import ( - "os" - "path/filepath" - "strings" - - "github.com/gabriel-vasile/mimetype" -) - -func detectFileType(file string) (mimeType string, err error) { - - fDescriptor, err := os.Open(file) - if err != nil { - return - } - defer fDescriptor.Close() - // We only have to pass the file header = first 261 bytes - head := make([]byte, 261) - - _, err = fDescriptor.Read(head) - if err != nil { - return - } - - mtype := mimetype.Detect(head) - mimeType = mtype.String() - - return -} - -func FileIsGZ(file string) (isGZ bool, err error) { - mtype, err := detectFileType(file) - if err != nil { - return - } - isGZ = mtype == "application/gzip" - return -} - -func FileIsTar(file string) (isTar bool, err error) { - mtype, err := detectFileType(file) - if err != nil { - return - } - isTar = mtype == "application/x-tar" - return -} - -func DetectTGZAndUnpack(filePath string, targetDir string) (err error) { - // detect if file is a tar gz and extract to targetDir - fileName := filepath.Base(filePath) - baseFileName := strings.ReplaceAll(fileName, ".tar.gz", "") - baseFileName = strings.ReplaceAll(baseFileName, ".tgz", "") - unpackDir, err := os.MkdirTemp("", "temp-cdk-unpack-archive-") - if err != nil { - return - } - defer os.RemoveAll(unpackDir) - - tarFile := filepath.Join(unpackDir, baseFileName+".tar") - isGZ, err := FileIsGZ(filePath) - if err != nil { - return - } - - if isGZ { - if err := Gunzip(filePath, tarFile); err != nil { - return err - } - } else { - - return - } - - isTar, err := FileIsTar(tarFile) - if err != nil { - return - } - - if isTar { - if err = UnTar(tarFile, targetDir); err != nil { - return - } - } else { - return - } - return -} diff --git a/vendor/github.com/lacework/go-sdk/internal/archive/gz.go b/vendor/github.com/lacework/go-sdk/internal/archive/gz.go deleted file mode 100644 index 177a76844..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/archive/gz.go +++ /dev/null @@ -1,34 +0,0 @@ -package archive - -import ( - "compress/gzip" - "io" - "os" -) - -// Inflate GZip file. -// -// Writes decompressed data to target path. -func Gunzip(source string, target string) (err error) { - reader, err := os.Open(source) - if err != nil { - return - } - defer reader.Close() - - archive, err := gzip.NewReader(reader) - if err != nil { - return - } - defer archive.Close() - - writer, err := os.Create(target) - if err != nil { - return - } - defer writer.Close() - - _, err = io.Copy(writer, archive) - - return -} diff --git a/vendor/github.com/lacework/go-sdk/internal/archive/tar.go b/vendor/github.com/lacework/go-sdk/internal/archive/tar.go deleted file mode 100644 index cf5e84793..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/archive/tar.go +++ /dev/null @@ -1,64 +0,0 @@ -package archive - -import ( - "archive/tar" - "io" - "os" - "path/filepath" -) - -// Extract tarball to dir -func UnTar(tarball string, dir string) (err error) { - reader, err := os.Open(tarball) - if err != nil { - return - } - defer reader.Close() - - tarReader := tar.NewReader(reader) - - for { - hdr, err := tarReader.Next() - if err == io.EOF { - break - } else if err != nil { - return err - } - path := filepath.Join(dir, hdr.Name) - mode := hdr.FileInfo().Mode() - switch hdr.Typeflag { - case tar.TypeReg: - file, err := os.Create(path) - if err != nil { - return err - } - defer file.Close() - - _, err = io.Copy(file, tarReader) - if err != nil { - return err - } - case tar.TypeDir: - err = os.MkdirAll(path, mode) - if err != nil { - return err - } - case tar.TypeLink: - err = os.Link(filepath.Join(dir, filepath.Clean(hdr.Linkname)), path) - if err != nil { - return err - } - case tar.TypeSymlink: - err = os.Symlink(filepath.Clean(hdr.Linkname), path) - if err != nil { - return err - } - case tar.TypeXGlobalHeader, tar.TypeXHeader: - continue - - } - - } - - return -} diff --git a/vendor/github.com/lacework/go-sdk/internal/array/contains.go b/vendor/github.com/lacework/go-sdk/internal/array/contains.go deleted file mode 100644 index 1869c0b1b..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/array/contains.go +++ /dev/null @@ -1,66 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package array - -import "strings" - -func ContainsStr(array []string, expected string) bool { - for _, value := range array { - if expected == value { - return true - } - } - return false -} - -func ContainsStrCaseInsensitive(array []string, expected string) bool { - for _, value := range array { - if strings.EqualFold(expected, value) { - return true - } - } - return false -} - -func ContainsPartialStr(array []string, expected string) bool { - for _, value := range array { - if strings.Contains(expected, value) { - return true - } - } - return false -} - -func ContainsInt(array []int, expected int) bool { - for _, value := range array { - if expected == value { - return true - } - } - return false -} - -func ContainsBool(array []bool, expected bool) bool { - for _, value := range array { - if expected == value { - return true - } - } - return false -} diff --git a/vendor/github.com/lacework/go-sdk/internal/array/join.go b/vendor/github.com/lacework/go-sdk/internal/array/join.go deleted file mode 100644 index 136f62d65..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/array/join.go +++ /dev/null @@ -1,31 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package array - -import ( - "fmt" - "strings" -) - -func JoinInt32(array []int32, delim string) string { - return strings.Trim( - strings.Replace(fmt.Sprint(array), " ", delim, -1), - "[]", - ) -} diff --git a/vendor/github.com/lacework/go-sdk/internal/array/sort.go b/vendor/github.com/lacework/go-sdk/internal/array/sort.go deleted file mode 100644 index 670bc0232..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/array/sort.go +++ /dev/null @@ -1,37 +0,0 @@ -package array - -import ( - "sort" - "strings" -) - -// Sort2D can be used 2d Arrays used for Table Outputs to sort headers -func Sort2D(slice [][]string) { - for range slice { - sort.Slice(slice[:], func(i, j int) bool { - elem := slice[i][0] - next := slice[j][0] - switch strings.Compare(elem, next) { - case -1: - return true - case 1: - return false - default: - // When equal compare next element - for x := 1; x < len(slice[i]); x++ { - secondaryElem := slice[i][x] - nextSecondaryElem := slice[j][x] - switch strings.Compare(secondaryElem, nextSecondaryElem) { - case -1: - return true - case 1: - return false - default: - continue - } - } - return false - } - }) - } -} diff --git a/vendor/github.com/lacework/go-sdk/internal/array/unique.go b/vendor/github.com/lacework/go-sdk/internal/array/unique.go deleted file mode 100644 index 013c332e1..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/array/unique.go +++ /dev/null @@ -1,33 +0,0 @@ -// -// Author:: Darren Murray () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package array - -// Unique removes duplicates from a slice and returns the list with only unique values -// accepts a slice of string or int -func Unique[T string | int](sliceList []T) []T { - var list []T - allKeys := make(map[T]bool) - for _, item := range sliceList { - if _, value := allKeys[item]; !value { - allKeys[item] = true - list = append(list, item) - } - } - return list -} diff --git a/vendor/github.com/lacework/go-sdk/internal/cache/cache.go b/vendor/github.com/lacework/go-sdk/internal/cache/cache.go deleted file mode 100644 index 9407f00b0..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/cache/cache.go +++ /dev/null @@ -1,34 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package cache - -import ( - "path" - - homedir "github.com/mitchellh/go-homedir" -) - -func CacheDir() (string, error) { - home, err := homedir.Dir() - if err != nil { - return "", err - } - - return path.Join(home, ".config", "lacework"), nil -} diff --git a/vendor/github.com/lacework/go-sdk/internal/capturer/capture_output.go b/vendor/github.com/lacework/go-sdk/internal/capturer/capture_output.go deleted file mode 100644 index 5e4f48b7e..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/capturer/capture_output.go +++ /dev/null @@ -1,62 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package capturer - -import ( - "bytes" - "io" - "os" - - "github.com/fatih/color" -) - -// captureOutput executes a function and captures the STDOUT and STDERR, -// useful to test logging messages or human readable output -func CaptureOutput(f func()) string { - r, w, err := os.Pipe() - if err != nil { - panic(err) - } - - stdout := os.Stdout - os.Stdout = w - defer func() { - os.Stdout = stdout - }() - - colorOut := color.Output - color.Output = w - defer func() { - color.Output = colorOut - }() - - stderr := os.Stderr - os.Stderr = w - defer func() { - os.Stderr = stderr - }() - - f() - w.Close() - - var buf bytes.Buffer - io.Copy(&buf, r) //nolint - - return buf.String() -} diff --git a/vendor/github.com/lacework/go-sdk/internal/databox/blob.go b/vendor/github.com/lacework/go-sdk/internal/databox/blob.go deleted file mode 100644 index c0c9e5677..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/databox/blob.go +++ /dev/null @@ -1,34 +0,0 @@ -// Code generated by: internal/databox/generator/main.go -// -// <<< DO NOT EDIT >>> -// - -package databox - -func init() { - box.Add("/reports/aws/cis.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 83, 51, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 49, 95, 50, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 73, 65, 77, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 51, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 52, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 52, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 52, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 87, 83, 95, 67, 73, 83, 95, 52, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 50, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 51, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 52, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 53, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 78, 69, 84, 87, 79, 82, 75, 73, 78, 71, 95, 53, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 77, 79, 78, 71, 79, 68, 66, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 71, 69, 78, 69, 82, 65, 76, 95, 83, 69, 67, 85, 82, 73, 84, 89, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 83, 69, 82, 86, 69, 82, 76, 69, 83, 83, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 82, 68, 83, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 69, 76, 65, 83, 84, 73, 67, 83, 69, 65, 82, 67, 72, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 69, 76, 65, 83, 84, 73, 67, 83, 69, 65, 82, 67, 72, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 69, 76, 65, 83, 84, 73, 67, 83, 69, 65, 82, 67, 72, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 76, 87, 95, 65, 87, 83, 95, 69, 76, 65, 83, 84, 73, 67, 83, 69, 65, 82, 67, 72, 95, 52, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) - box.Add("/reports/azure/cis.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 95, 50, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 50, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 52, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 53, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 54, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 55, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 56, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 56, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 56, 95, 51, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) - box.Add("/reports/azure/cis_131.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 49, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 49, 95, 50, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 76, 87, 95, 73, 65, 77, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 76, 87, 95, 73, 65, 77, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 76, 87, 95, 73, 65, 77, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 50, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 51, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 52, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 53, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 54, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 55, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 56, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 65, 122, 117, 114, 101, 95, 67, 73, 83, 95, 49, 51, 49, 95, 57, 95, 49, 49, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) - box.Add("/reports/gcp/cis.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 52, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 53, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 53, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 53, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 54, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 54, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 54, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 54, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 95, 55, 95, 49, 56, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) - box.Add("/reports/gcp/cis_12.json", []byte{123, 10, 32, 32, 34, 114, 101, 99, 111, 109, 109, 101, 110, 100, 97, 116, 105, 111, 110, 95, 105, 100, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 49, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 50, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 51, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 52, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 53, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 53, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 49, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 49, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 49, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 56, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 57, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 48, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 50, 95, 49, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 51, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 51, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 52, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 53, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 54, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 54, 95, 55, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 55, 95, 49, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 55, 95, 50, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 34, 71, 67, 80, 95, 67, 73, 83, 49, 50, 95, 55, 95, 51, 34, 58, 32, 34, 34, 10, 32, 32, 125, 10, 125}) - box.Add("/scaffoldings/golang/.gitignore", []byte{35, 32, 77, 97, 99, 32, 117, 115, 101, 114, 115, 10, 46, 68, 83, 95, 83, 116, 111, 114, 101, 10, 10, 35, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 67, 68, 75, 32, 100, 101, 118, 45, 109, 111, 100, 101, 10, 46, 100, 101, 118, 10, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 10, 10, 35, 32, 118, 105, 109, 10, 42, 46, 115, 119, 112, 10, 10, 35, 32, 71, 111, 32, 116, 101, 115, 116, 32, 99, 111, 118, 101, 114, 97, 103, 101, 10, 99, 111, 118, 101, 114, 97, 103, 101, 46, 111, 117, 116, 10, 99, 111, 118, 101, 114, 97, 103, 101, 46, 104, 116, 109, 108, 10, 10, 35, 32, 98, 105, 110, 97, 114, 105, 101, 115, 32, 102, 111, 108, 100, 101, 114, 10, 47, 98, 105, 110, 10, 10, 35, 32, 118, 115, 99, 111, 100, 101, 32, 100, 101, 98, 117, 103, 46, 116, 101, 115, 116, 10, 42, 42, 47, 100, 101, 98, 117, 103, 46, 116, 101, 115, 116, 10, 46, 118, 115, 99, 111, 100, 101, 47, 42, 10}) - box.Add("/scaffoldings/golang/LICENSE", []byte{10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 44, 32, 74, 97, 110, 117, 97, 114, 121, 32, 50, 48, 48, 52, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 10, 10, 32, 32, 32, 84, 69, 82, 77, 83, 32, 65, 78, 68, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 70, 79, 82, 32, 85, 83, 69, 44, 32, 82, 69, 80, 82, 79, 68, 85, 67, 84, 73, 79, 78, 44, 32, 65, 78, 68, 32, 68, 73, 83, 84, 82, 73, 66, 85, 84, 73, 79, 78, 10, 10, 32, 32, 32, 49, 46, 32, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 115, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 102, 111, 114, 32, 117, 115, 101, 44, 32, 114, 101, 112, 114, 111, 100, 117, 99, 116, 105, 111, 110, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 100, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 97, 115, 32, 100, 101, 102, 105, 110, 101, 100, 32, 98, 121, 32, 83, 101, 99, 116, 105, 111, 110, 115, 32, 49, 32, 116, 104, 114, 111, 117, 103, 104, 32, 57, 32, 111, 102, 32, 116, 104, 105, 115, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 76, 105, 99, 101, 110, 115, 111, 114, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 32, 111, 114, 32, 101, 110, 116, 105, 116, 121, 32, 97, 117, 116, 104, 111, 114, 105, 122, 101, 100, 32, 98, 121, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 32, 116, 104, 97, 116, 32, 105, 115, 32, 103, 114, 97, 110, 116, 105, 110, 103, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 76, 101, 103, 97, 108, 32, 69, 110, 116, 105, 116, 121, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 117, 110, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 32, 97, 99, 116, 105, 110, 103, 32, 101, 110, 116, 105, 116, 121, 32, 97, 110, 100, 32, 97, 108, 108, 10, 32, 32, 32, 32, 32, 32, 111, 116, 104, 101, 114, 32, 101, 110, 116, 105, 116, 105, 101, 115, 32, 116, 104, 97, 116, 32, 99, 111, 110, 116, 114, 111, 108, 44, 32, 97, 114, 101, 32, 99, 111, 110, 116, 114, 111, 108, 108, 101, 100, 32, 98, 121, 44, 32, 111, 114, 32, 97, 114, 101, 32, 117, 110, 100, 101, 114, 32, 99, 111, 109, 109, 111, 110, 10, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 114, 111, 108, 32, 119, 105, 116, 104, 32, 116, 104, 97, 116, 32, 101, 110, 116, 105, 116, 121, 46, 32, 70, 111, 114, 32, 116, 104, 101, 32, 112, 117, 114, 112, 111, 115, 101, 115, 32, 111, 102, 32, 116, 104, 105, 115, 32, 100, 101, 102, 105, 110, 105, 116, 105, 111, 110, 44, 10, 32, 32, 32, 32, 32, 32, 34, 99, 111, 110, 116, 114, 111, 108, 34, 32, 109, 101, 97, 110, 115, 32, 40, 105, 41, 32, 116, 104, 101, 32, 112, 111, 119, 101, 114, 44, 32, 100, 105, 114, 101, 99, 116, 32, 111, 114, 32, 105, 110, 100, 105, 114, 101, 99, 116, 44, 32, 116, 111, 32, 99, 97, 117, 115, 101, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 100, 105, 114, 101, 99, 116, 105, 111, 110, 32, 111, 114, 32, 109, 97, 110, 97, 103, 101, 109, 101, 110, 116, 32, 111, 102, 32, 115, 117, 99, 104, 32, 101, 110, 116, 105, 116, 121, 44, 32, 119, 104, 101, 116, 104, 101, 114, 32, 98, 121, 32, 99, 111, 110, 116, 114, 97, 99, 116, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 44, 32, 111, 114, 32, 40, 105, 105, 41, 32, 111, 119, 110, 101, 114, 115, 104, 105, 112, 32, 111, 102, 32, 102, 105, 102, 116, 121, 32, 112, 101, 114, 99, 101, 110, 116, 32, 40, 53, 48, 37, 41, 32, 111, 114, 32, 109, 111, 114, 101, 32, 111, 102, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 111, 117, 116, 115, 116, 97, 110, 100, 105, 110, 103, 32, 115, 104, 97, 114, 101, 115, 44, 32, 111, 114, 32, 40, 105, 105, 105, 41, 32, 98, 101, 110, 101, 102, 105, 99, 105, 97, 108, 32, 111, 119, 110, 101, 114, 115, 104, 105, 112, 32, 111, 102, 32, 115, 117, 99, 104, 32, 101, 110, 116, 105, 116, 121, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 89, 111, 117, 34, 32, 40, 111, 114, 32, 34, 89, 111, 117, 114, 34, 41, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 97, 110, 32, 105, 110, 100, 105, 118, 105, 100, 117, 97, 108, 32, 111, 114, 32, 76, 101, 103, 97, 108, 32, 69, 110, 116, 105, 116, 121, 10, 32, 32, 32, 32, 32, 32, 101, 120, 101, 114, 99, 105, 115, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 103, 114, 97, 110, 116, 101, 100, 32, 98, 121, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 83, 111, 117, 114, 99, 101, 34, 32, 102, 111, 114, 109, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 112, 114, 101, 102, 101, 114, 114, 101, 100, 32, 102, 111, 114, 109, 32, 102, 111, 114, 32, 109, 97, 107, 105, 110, 103, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 44, 10, 32, 32, 32, 32, 32, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 98, 117, 116, 32, 110, 111, 116, 32, 108, 105, 109, 105, 116, 101, 100, 32, 116, 111, 32, 115, 111, 102, 116, 119, 97, 114, 101, 32, 115, 111, 117, 114, 99, 101, 32, 99, 111, 100, 101, 44, 32, 100, 111, 99, 117, 109, 101, 110, 116, 97, 116, 105, 111, 110, 10, 32, 32, 32, 32, 32, 32, 115, 111, 117, 114, 99, 101, 44, 32, 97, 110, 100, 32, 99, 111, 110, 102, 105, 103, 117, 114, 97, 116, 105, 111, 110, 32, 102, 105, 108, 101, 115, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 79, 98, 106, 101, 99, 116, 34, 32, 102, 111, 114, 109, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 97, 110, 121, 32, 102, 111, 114, 109, 32, 114, 101, 115, 117, 108, 116, 105, 110, 103, 32, 102, 114, 111, 109, 32, 109, 101, 99, 104, 97, 110, 105, 99, 97, 108, 10, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 111, 114, 32, 116, 114, 97, 110, 115, 108, 97, 116, 105, 111, 110, 32, 111, 102, 32, 97, 32, 83, 111, 117, 114, 99, 101, 32, 102, 111, 114, 109, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 98, 117, 116, 10, 32, 32, 32, 32, 32, 32, 110, 111, 116, 32, 108, 105, 109, 105, 116, 101, 100, 32, 116, 111, 32, 99, 111, 109, 112, 105, 108, 101, 100, 32, 111, 98, 106, 101, 99, 116, 32, 99, 111, 100, 101, 44, 32, 103, 101, 110, 101, 114, 97, 116, 101, 100, 32, 100, 111, 99, 117, 109, 101, 110, 116, 97, 116, 105, 111, 110, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 100, 32, 99, 111, 110, 118, 101, 114, 115, 105, 111, 110, 115, 32, 116, 111, 32, 111, 116, 104, 101, 114, 32, 109, 101, 100, 105, 97, 32, 116, 121, 112, 101, 115, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 87, 111, 114, 107, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 116, 104, 101, 32, 119, 111, 114, 107, 32, 111, 102, 32, 97, 117, 116, 104, 111, 114, 115, 104, 105, 112, 44, 32, 119, 104, 101, 116, 104, 101, 114, 32, 105, 110, 32, 83, 111, 117, 114, 99, 101, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 79, 98, 106, 101, 99, 116, 32, 102, 111, 114, 109, 44, 32, 109, 97, 100, 101, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 97, 115, 32, 105, 110, 100, 105, 99, 97, 116, 101, 100, 32, 98, 121, 32, 97, 10, 32, 32, 32, 32, 32, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 110, 111, 116, 105, 99, 101, 32, 116, 104, 97, 116, 32, 105, 115, 32, 105, 110, 99, 108, 117, 100, 101, 100, 32, 105, 110, 32, 111, 114, 32, 97, 116, 116, 97, 99, 104, 101, 100, 32, 116, 111, 32, 116, 104, 101, 32, 119, 111, 114, 107, 10, 32, 32, 32, 32, 32, 32, 40, 97, 110, 32, 101, 120, 97, 109, 112, 108, 101, 32, 105, 115, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 105, 110, 32, 116, 104, 101, 32, 65, 112, 112, 101, 110, 100, 105, 120, 32, 98, 101, 108, 111, 119, 41, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 97, 110, 121, 32, 119, 111, 114, 107, 44, 32, 119, 104, 101, 116, 104, 101, 114, 32, 105, 110, 32, 83, 111, 117, 114, 99, 101, 32, 111, 114, 32, 79, 98, 106, 101, 99, 116, 10, 32, 32, 32, 32, 32, 32, 102, 111, 114, 109, 44, 32, 116, 104, 97, 116, 32, 105, 115, 32, 98, 97, 115, 101, 100, 32, 111, 110, 32, 40, 111, 114, 32, 100, 101, 114, 105, 118, 101, 100, 32, 102, 114, 111, 109, 41, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 102, 111, 114, 32, 119, 104, 105, 99, 104, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 101, 100, 105, 116, 111, 114, 105, 97, 108, 32, 114, 101, 118, 105, 115, 105, 111, 110, 115, 44, 32, 97, 110, 110, 111, 116, 97, 116, 105, 111, 110, 115, 44, 32, 101, 108, 97, 98, 111, 114, 97, 116, 105, 111, 110, 115, 44, 32, 111, 114, 32, 111, 116, 104, 101, 114, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 10, 32, 32, 32, 32, 32, 32, 114, 101, 112, 114, 101, 115, 101, 110, 116, 44, 32, 97, 115, 32, 97, 32, 119, 104, 111, 108, 101, 44, 32, 97, 110, 32, 111, 114, 105, 103, 105, 110, 97, 108, 32, 119, 111, 114, 107, 32, 111, 102, 32, 97, 117, 116, 104, 111, 114, 115, 104, 105, 112, 46, 32, 70, 111, 114, 32, 116, 104, 101, 32, 112, 117, 114, 112, 111, 115, 101, 115, 10, 32, 32, 32, 32, 32, 32, 111, 102, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 115, 104, 97, 108, 108, 32, 110, 111, 116, 32, 105, 110, 99, 108, 117, 100, 101, 32, 119, 111, 114, 107, 115, 32, 116, 104, 97, 116, 32, 114, 101, 109, 97, 105, 110, 10, 32, 32, 32, 32, 32, 32, 115, 101, 112, 97, 114, 97, 98, 108, 101, 32, 102, 114, 111, 109, 44, 32, 111, 114, 32, 109, 101, 114, 101, 108, 121, 32, 108, 105, 110, 107, 32, 40, 111, 114, 32, 98, 105, 110, 100, 32, 98, 121, 32, 110, 97, 109, 101, 41, 32, 116, 111, 32, 116, 104, 101, 32, 105, 110, 116, 101, 114, 102, 97, 99, 101, 115, 32, 111, 102, 44, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 101, 114, 101, 111, 102, 46, 10, 10, 32, 32, 32, 32, 32, 32, 34, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 97, 110, 121, 32, 119, 111, 114, 107, 32, 111, 102, 32, 97, 117, 116, 104, 111, 114, 115, 104, 105, 112, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 111, 114, 105, 103, 105, 110, 97, 108, 32, 118, 101, 114, 115, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 97, 110, 121, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 32, 111, 114, 32, 97, 100, 100, 105, 116, 105, 111, 110, 115, 10, 32, 32, 32, 32, 32, 32, 116, 111, 32, 116, 104, 97, 116, 32, 87, 111, 114, 107, 32, 111, 114, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 101, 114, 101, 111, 102, 44, 32, 116, 104, 97, 116, 32, 105, 115, 32, 105, 110, 116, 101, 110, 116, 105, 111, 110, 97, 108, 108, 121, 10, 32, 32, 32, 32, 32, 32, 115, 117, 98, 109, 105, 116, 116, 101, 100, 32, 116, 111, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 102, 111, 114, 32, 105, 110, 99, 108, 117, 115, 105, 111, 110, 32, 105, 110, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 98, 121, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 10, 32, 32, 32, 32, 32, 32, 111, 114, 32, 98, 121, 32, 97, 110, 32, 105, 110, 100, 105, 118, 105, 100, 117, 97, 108, 32, 111, 114, 32, 76, 101, 103, 97, 108, 32, 69, 110, 116, 105, 116, 121, 32, 97, 117, 116, 104, 111, 114, 105, 122, 101, 100, 32, 116, 111, 32, 115, 117, 98, 109, 105, 116, 32, 111, 110, 32, 98, 101, 104, 97, 108, 102, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 46, 32, 70, 111, 114, 32, 116, 104, 101, 32, 112, 117, 114, 112, 111, 115, 101, 115, 32, 111, 102, 32, 116, 104, 105, 115, 32, 100, 101, 102, 105, 110, 105, 116, 105, 111, 110, 44, 32, 34, 115, 117, 98, 109, 105, 116, 116, 101, 100, 34, 10, 32, 32, 32, 32, 32, 32, 109, 101, 97, 110, 115, 32, 97, 110, 121, 32, 102, 111, 114, 109, 32, 111, 102, 32, 101, 108, 101, 99, 116, 114, 111, 110, 105, 99, 44, 32, 118, 101, 114, 98, 97, 108, 44, 32, 111, 114, 32, 119, 114, 105, 116, 116, 101, 110, 32, 99, 111, 109, 109, 117, 110, 105, 99, 97, 116, 105, 111, 110, 32, 115, 101, 110, 116, 10, 32, 32, 32, 32, 32, 32, 116, 111, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 111, 114, 32, 105, 116, 115, 32, 114, 101, 112, 114, 101, 115, 101, 110, 116, 97, 116, 105, 118, 101, 115, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 98, 117, 116, 32, 110, 111, 116, 32, 108, 105, 109, 105, 116, 101, 100, 32, 116, 111, 10, 32, 32, 32, 32, 32, 32, 99, 111, 109, 109, 117, 110, 105, 99, 97, 116, 105, 111, 110, 32, 111, 110, 32, 101, 108, 101, 99, 116, 114, 111, 110, 105, 99, 32, 109, 97, 105, 108, 105, 110, 103, 32, 108, 105, 115, 116, 115, 44, 32, 115, 111, 117, 114, 99, 101, 32, 99, 111, 100, 101, 32, 99, 111, 110, 116, 114, 111, 108, 32, 115, 121, 115, 116, 101, 109, 115, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 100, 32, 105, 115, 115, 117, 101, 32, 116, 114, 97, 99, 107, 105, 110, 103, 32, 115, 121, 115, 116, 101, 109, 115, 32, 116, 104, 97, 116, 32, 97, 114, 101, 32, 109, 97, 110, 97, 103, 101, 100, 32, 98, 121, 44, 32, 111, 114, 32, 111, 110, 32, 98, 101, 104, 97, 108, 102, 32, 111, 102, 44, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 102, 111, 114, 32, 116, 104, 101, 32, 112, 117, 114, 112, 111, 115, 101, 32, 111, 102, 32, 100, 105, 115, 99, 117, 115, 115, 105, 110, 103, 32, 97, 110, 100, 32, 105, 109, 112, 114, 111, 118, 105, 110, 103, 32, 116, 104, 101, 32, 87, 111, 114, 107, 44, 32, 98, 117, 116, 10, 32, 32, 32, 32, 32, 32, 101, 120, 99, 108, 117, 100, 105, 110, 103, 32, 99, 111, 109, 109, 117, 110, 105, 99, 97, 116, 105, 111, 110, 32, 116, 104, 97, 116, 32, 105, 115, 32, 99, 111, 110, 115, 112, 105, 99, 117, 111, 117, 115, 108, 121, 32, 109, 97, 114, 107, 101, 100, 32, 111, 114, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 10, 32, 32, 32, 32, 32, 32, 100, 101, 115, 105, 103, 110, 97, 116, 101, 100, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 32, 98, 121, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 32, 97, 115, 32, 34, 78, 111, 116, 32, 97, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 46, 34, 10, 10, 32, 32, 32, 32, 32, 32, 34, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 34, 32, 115, 104, 97, 108, 108, 32, 109, 101, 97, 110, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 97, 110, 100, 32, 97, 110, 121, 32, 105, 110, 100, 105, 118, 105, 100, 117, 97, 108, 32, 111, 114, 32, 76, 101, 103, 97, 108, 32, 69, 110, 116, 105, 116, 121, 10, 32, 32, 32, 32, 32, 32, 111, 110, 32, 98, 101, 104, 97, 108, 102, 32, 111, 102, 32, 119, 104, 111, 109, 32, 97, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 104, 97, 115, 32, 98, 101, 101, 110, 32, 114, 101, 99, 101, 105, 118, 101, 100, 32, 98, 121, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 115, 117, 98, 115, 101, 113, 117, 101, 110, 116, 108, 121, 32, 105, 110, 99, 111, 114, 112, 111, 114, 97, 116, 101, 100, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 101, 32, 87, 111, 114, 107, 46, 10, 10, 32, 32, 32, 50, 46, 32, 71, 114, 97, 110, 116, 32, 111, 102, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 76, 105, 99, 101, 110, 115, 101, 46, 32, 83, 117, 98, 106, 101, 99, 116, 32, 116, 111, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 101, 97, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 104, 101, 114, 101, 98, 121, 32, 103, 114, 97, 110, 116, 115, 32, 116, 111, 32, 89, 111, 117, 32, 97, 32, 112, 101, 114, 112, 101, 116, 117, 97, 108, 44, 10, 32, 32, 32, 32, 32, 32, 119, 111, 114, 108, 100, 119, 105, 100, 101, 44, 32, 110, 111, 110, 45, 101, 120, 99, 108, 117, 115, 105, 118, 101, 44, 32, 110, 111, 45, 99, 104, 97, 114, 103, 101, 44, 32, 114, 111, 121, 97, 108, 116, 121, 45, 102, 114, 101, 101, 44, 32, 105, 114, 114, 101, 118, 111, 99, 97, 98, 108, 101, 10, 32, 32, 32, 32, 32, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 108, 105, 99, 101, 110, 115, 101, 32, 116, 111, 32, 114, 101, 112, 114, 111, 100, 117, 99, 101, 44, 32, 112, 114, 101, 112, 97, 114, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 111, 102, 44, 10, 32, 32, 32, 32, 32, 32, 112, 117, 98, 108, 105, 99, 108, 121, 32, 100, 105, 115, 112, 108, 97, 121, 44, 32, 112, 117, 98, 108, 105, 99, 108, 121, 32, 112, 101, 114, 102, 111, 114, 109, 44, 32, 115, 117, 98, 108, 105, 99, 101, 110, 115, 101, 44, 32, 97, 110, 100, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 115, 117, 99, 104, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 105, 110, 32, 83, 111, 117, 114, 99, 101, 32, 111, 114, 32, 79, 98, 106, 101, 99, 116, 32, 102, 111, 114, 109, 46, 10, 10, 32, 32, 32, 51, 46, 32, 71, 114, 97, 110, 116, 32, 111, 102, 32, 80, 97, 116, 101, 110, 116, 32, 76, 105, 99, 101, 110, 115, 101, 46, 32, 83, 117, 98, 106, 101, 99, 116, 32, 116, 111, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 101, 97, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 104, 101, 114, 101, 98, 121, 32, 103, 114, 97, 110, 116, 115, 32, 116, 111, 32, 89, 111, 117, 32, 97, 32, 112, 101, 114, 112, 101, 116, 117, 97, 108, 44, 10, 32, 32, 32, 32, 32, 32, 119, 111, 114, 108, 100, 119, 105, 100, 101, 44, 32, 110, 111, 110, 45, 101, 120, 99, 108, 117, 115, 105, 118, 101, 44, 32, 110, 111, 45, 99, 104, 97, 114, 103, 101, 44, 32, 114, 111, 121, 97, 108, 116, 121, 45, 102, 114, 101, 101, 44, 32, 105, 114, 114, 101, 118, 111, 99, 97, 98, 108, 101, 10, 32, 32, 32, 32, 32, 32, 40, 101, 120, 99, 101, 112, 116, 32, 97, 115, 32, 115, 116, 97, 116, 101, 100, 32, 105, 110, 32, 116, 104, 105, 115, 32, 115, 101, 99, 116, 105, 111, 110, 41, 32, 112, 97, 116, 101, 110, 116, 32, 108, 105, 99, 101, 110, 115, 101, 32, 116, 111, 32, 109, 97, 107, 101, 44, 32, 104, 97, 118, 101, 32, 109, 97, 100, 101, 44, 10, 32, 32, 32, 32, 32, 32, 117, 115, 101, 44, 32, 111, 102, 102, 101, 114, 32, 116, 111, 32, 115, 101, 108, 108, 44, 32, 115, 101, 108, 108, 44, 32, 105, 109, 112, 111, 114, 116, 44, 32, 97, 110, 100, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 32, 116, 114, 97, 110, 115, 102, 101, 114, 32, 116, 104, 101, 32, 87, 111, 114, 107, 44, 10, 32, 32, 32, 32, 32, 32, 119, 104, 101, 114, 101, 32, 115, 117, 99, 104, 32, 108, 105, 99, 101, 110, 115, 101, 32, 97, 112, 112, 108, 105, 101, 115, 32, 111, 110, 108, 121, 32, 116, 111, 32, 116, 104, 111, 115, 101, 32, 112, 97, 116, 101, 110, 116, 32, 99, 108, 97, 105, 109, 115, 32, 108, 105, 99, 101, 110, 115, 97, 98, 108, 101, 10, 32, 32, 32, 32, 32, 32, 98, 121, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 116, 104, 97, 116, 32, 97, 114, 101, 32, 110, 101, 99, 101, 115, 115, 97, 114, 105, 108, 121, 32, 105, 110, 102, 114, 105, 110, 103, 101, 100, 32, 98, 121, 32, 116, 104, 101, 105, 114, 10, 32, 32, 32, 32, 32, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 40, 115, 41, 32, 97, 108, 111, 110, 101, 32, 111, 114, 32, 98, 121, 32, 99, 111, 109, 98, 105, 110, 97, 116, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 105, 114, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 40, 115, 41, 10, 32, 32, 32, 32, 32, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 116, 111, 32, 119, 104, 105, 99, 104, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 40, 115, 41, 32, 119, 97, 115, 32, 115, 117, 98, 109, 105, 116, 116, 101, 100, 46, 32, 73, 102, 32, 89, 111, 117, 10, 32, 32, 32, 32, 32, 32, 105, 110, 115, 116, 105, 116, 117, 116, 101, 32, 112, 97, 116, 101, 110, 116, 32, 108, 105, 116, 105, 103, 97, 116, 105, 111, 110, 32, 97, 103, 97, 105, 110, 115, 116, 32, 97, 110, 121, 32, 101, 110, 116, 105, 116, 121, 32, 40, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 97, 10, 32, 32, 32, 32, 32, 32, 99, 114, 111, 115, 115, 45, 99, 108, 97, 105, 109, 32, 111, 114, 32, 99, 111, 117, 110, 116, 101, 114, 99, 108, 97, 105, 109, 32, 105, 110, 32, 97, 32, 108, 97, 119, 115, 117, 105, 116, 41, 32, 97, 108, 108, 101, 103, 105, 110, 103, 32, 116, 104, 97, 116, 32, 116, 104, 101, 32, 87, 111, 114, 107, 10, 32, 32, 32, 32, 32, 32, 111, 114, 32, 97, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 105, 110, 99, 111, 114, 112, 111, 114, 97, 116, 101, 100, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 99, 111, 110, 115, 116, 105, 116, 117, 116, 101, 115, 32, 100, 105, 114, 101, 99, 116, 10, 32, 32, 32, 32, 32, 32, 111, 114, 32, 99, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 121, 32, 112, 97, 116, 101, 110, 116, 32, 105, 110, 102, 114, 105, 110, 103, 101, 109, 101, 110, 116, 44, 32, 116, 104, 101, 110, 32, 97, 110, 121, 32, 112, 97, 116, 101, 110, 116, 32, 108, 105, 99, 101, 110, 115, 101, 115, 10, 32, 32, 32, 32, 32, 32, 103, 114, 97, 110, 116, 101, 100, 32, 116, 111, 32, 89, 111, 117, 32, 117, 110, 100, 101, 114, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 97, 116, 32, 87, 111, 114, 107, 32, 115, 104, 97, 108, 108, 32, 116, 101, 114, 109, 105, 110, 97, 116, 101, 10, 32, 32, 32, 32, 32, 32, 97, 115, 32, 111, 102, 32, 116, 104, 101, 32, 100, 97, 116, 101, 32, 115, 117, 99, 104, 32, 108, 105, 116, 105, 103, 97, 116, 105, 111, 110, 32, 105, 115, 32, 102, 105, 108, 101, 100, 46, 10, 10, 32, 32, 32, 52, 46, 32, 82, 101, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 46, 32, 89, 111, 117, 32, 109, 97, 121, 32, 114, 101, 112, 114, 111, 100, 117, 99, 101, 32, 97, 110, 100, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 32, 99, 111, 112, 105, 101, 115, 32, 111, 102, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 87, 111, 114, 107, 32, 111, 114, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 101, 114, 101, 111, 102, 32, 105, 110, 32, 97, 110, 121, 32, 109, 101, 100, 105, 117, 109, 44, 32, 119, 105, 116, 104, 32, 111, 114, 32, 119, 105, 116, 104, 111, 117, 116, 10, 32, 32, 32, 32, 32, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 44, 32, 97, 110, 100, 32, 105, 110, 32, 83, 111, 117, 114, 99, 101, 32, 111, 114, 32, 79, 98, 106, 101, 99, 116, 32, 102, 111, 114, 109, 44, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 116, 104, 97, 116, 32, 89, 111, 117, 10, 32, 32, 32, 32, 32, 32, 109, 101, 101, 116, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 58, 10, 10, 32, 32, 32, 32, 32, 32, 40, 97, 41, 32, 89, 111, 117, 32, 109, 117, 115, 116, 32, 103, 105, 118, 101, 32, 97, 110, 121, 32, 111, 116, 104, 101, 114, 32, 114, 101, 99, 105, 112, 105, 101, 110, 116, 115, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 59, 32, 97, 110, 100, 10, 10, 32, 32, 32, 32, 32, 32, 40, 98, 41, 32, 89, 111, 117, 32, 109, 117, 115, 116, 32, 99, 97, 117, 115, 101, 32, 97, 110, 121, 32, 109, 111, 100, 105, 102, 105, 101, 100, 32, 102, 105, 108, 101, 115, 32, 116, 111, 32, 99, 97, 114, 114, 121, 32, 112, 114, 111, 109, 105, 110, 101, 110, 116, 32, 110, 111, 116, 105, 99, 101, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 116, 97, 116, 105, 110, 103, 32, 116, 104, 97, 116, 32, 89, 111, 117, 32, 99, 104, 97, 110, 103, 101, 100, 32, 116, 104, 101, 32, 102, 105, 108, 101, 115, 59, 32, 97, 110, 100, 10, 10, 32, 32, 32, 32, 32, 32, 40, 99, 41, 32, 89, 111, 117, 32, 109, 117, 115, 116, 32, 114, 101, 116, 97, 105, 110, 44, 32, 105, 110, 32, 116, 104, 101, 32, 83, 111, 117, 114, 99, 101, 32, 102, 111, 114, 109, 32, 111, 102, 32, 97, 110, 121, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 104, 97, 116, 32, 89, 111, 117, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 44, 32, 97, 108, 108, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 44, 32, 112, 97, 116, 101, 110, 116, 44, 32, 116, 114, 97, 100, 101, 109, 97, 114, 107, 44, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 116, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 110, 111, 116, 105, 99, 101, 115, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 83, 111, 117, 114, 99, 101, 32, 102, 111, 114, 109, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 101, 120, 99, 108, 117, 100, 105, 110, 103, 32, 116, 104, 111, 115, 101, 32, 110, 111, 116, 105, 99, 101, 115, 32, 116, 104, 97, 116, 32, 100, 111, 32, 110, 111, 116, 32, 112, 101, 114, 116, 97, 105, 110, 32, 116, 111, 32, 97, 110, 121, 32, 112, 97, 114, 116, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 59, 32, 97, 110, 100, 10, 10, 32, 32, 32, 32, 32, 32, 40, 100, 41, 32, 73, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 105, 110, 99, 108, 117, 100, 101, 115, 32, 97, 32, 34, 78, 79, 84, 73, 67, 69, 34, 32, 116, 101, 120, 116, 32, 102, 105, 108, 101, 32, 97, 115, 32, 112, 97, 114, 116, 32, 111, 102, 32, 105, 116, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 44, 32, 116, 104, 101, 110, 32, 97, 110, 121, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 97, 116, 32, 89, 111, 117, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 32, 109, 117, 115, 116, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 110, 99, 108, 117, 100, 101, 32, 97, 32, 114, 101, 97, 100, 97, 98, 108, 101, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 97, 116, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 110, 111, 116, 105, 99, 101, 115, 32, 99, 111, 110, 116, 97, 105, 110, 101, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 116, 104, 105, 110, 32, 115, 117, 99, 104, 32, 78, 79, 84, 73, 67, 69, 32, 102, 105, 108, 101, 44, 32, 101, 120, 99, 108, 117, 100, 105, 110, 103, 32, 116, 104, 111, 115, 101, 32, 110, 111, 116, 105, 99, 101, 115, 32, 116, 104, 97, 116, 32, 100, 111, 32, 110, 111, 116, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 101, 114, 116, 97, 105, 110, 32, 116, 111, 32, 97, 110, 121, 32, 112, 97, 114, 116, 32, 111, 102, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 44, 32, 105, 110, 32, 97, 116, 32, 108, 101, 97, 115, 116, 32, 111, 110, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 102, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 32, 112, 108, 97, 99, 101, 115, 58, 32, 119, 105, 116, 104, 105, 110, 32, 97, 32, 78, 79, 84, 73, 67, 69, 32, 116, 101, 120, 116, 32, 102, 105, 108, 101, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 115, 32, 112, 97, 114, 116, 32, 111, 102, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 59, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 101, 32, 83, 111, 117, 114, 99, 101, 32, 102, 111, 114, 109, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 99, 117, 109, 101, 110, 116, 97, 116, 105, 111, 110, 44, 32, 105, 102, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 97, 108, 111, 110, 103, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 59, 32, 111, 114, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 116, 104, 105, 110, 32, 97, 32, 100, 105, 115, 112, 108, 97, 121, 32, 103, 101, 110, 101, 114, 97, 116, 101, 100, 32, 98, 121, 32, 116, 104, 101, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 44, 32, 105, 102, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 101, 114, 101, 118, 101, 114, 32, 115, 117, 99, 104, 32, 116, 104, 105, 114, 100, 45, 112, 97, 114, 116, 121, 32, 110, 111, 116, 105, 99, 101, 115, 32, 110, 111, 114, 109, 97, 108, 108, 121, 32, 97, 112, 112, 101, 97, 114, 46, 32, 84, 104, 101, 32, 99, 111, 110, 116, 101, 110, 116, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 102, 32, 116, 104, 101, 32, 78, 79, 84, 73, 67, 69, 32, 102, 105, 108, 101, 32, 97, 114, 101, 32, 102, 111, 114, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 97, 108, 32, 112, 117, 114, 112, 111, 115, 101, 115, 32, 111, 110, 108, 121, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 32, 110, 111, 116, 32, 109, 111, 100, 105, 102, 121, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 32, 89, 111, 117, 32, 109, 97, 121, 32, 97, 100, 100, 32, 89, 111, 117, 114, 32, 111, 119, 110, 32, 97, 116, 116, 114, 105, 98, 117, 116, 105, 111, 110, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 110, 111, 116, 105, 99, 101, 115, 32, 119, 105, 116, 104, 105, 110, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 97, 116, 32, 89, 111, 117, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 44, 32, 97, 108, 111, 110, 103, 115, 105, 100, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 32, 97, 115, 32, 97, 110, 32, 97, 100, 100, 101, 110, 100, 117, 109, 32, 116, 111, 32, 116, 104, 101, 32, 78, 79, 84, 73, 67, 69, 32, 116, 101, 120, 116, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 87, 111, 114, 107, 44, 32, 112, 114, 111, 118, 105, 100, 101, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 104, 97, 116, 32, 115, 117, 99, 104, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 97, 116, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 110, 111, 116, 105, 99, 101, 115, 32, 99, 97, 110, 110, 111, 116, 32, 98, 101, 32, 99, 111, 110, 115, 116, 114, 117, 101, 100, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 115, 32, 109, 111, 100, 105, 102, 121, 105, 110, 103, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 32, 32, 32, 89, 111, 117, 32, 109, 97, 121, 32, 97, 100, 100, 32, 89, 111, 117, 114, 32, 111, 119, 110, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 115, 116, 97, 116, 101, 109, 101, 110, 116, 32, 116, 111, 32, 89, 111, 117, 114, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 32, 97, 110, 100, 10, 32, 32, 32, 32, 32, 32, 109, 97, 121, 32, 112, 114, 111, 118, 105, 100, 101, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 111, 114, 32, 100, 105, 102, 102, 101, 114, 101, 110, 116, 32, 108, 105, 99, 101, 110, 115, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 10, 32, 32, 32, 32, 32, 32, 102, 111, 114, 32, 117, 115, 101, 44, 32, 114, 101, 112, 114, 111, 100, 117, 99, 116, 105, 111, 110, 44, 32, 111, 114, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 111, 102, 32, 89, 111, 117, 114, 32, 109, 111, 100, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 44, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 102, 111, 114, 32, 97, 110, 121, 32, 115, 117, 99, 104, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 97, 115, 32, 97, 32, 119, 104, 111, 108, 101, 44, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 89, 111, 117, 114, 32, 117, 115, 101, 44, 10, 32, 32, 32, 32, 32, 32, 114, 101, 112, 114, 111, 100, 117, 99, 116, 105, 111, 110, 44, 32, 97, 110, 100, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 32, 99, 111, 109, 112, 108, 105, 101, 115, 32, 119, 105, 116, 104, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 115, 116, 97, 116, 101, 100, 32, 105, 110, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 53, 46, 32, 83, 117, 98, 109, 105, 115, 115, 105, 111, 110, 32, 111, 102, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 115, 46, 32, 85, 110, 108, 101, 115, 115, 32, 89, 111, 117, 32, 101, 120, 112, 108, 105, 99, 105, 116, 108, 121, 32, 115, 116, 97, 116, 101, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 121, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 32, 105, 110, 116, 101, 110, 116, 105, 111, 110, 97, 108, 108, 121, 32, 115, 117, 98, 109, 105, 116, 116, 101, 100, 32, 102, 111, 114, 32, 105, 110, 99, 108, 117, 115, 105, 111, 110, 32, 105, 110, 32, 116, 104, 101, 32, 87, 111, 114, 107, 10, 32, 32, 32, 32, 32, 32, 98, 121, 32, 89, 111, 117, 32, 116, 111, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 115, 104, 97, 108, 108, 32, 98, 101, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 97, 110, 100, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 32, 111, 102, 10, 32, 32, 32, 32, 32, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 119, 105, 116, 104, 111, 117, 116, 32, 97, 110, 121, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 116, 101, 114, 109, 115, 32, 111, 114, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 46, 10, 32, 32, 32, 32, 32, 32, 78, 111, 116, 119, 105, 116, 104, 115, 116, 97, 110, 100, 105, 110, 103, 32, 116, 104, 101, 32, 97, 98, 111, 118, 101, 44, 32, 110, 111, 116, 104, 105, 110, 103, 32, 104, 101, 114, 101, 105, 110, 32, 115, 104, 97, 108, 108, 32, 115, 117, 112, 101, 114, 115, 101, 100, 101, 32, 111, 114, 32, 109, 111, 100, 105, 102, 121, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 116, 101, 114, 109, 115, 32, 111, 102, 32, 97, 110, 121, 32, 115, 101, 112, 97, 114, 97, 116, 101, 32, 108, 105, 99, 101, 110, 115, 101, 32, 97, 103, 114, 101, 101, 109, 101, 110, 116, 32, 121, 111, 117, 32, 109, 97, 121, 32, 104, 97, 118, 101, 32, 101, 120, 101, 99, 117, 116, 101, 100, 10, 32, 32, 32, 32, 32, 32, 119, 105, 116, 104, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 114, 101, 103, 97, 114, 100, 105, 110, 103, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 115, 46, 10, 10, 32, 32, 32, 54, 46, 32, 84, 114, 97, 100, 101, 109, 97, 114, 107, 115, 46, 32, 84, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 32, 100, 111, 101, 115, 32, 110, 111, 116, 32, 103, 114, 97, 110, 116, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 32, 116, 111, 32, 117, 115, 101, 32, 116, 104, 101, 32, 116, 114, 97, 100, 101, 10, 32, 32, 32, 32, 32, 32, 110, 97, 109, 101, 115, 44, 32, 116, 114, 97, 100, 101, 109, 97, 114, 107, 115, 44, 32, 115, 101, 114, 118, 105, 99, 101, 32, 109, 97, 114, 107, 115, 44, 32, 111, 114, 32, 112, 114, 111, 100, 117, 99, 116, 32, 110, 97, 109, 101, 115, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 111, 114, 44, 10, 32, 32, 32, 32, 32, 32, 101, 120, 99, 101, 112, 116, 32, 97, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 102, 111, 114, 32, 114, 101, 97, 115, 111, 110, 97, 98, 108, 101, 32, 97, 110, 100, 32, 99, 117, 115, 116, 111, 109, 97, 114, 121, 32, 117, 115, 101, 32, 105, 110, 32, 100, 101, 115, 99, 114, 105, 98, 105, 110, 103, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 111, 114, 105, 103, 105, 110, 32, 111, 102, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 114, 101, 112, 114, 111, 100, 117, 99, 105, 110, 103, 32, 116, 104, 101, 32, 99, 111, 110, 116, 101, 110, 116, 32, 111, 102, 32, 116, 104, 101, 32, 78, 79, 84, 73, 67, 69, 32, 102, 105, 108, 101, 46, 10, 10, 32, 32, 32, 55, 46, 32, 68, 105, 115, 99, 108, 97, 105, 109, 101, 114, 32, 111, 102, 32, 87, 97, 114, 114, 97, 110, 116, 121, 46, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 76, 105, 99, 101, 110, 115, 111, 114, 32, 112, 114, 111, 118, 105, 100, 101, 115, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 40, 97, 110, 100, 32, 101, 97, 99, 104, 10, 32, 32, 32, 32, 32, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 112, 114, 111, 118, 105, 100, 101, 115, 32, 105, 116, 115, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 105, 111, 110, 115, 41, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 32, 32, 32, 32, 32, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 10, 32, 32, 32, 32, 32, 32, 105, 109, 112, 108, 105, 101, 100, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 44, 32, 119, 105, 116, 104, 111, 117, 116, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 44, 32, 97, 110, 121, 32, 119, 97, 114, 114, 97, 110, 116, 105, 101, 115, 32, 111, 114, 32, 99, 111, 110, 100, 105, 116, 105, 111, 110, 115, 10, 32, 32, 32, 32, 32, 32, 111, 102, 32, 84, 73, 84, 76, 69, 44, 32, 78, 79, 78, 45, 73, 78, 70, 82, 73, 78, 71, 69, 77, 69, 78, 84, 44, 32, 77, 69, 82, 67, 72, 65, 78, 84, 65, 66, 73, 76, 73, 84, 89, 44, 32, 111, 114, 32, 70, 73, 84, 78, 69, 83, 83, 32, 70, 79, 82, 32, 65, 10, 32, 32, 32, 32, 32, 32, 80, 65, 82, 84, 73, 67, 85, 76, 65, 82, 32, 80, 85, 82, 80, 79, 83, 69, 46, 32, 89, 111, 117, 32, 97, 114, 101, 32, 115, 111, 108, 101, 108, 121, 32, 114, 101, 115, 112, 111, 110, 115, 105, 98, 108, 101, 32, 102, 111, 114, 32, 100, 101, 116, 101, 114, 109, 105, 110, 105, 110, 103, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 97, 112, 112, 114, 111, 112, 114, 105, 97, 116, 101, 110, 101, 115, 115, 32, 111, 102, 32, 117, 115, 105, 110, 103, 32, 111, 114, 32, 114, 101, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 110, 103, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 97, 110, 100, 32, 97, 115, 115, 117, 109, 101, 32, 97, 110, 121, 10, 32, 32, 32, 32, 32, 32, 114, 105, 115, 107, 115, 32, 97, 115, 115, 111, 99, 105, 97, 116, 101, 100, 32, 119, 105, 116, 104, 32, 89, 111, 117, 114, 32, 101, 120, 101, 114, 99, 105, 115, 101, 32, 111, 102, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 10, 32, 32, 32, 56, 46, 32, 76, 105, 109, 105, 116, 97, 116, 105, 111, 110, 32, 111, 102, 32, 76, 105, 97, 98, 105, 108, 105, 116, 121, 46, 32, 73, 110, 32, 110, 111, 32, 101, 118, 101, 110, 116, 32, 97, 110, 100, 32, 117, 110, 100, 101, 114, 32, 110, 111, 32, 108, 101, 103, 97, 108, 32, 116, 104, 101, 111, 114, 121, 44, 10, 32, 32, 32, 32, 32, 32, 119, 104, 101, 116, 104, 101, 114, 32, 105, 110, 32, 116, 111, 114, 116, 32, 40, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 110, 101, 103, 108, 105, 103, 101, 110, 99, 101, 41, 44, 32, 99, 111, 110, 116, 114, 97, 99, 116, 44, 32, 111, 114, 32, 111, 116, 104, 101, 114, 119, 105, 115, 101, 44, 10, 32, 32, 32, 32, 32, 32, 117, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 40, 115, 117, 99, 104, 32, 97, 115, 32, 100, 101, 108, 105, 98, 101, 114, 97, 116, 101, 32, 97, 110, 100, 32, 103, 114, 111, 115, 115, 108, 121, 10, 32, 32, 32, 32, 32, 32, 110, 101, 103, 108, 105, 103, 101, 110, 116, 32, 97, 99, 116, 115, 41, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 104, 97, 108, 108, 32, 97, 110, 121, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 98, 101, 10, 32, 32, 32, 32, 32, 32, 108, 105, 97, 98, 108, 101, 32, 116, 111, 32, 89, 111, 117, 32, 102, 111, 114, 32, 100, 97, 109, 97, 103, 101, 115, 44, 32, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 97, 110, 121, 32, 100, 105, 114, 101, 99, 116, 44, 32, 105, 110, 100, 105, 114, 101, 99, 116, 44, 32, 115, 112, 101, 99, 105, 97, 108, 44, 10, 32, 32, 32, 32, 32, 32, 105, 110, 99, 105, 100, 101, 110, 116, 97, 108, 44, 32, 111, 114, 32, 99, 111, 110, 115, 101, 113, 117, 101, 110, 116, 105, 97, 108, 32, 100, 97, 109, 97, 103, 101, 115, 32, 111, 102, 32, 97, 110, 121, 32, 99, 104, 97, 114, 97, 99, 116, 101, 114, 32, 97, 114, 105, 115, 105, 110, 103, 32, 97, 115, 32, 97, 10, 32, 32, 32, 32, 32, 32, 114, 101, 115, 117, 108, 116, 32, 111, 102, 32, 116, 104, 105, 115, 32, 76, 105, 99, 101, 110, 115, 101, 32, 111, 114, 32, 111, 117, 116, 32, 111, 102, 32, 116, 104, 101, 32, 117, 115, 101, 32, 111, 114, 32, 105, 110, 97, 98, 105, 108, 105, 116, 121, 32, 116, 111, 32, 117, 115, 101, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 87, 111, 114, 107, 32, 40, 105, 110, 99, 108, 117, 100, 105, 110, 103, 32, 98, 117, 116, 32, 110, 111, 116, 32, 108, 105, 109, 105, 116, 101, 100, 32, 116, 111, 32, 100, 97, 109, 97, 103, 101, 115, 32, 102, 111, 114, 32, 108, 111, 115, 115, 32, 111, 102, 32, 103, 111, 111, 100, 119, 105, 108, 108, 44, 10, 32, 32, 32, 32, 32, 32, 119, 111, 114, 107, 32, 115, 116, 111, 112, 112, 97, 103, 101, 44, 32, 99, 111, 109, 112, 117, 116, 101, 114, 32, 102, 97, 105, 108, 117, 114, 101, 32, 111, 114, 32, 109, 97, 108, 102, 117, 110, 99, 116, 105, 111, 110, 44, 32, 111, 114, 32, 97, 110, 121, 32, 97, 110, 100, 32, 97, 108, 108, 10, 32, 32, 32, 32, 32, 32, 111, 116, 104, 101, 114, 32, 99, 111, 109, 109, 101, 114, 99, 105, 97, 108, 32, 100, 97, 109, 97, 103, 101, 115, 32, 111, 114, 32, 108, 111, 115, 115, 101, 115, 41, 44, 32, 101, 118, 101, 110, 32, 105, 102, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 10, 32, 32, 32, 32, 32, 32, 104, 97, 115, 32, 98, 101, 101, 110, 32, 97, 100, 118, 105, 115, 101, 100, 32, 111, 102, 32, 116, 104, 101, 32, 112, 111, 115, 115, 105, 98, 105, 108, 105, 116, 121, 32, 111, 102, 32, 115, 117, 99, 104, 32, 100, 97, 109, 97, 103, 101, 115, 46, 10, 10, 32, 32, 32, 57, 46, 32, 65, 99, 99, 101, 112, 116, 105, 110, 103, 32, 87, 97, 114, 114, 97, 110, 116, 121, 32, 111, 114, 32, 65, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 76, 105, 97, 98, 105, 108, 105, 116, 121, 46, 32, 87, 104, 105, 108, 101, 32, 114, 101, 100, 105, 115, 116, 114, 105, 98, 117, 116, 105, 110, 103, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 87, 111, 114, 107, 32, 111, 114, 32, 68, 101, 114, 105, 118, 97, 116, 105, 118, 101, 32, 87, 111, 114, 107, 115, 32, 116, 104, 101, 114, 101, 111, 102, 44, 32, 89, 111, 117, 32, 109, 97, 121, 32, 99, 104, 111, 111, 115, 101, 32, 116, 111, 32, 111, 102, 102, 101, 114, 44, 10, 32, 32, 32, 32, 32, 32, 97, 110, 100, 32, 99, 104, 97, 114, 103, 101, 32, 97, 32, 102, 101, 101, 32, 102, 111, 114, 44, 32, 97, 99, 99, 101, 112, 116, 97, 110, 99, 101, 32, 111, 102, 32, 115, 117, 112, 112, 111, 114, 116, 44, 32, 119, 97, 114, 114, 97, 110, 116, 121, 44, 32, 105, 110, 100, 101, 109, 110, 105, 116, 121, 44, 10, 32, 32, 32, 32, 32, 32, 111, 114, 32, 111, 116, 104, 101, 114, 32, 108, 105, 97, 98, 105, 108, 105, 116, 121, 32, 111, 98, 108, 105, 103, 97, 116, 105, 111, 110, 115, 32, 97, 110, 100, 47, 111, 114, 32, 114, 105, 103, 104, 116, 115, 32, 99, 111, 110, 115, 105, 115, 116, 101, 110, 116, 32, 119, 105, 116, 104, 32, 116, 104, 105, 115, 10, 32, 32, 32, 32, 32, 32, 76, 105, 99, 101, 110, 115, 101, 46, 32, 72, 111, 119, 101, 118, 101, 114, 44, 32, 105, 110, 32, 97, 99, 99, 101, 112, 116, 105, 110, 103, 32, 115, 117, 99, 104, 32, 111, 98, 108, 105, 103, 97, 116, 105, 111, 110, 115, 44, 32, 89, 111, 117, 32, 109, 97, 121, 32, 97, 99, 116, 32, 111, 110, 108, 121, 10, 32, 32, 32, 32, 32, 32, 111, 110, 32, 89, 111, 117, 114, 32, 111, 119, 110, 32, 98, 101, 104, 97, 108, 102, 32, 97, 110, 100, 32, 111, 110, 32, 89, 111, 117, 114, 32, 115, 111, 108, 101, 32, 114, 101, 115, 112, 111, 110, 115, 105, 98, 105, 108, 105, 116, 121, 44, 32, 110, 111, 116, 32, 111, 110, 32, 98, 101, 104, 97, 108, 102, 10, 32, 32, 32, 32, 32, 32, 111, 102, 32, 97, 110, 121, 32, 111, 116, 104, 101, 114, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 44, 32, 97, 110, 100, 32, 111, 110, 108, 121, 32, 105, 102, 32, 89, 111, 117, 32, 97, 103, 114, 101, 101, 32, 116, 111, 32, 105, 110, 100, 101, 109, 110, 105, 102, 121, 44, 10, 32, 32, 32, 32, 32, 32, 100, 101, 102, 101, 110, 100, 44, 32, 97, 110, 100, 32, 104, 111, 108, 100, 32, 101, 97, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 104, 97, 114, 109, 108, 101, 115, 115, 32, 102, 111, 114, 32, 97, 110, 121, 32, 108, 105, 97, 98, 105, 108, 105, 116, 121, 10, 32, 32, 32, 32, 32, 32, 105, 110, 99, 117, 114, 114, 101, 100, 32, 98, 121, 44, 32, 111, 114, 32, 99, 108, 97, 105, 109, 115, 32, 97, 115, 115, 101, 114, 116, 101, 100, 32, 97, 103, 97, 105, 110, 115, 116, 44, 32, 115, 117, 99, 104, 32, 67, 111, 110, 116, 114, 105, 98, 117, 116, 111, 114, 32, 98, 121, 32, 114, 101, 97, 115, 111, 110, 10, 32, 32, 32, 32, 32, 32, 111, 102, 32, 121, 111, 117, 114, 32, 97, 99, 99, 101, 112, 116, 105, 110, 103, 32, 97, 110, 121, 32, 115, 117, 99, 104, 32, 119, 97, 114, 114, 97, 110, 116, 121, 32, 111, 114, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 32, 108, 105, 97, 98, 105, 108, 105, 116, 121, 46, 10, 10, 32, 32, 32, 69, 78, 68, 32, 79, 70, 32, 84, 69, 82, 77, 83, 32, 65, 78, 68, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 10, 10, 32, 32, 32, 65, 80, 80, 69, 78, 68, 73, 88, 58, 32, 72, 111, 119, 32, 116, 111, 32, 97, 112, 112, 108, 121, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 116, 111, 32, 121, 111, 117, 114, 32, 119, 111, 114, 107, 46, 10, 10, 32, 32, 32, 32, 32, 32, 84, 111, 32, 97, 112, 112, 108, 121, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 116, 111, 32, 121, 111, 117, 114, 32, 119, 111, 114, 107, 44, 32, 97, 116, 116, 97, 99, 104, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 10, 32, 32, 32, 32, 32, 32, 98, 111, 105, 108, 101, 114, 112, 108, 97, 116, 101, 32, 110, 111, 116, 105, 99, 101, 44, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 102, 105, 101, 108, 100, 115, 32, 101, 110, 99, 108, 111, 115, 101, 100, 32, 98, 121, 32, 98, 114, 97, 99, 107, 101, 116, 115, 32, 34, 91, 93, 34, 10, 32, 32, 32, 32, 32, 32, 114, 101, 112, 108, 97, 99, 101, 100, 32, 119, 105, 116, 104, 32, 121, 111, 117, 114, 32, 111, 119, 110, 32, 105, 100, 101, 110, 116, 105, 102, 121, 105, 110, 103, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 46, 32, 40, 68, 111, 110, 39, 116, 32, 105, 110, 99, 108, 117, 100, 101, 10, 32, 32, 32, 32, 32, 32, 116, 104, 101, 32, 98, 114, 97, 99, 107, 101, 116, 115, 33, 41, 32, 32, 84, 104, 101, 32, 116, 101, 120, 116, 32, 115, 104, 111, 117, 108, 100, 32, 98, 101, 32, 101, 110, 99, 108, 111, 115, 101, 100, 32, 105, 110, 32, 116, 104, 101, 32, 97, 112, 112, 114, 111, 112, 114, 105, 97, 116, 101, 10, 32, 32, 32, 32, 32, 32, 99, 111, 109, 109, 101, 110, 116, 32, 115, 121, 110, 116, 97, 120, 32, 102, 111, 114, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 102, 111, 114, 109, 97, 116, 46, 32, 87, 101, 32, 97, 108, 115, 111, 32, 114, 101, 99, 111, 109, 109, 101, 110, 100, 32, 116, 104, 97, 116, 32, 97, 10, 32, 32, 32, 32, 32, 32, 102, 105, 108, 101, 32, 111, 114, 32, 99, 108, 97, 115, 115, 32, 110, 97, 109, 101, 32, 97, 110, 100, 32, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 32, 111, 102, 32, 112, 117, 114, 112, 111, 115, 101, 32, 98, 101, 32, 105, 110, 99, 108, 117, 100, 101, 100, 32, 111, 110, 32, 116, 104, 101, 10, 32, 32, 32, 32, 32, 32, 115, 97, 109, 101, 32, 34, 112, 114, 105, 110, 116, 101, 100, 32, 112, 97, 103, 101, 34, 32, 97, 115, 32, 116, 104, 101, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 110, 111, 116, 105, 99, 101, 32, 102, 111, 114, 32, 101, 97, 115, 105, 101, 114, 10, 32, 32, 32, 32, 32, 32, 105, 100, 101, 110, 116, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 105, 114, 100, 45, 112, 97, 114, 116, 121, 32, 97, 114, 99, 104, 105, 118, 101, 115, 46, 10, 10, 32, 32, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 91, 121, 121, 121, 121, 93, 32, 91, 110, 97, 109, 101, 32, 111, 102, 32, 99, 111, 112, 121, 114, 105, 103, 104, 116, 32, 111, 119, 110, 101, 114, 93, 10, 10, 32, 32, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 32, 32, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 32, 32, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 10, 32, 32, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 10, 32, 32, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 32, 32, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 32, 32, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 32, 32, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 32, 32, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10}) - box.Add("/scaffoldings/golang/Makefile", []byte{71, 79, 76, 65, 78, 71, 67, 73, 76, 73, 78, 84, 86, 69, 82, 83, 73, 79, 78, 63, 61, 49, 46, 53, 48, 46, 48, 10, 71, 79, 73, 77, 80, 79, 82, 84, 83, 86, 69, 82, 83, 73, 79, 78, 63, 61, 118, 48, 46, 49, 46, 49, 50, 10, 71, 79, 88, 86, 69, 82, 83, 73, 79, 78, 63, 61, 118, 49, 46, 48, 46, 49, 10, 71, 79, 84, 69, 83, 84, 83, 85, 77, 86, 69, 82, 83, 73, 79, 78, 63, 61, 118, 49, 46, 56, 46, 50, 10, 67, 79, 86, 69, 82, 65, 71, 69, 79, 85, 84, 63, 61, 99, 111, 118, 101, 114, 97, 103, 101, 46, 111, 117, 116, 10, 67, 79, 86, 69, 82, 65, 71, 69, 72, 84, 77, 76, 63, 61, 99, 111, 118, 101, 114, 97, 103, 101, 46, 104, 116, 109, 108, 10, 71, 79, 74, 85, 78, 73, 84, 79, 85, 84, 63, 61, 103, 111, 45, 106, 117, 110, 105, 116, 46, 120, 109, 108, 10, 80, 65, 67, 75, 65, 71, 69, 78, 65, 77, 69, 63, 61, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 10, 67, 76, 73, 78, 65, 77, 69, 63, 61, 108, 97, 99, 101, 119, 111, 114, 107, 10, 71, 79, 95, 76, 68, 70, 76, 65, 71, 83, 61, 34, 45, 88, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 118, 101, 114, 115, 105, 111, 110, 46, 86, 101, 114, 115, 105, 111, 110, 61, 36, 40, 115, 104, 101, 108, 108, 32, 99, 97, 116, 32, 86, 69, 82, 83, 73, 79, 78, 41, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 88, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 118, 101, 114, 115, 105, 111, 110, 46, 71, 105, 116, 83, 72, 65, 61, 36, 40, 115, 104, 101, 108, 108, 32, 103, 105, 116, 32, 114, 101, 118, 45, 112, 97, 114, 115, 101, 32, 72, 69, 65, 68, 41, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 88, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 118, 101, 114, 115, 105, 111, 110, 46, 46, 66, 117, 105, 108, 100, 84, 105, 109, 101, 61, 36, 40, 115, 104, 101, 108, 108, 32, 100, 97, 116, 101, 32, 43, 37, 89, 37, 109, 37, 100, 37, 72, 37, 77, 37, 83, 41, 34, 10, 71, 79, 70, 76, 65, 71, 83, 61, 45, 109, 111, 100, 61, 118, 101, 110, 100, 111, 114, 10, 67, 71, 79, 95, 69, 78, 65, 66, 76, 69, 68, 63, 61, 48, 10, 101, 120, 112, 111, 114, 116, 32, 71, 79, 70, 76, 65, 71, 83, 32, 71, 79, 95, 76, 68, 70, 76, 65, 71, 83, 32, 67, 71, 79, 95, 69, 78, 65, 66, 76, 69, 68, 10, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 104, 101, 108, 112, 10, 104, 101, 108, 112, 58, 10, 9, 64, 101, 99, 104, 111, 32, 34, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 34, 10, 9, 64, 101, 99, 104, 111, 32, 34, 32, 77, 97, 107, 101, 102, 105, 108, 101, 32, 104, 101, 108, 112, 101, 114, 58, 34, 10, 9, 64, 101, 99, 104, 111, 32, 34, 34, 10, 9, 64, 103, 114, 101, 112, 32, 45, 70, 104, 32, 34, 35, 35, 34, 32, 36, 40, 77, 65, 75, 69, 70, 73, 76, 69, 95, 76, 73, 83, 84, 41, 32, 124, 32, 103, 114, 101, 112, 32, 45, 118, 32, 103, 114, 101, 112, 32, 124, 32, 115, 101, 100, 32, 45, 101, 32, 39, 115, 47, 92, 92, 36, 36, 47, 47, 39, 32, 124, 32, 115, 101, 100, 32, 45, 69, 32, 39, 115, 47, 94, 40, 91, 94, 58, 93, 42, 41, 58, 46, 42, 35, 35, 40, 46, 42, 41, 47, 32, 92, 49, 32, 45, 92, 50, 47, 39, 10, 9, 64, 101, 99, 104, 111, 32, 34, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 34, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 112, 114, 101, 112, 97, 114, 101, 10, 112, 114, 101, 112, 97, 114, 101, 58, 32, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 32, 103, 111, 45, 118, 101, 110, 100, 111, 114, 32, 35, 35, 32, 73, 110, 105, 116, 105, 97, 108, 105, 122, 101, 32, 116, 104, 101, 32, 103, 111, 32, 101, 110, 118, 105, 114, 111, 110, 109, 101, 110, 116, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 103, 111, 45, 118, 101, 110, 100, 111, 114, 10, 103, 111, 45, 118, 101, 110, 100, 111, 114, 58, 32, 35, 35, 32, 82, 117, 110, 115, 32, 103, 111, 32, 109, 111, 100, 32, 116, 105, 100, 121, 44, 32, 118, 101, 110, 100, 111, 114, 32, 97, 110, 100, 32, 118, 101, 114, 105, 102, 121, 32, 116, 111, 32, 99, 108, 101, 97, 110, 117, 112, 44, 32, 99, 111, 112, 121, 32, 97, 110, 100, 32, 118, 101, 114, 105, 102, 121, 32, 100, 101, 112, 101, 110, 100, 101, 110, 99, 105, 101, 115, 10, 9, 103, 111, 32, 109, 111, 100, 32, 116, 105, 100, 121, 10, 9, 103, 111, 32, 109, 111, 100, 32, 118, 101, 110, 100, 111, 114, 10, 9, 103, 111, 32, 109, 111, 100, 32, 118, 101, 114, 105, 102, 121, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 116, 101, 115, 116, 10, 116, 101, 115, 116, 58, 32, 112, 114, 101, 112, 97, 114, 101, 32, 35, 35, 32, 82, 117, 110, 32, 117, 110, 105, 116, 32, 116, 101, 115, 116, 115, 10, 9, 103, 111, 116, 101, 115, 116, 115, 117, 109, 32, 45, 102, 32, 116, 101, 115, 116, 110, 97, 109, 101, 32, 45, 45, 32, 45, 118, 32, 45, 99, 111, 118, 101, 114, 32, 45, 114, 117, 110, 61, 36, 40, 114, 101, 103, 101, 120, 41, 32, 45, 99, 111, 118, 101, 114, 112, 114, 111, 102, 105, 108, 101, 61, 36, 40, 67, 79, 86, 69, 82, 65, 71, 69, 79, 85, 84, 41, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 46, 47, 46, 46, 46, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 105, 109, 112, 111, 114, 116, 115, 45, 99, 104, 101, 99, 107, 10, 105, 109, 112, 111, 114, 116, 115, 45, 99, 104, 101, 99, 107, 58, 32, 35, 35, 32, 76, 105, 115, 116, 115, 32, 105, 109, 112, 111, 114, 116, 115, 32, 105, 115, 115, 117, 101, 115, 10, 9, 64, 116, 101, 115, 116, 32, 45, 122, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 105, 109, 112, 111, 114, 116, 115, 32, 45, 108, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 45, 102, 32, 123, 123, 46, 68, 105, 114, 125, 125, 32, 46, 47, 46, 46, 46, 41, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 108, 105, 110, 116, 10, 108, 105, 110, 116, 58, 32, 35, 35, 32, 82, 117, 110, 115, 32, 103, 111, 32, 108, 105, 110, 116, 101, 114, 10, 9, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 32, 114, 117, 110, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 102, 109, 116, 10, 102, 109, 116, 58, 32, 35, 35, 32, 82, 117, 110, 115, 32, 97, 110, 100, 32, 97, 112, 112, 108, 105, 101, 115, 32, 103, 111, 32, 102, 111, 114, 109, 97, 116, 116, 105, 110, 103, 32, 99, 104, 97, 110, 103, 101, 115, 10, 9, 64, 103, 111, 102, 109, 116, 32, 45, 119, 32, 45, 108, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 45, 102, 32, 123, 123, 46, 68, 105, 114, 125, 125, 32, 46, 47, 46, 46, 46, 41, 10, 9, 64, 103, 111, 105, 109, 112, 111, 114, 116, 115, 32, 45, 119, 32, 45, 108, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 45, 102, 32, 123, 123, 46, 68, 105, 114, 125, 125, 32, 46, 47, 46, 46, 46, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 102, 109, 116, 45, 99, 104, 101, 99, 107, 10, 102, 109, 116, 45, 99, 104, 101, 99, 107, 58, 32, 35, 35, 32, 76, 105, 115, 116, 115, 32, 102, 111, 114, 109, 97, 116, 116, 105, 110, 103, 32, 105, 115, 115, 117, 101, 115, 10, 9, 64, 116, 101, 115, 116, 32, 45, 122, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 102, 109, 116, 32, 45, 108, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 108, 105, 115, 116, 32, 45, 102, 32, 123, 123, 46, 68, 105, 114, 125, 125, 32, 46, 47, 46, 46, 46, 41, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 98, 117, 105, 108, 100, 10, 98, 117, 105, 108, 100, 58, 32, 35, 35, 32, 67, 111, 109, 112, 105, 108, 101, 115, 32, 98, 105, 110, 97, 114, 121, 32, 102, 111, 114, 32, 116, 104, 101, 32, 114, 117, 110, 110, 105, 110, 103, 32, 119, 111, 114, 107, 115, 116, 97, 116, 105, 111, 110, 32, 40, 67, 68, 75, 32, 115, 117, 112, 112, 111, 114, 116, 41, 10, 9, 103, 111, 32, 98, 117, 105, 108, 100, 32, 46, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 98, 117, 105, 108, 100, 45, 99, 114, 111, 115, 115, 45, 112, 108, 97, 116, 102, 111, 114, 109, 10, 98, 117, 105, 108, 100, 45, 99, 114, 111, 115, 115, 45, 112, 108, 97, 116, 102, 111, 114, 109, 58, 32, 35, 35, 32, 67, 111, 109, 112, 105, 108, 101, 115, 32, 98, 105, 110, 97, 114, 105, 101, 115, 32, 102, 111, 114, 32, 97, 108, 108, 32, 115, 117, 112, 112, 111, 114, 116, 101, 100, 32, 112, 108, 97, 116, 102, 111, 114, 109, 115, 10, 9, 103, 111, 120, 32, 45, 111, 117, 116, 112, 117, 116, 61, 34, 98, 105, 110, 47, 36, 40, 80, 65, 67, 75, 65, 71, 69, 78, 65, 77, 69, 41, 45, 123, 123, 46, 79, 83, 125, 125, 45, 123, 123, 46, 65, 114, 99, 104, 125, 125, 34, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 111, 115, 61, 34, 108, 105, 110, 117, 120, 32, 119, 105, 110, 100, 111, 119, 115, 34, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 97, 114, 99, 104, 61, 34, 97, 109, 100, 54, 52, 32, 51, 56, 54, 34, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 111, 115, 97, 114, 99, 104, 61, 34, 100, 97, 114, 119, 105, 110, 47, 97, 109, 100, 54, 52, 32, 100, 97, 114, 119, 105, 110, 47, 97, 114, 109, 54, 52, 32, 108, 105, 110, 117, 120, 47, 97, 114, 109, 32, 108, 105, 110, 117, 120, 47, 97, 114, 109, 54, 52, 34, 32, 92, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 108, 100, 102, 108, 97, 103, 115, 61, 36, 40, 71, 79, 95, 76, 68, 70, 76, 65, 71, 83, 41, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 10, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 58, 32, 35, 35, 32, 73, 110, 115, 116, 97, 108, 108, 32, 103, 111, 32, 105, 110, 100, 105, 114, 101, 99, 116, 32, 100, 101, 112, 101, 110, 100, 101, 110, 99, 105, 101, 115, 10, 105, 102, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 41, 41, 10, 9, 99, 117, 114, 108, 32, 45, 115, 83, 102, 76, 32, 104, 116, 116, 112, 115, 58, 47, 47, 114, 97, 119, 46, 103, 105, 116, 104, 117, 98, 117, 115, 101, 114, 99, 111, 110, 116, 101, 110, 116, 46, 99, 111, 109, 47, 103, 111, 108, 97, 110, 103, 99, 105, 47, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 47, 109, 97, 115, 116, 101, 114, 47, 105, 110, 115, 116, 97, 108, 108, 46, 115, 104, 32, 124, 32, 115, 104, 32, 45, 115, 32, 45, 45, 32, 45, 98, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 32, 118, 36, 40, 71, 79, 76, 65, 78, 71, 67, 73, 76, 73, 78, 84, 86, 69, 82, 83, 73, 79, 78, 41, 10, 101, 110, 100, 105, 102, 10, 105, 102, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 105, 109, 112, 111, 114, 116, 115, 41, 41, 10, 9, 71, 79, 70, 76, 65, 71, 83, 61, 45, 109, 111, 100, 61, 114, 101, 97, 100, 111, 110, 108, 121, 32, 103, 111, 32, 105, 110, 115, 116, 97, 108, 108, 32, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 120, 47, 116, 111, 111, 108, 115, 47, 99, 109, 100, 47, 103, 111, 105, 109, 112, 111, 114, 116, 115, 64, 36, 40, 71, 79, 73, 77, 80, 79, 82, 84, 83, 86, 69, 82, 83, 73, 79, 78, 41, 10, 101, 110, 100, 105, 102, 10, 105, 102, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 120, 41, 41, 10, 9, 71, 79, 70, 76, 65, 71, 83, 61, 45, 109, 111, 100, 61, 114, 101, 97, 100, 111, 110, 108, 121, 32, 103, 111, 32, 105, 110, 115, 116, 97, 108, 108, 32, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 109, 105, 116, 99, 104, 101, 108, 108, 104, 47, 103, 111, 120, 64, 36, 40, 71, 79, 88, 86, 69, 82, 83, 73, 79, 78, 41, 10, 101, 110, 100, 105, 102, 10, 105, 102, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 116, 101, 115, 116, 115, 117, 109, 41, 41, 10, 9, 71, 79, 70, 76, 65, 71, 83, 61, 45, 109, 111, 100, 61, 114, 101, 97, 100, 111, 110, 108, 121, 32, 103, 111, 32, 105, 110, 115, 116, 97, 108, 108, 32, 103, 111, 116, 101, 115, 116, 46, 116, 111, 111, 108, 115, 47, 103, 111, 116, 101, 115, 116, 115, 117, 109, 64, 36, 40, 71, 79, 84, 69, 83, 84, 83, 85, 77, 86, 69, 82, 83, 73, 79, 78, 41, 10, 101, 110, 100, 105, 102, 10, 10, 46, 80, 72, 79, 78, 89, 58, 32, 117, 110, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 10, 117, 110, 105, 110, 115, 116, 97, 108, 108, 45, 116, 111, 111, 108, 115, 58, 32, 35, 35, 32, 85, 110, 105, 110, 115, 116, 97, 108, 108, 32, 103, 111, 32, 105, 110, 100, 105, 114, 101, 99, 116, 32, 100, 101, 112, 101, 110, 100, 101, 110, 99, 105, 101, 115, 10, 105, 102, 110, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 41, 41, 10, 9, 114, 109, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 47, 103, 111, 108, 97, 110, 103, 99, 105, 45, 108, 105, 110, 116, 10, 101, 110, 100, 105, 102, 10, 105, 102, 110, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 105, 109, 112, 111, 114, 116, 115, 41, 41, 10, 9, 114, 109, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 47, 103, 111, 105, 109, 112, 111, 114, 116, 115, 10, 101, 110, 100, 105, 102, 10, 105, 102, 110, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 120, 41, 41, 10, 9, 114, 109, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 47, 103, 111, 120, 10, 101, 110, 100, 105, 102, 10, 105, 102, 110, 101, 113, 32, 40, 44, 32, 36, 40, 115, 104, 101, 108, 108, 32, 119, 104, 105, 99, 104, 32, 103, 111, 116, 101, 115, 116, 115, 117, 109, 41, 41, 10, 9, 114, 109, 32, 36, 40, 115, 104, 101, 108, 108, 32, 103, 111, 32, 101, 110, 118, 32, 71, 79, 80, 65, 84, 72, 41, 47, 98, 105, 110, 47, 103, 111, 116, 101, 115, 116, 115, 117, 109, 10, 101, 110, 100, 105, 102, 10}) - box.Add("/scaffoldings/golang/VERSION", []byte{48, 46, 48, 46, 49, 45, 100, 101, 118, 10}) - box.Add("/scaffoldings/golang/bin/.gitkeeper", []byte{}) - box.Add("/scaffoldings/golang/cmd/README.md", []byte{35, 32, 96, 47, 99, 109, 100, 96, 10, 10, 77, 97, 105, 110, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 115, 32, 102, 111, 114, 32, 116, 104, 105, 115, 32, 112, 114, 111, 106, 101, 99, 116, 46}) - box.Add("/scaffoldings/golang/cmd/cdk.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 99, 109, 100, 10, 10, 105, 109, 112, 111, 114, 116, 32, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 111, 98, 114, 97, 34, 10, 10, 118, 97, 114, 32, 40, 10, 9, 99, 100, 107, 73, 110, 105, 116, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 9, 85, 115, 101, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 99, 100, 107, 45, 105, 110, 105, 116, 34, 44, 10, 9, 9, 83, 104, 111, 114, 116, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 105, 110, 116, 101, 114, 110, 97, 108, 32, 117, 115, 101, 32, 111, 110, 108, 121, 34, 44, 10, 9, 9, 76, 111, 110, 103, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 96, 73, 110, 116, 101, 114, 110, 97, 108, 32, 99, 111, 109, 109, 97, 110, 100, 32, 116, 104, 97, 116, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 101, 115, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 116, 111, 32, 98, 101, 32, 117, 115, 101, 100, 32, 98, 121, 32, 116, 104, 101, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 67, 76, 73, 32, 97, 115, 32, 97, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 46, 96, 44, 10, 9, 9, 72, 105, 100, 100, 101, 110, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 68, 105, 115, 97, 98, 108, 101, 65, 117, 116, 111, 71, 101, 110, 84, 97, 103, 58, 32, 116, 114, 117, 101, 44, 10, 9, 9, 83, 105, 108, 101, 110, 99, 101, 69, 114, 114, 111, 114, 115, 58, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 82, 117, 110, 69, 58, 32, 102, 117, 110, 99, 40, 95, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 95, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 9, 9, 47, 47, 32, 84, 104, 105, 115, 32, 101, 118, 101, 110, 116, 32, 105, 115, 32, 111, 110, 108, 121, 32, 101, 120, 101, 99, 117, 116, 101, 100, 32, 97, 102, 116, 101, 114, 32, 116, 104, 105, 115, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 105, 115, 32, 105, 110, 115, 116, 97, 108, 108, 101, 100, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 85, 115, 101, 32, 116, 104, 105, 115, 32, 101, 118, 101, 110, 116, 32, 116, 111, 32, 100, 101, 112, 108, 111, 121, 32, 97, 110, 121, 32, 110, 101, 99, 101, 115, 115, 97, 114, 121, 32, 102, 105, 108, 101, 44, 32, 99, 97, 99, 104, 101, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 44, 32, 116, 111, 32, 105, 110, 115, 116, 97, 108, 108, 32, 97, 100, 100, 105, 116, 105, 111, 110, 97, 108, 10, 9, 9, 9, 47, 47, 32, 108, 105, 98, 114, 97, 114, 105, 101, 115, 32, 112, 97, 99, 107, 97, 103, 101, 100, 32, 119, 105, 116, 104, 105, 110, 32, 116, 104, 101, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 46, 32, 65, 110, 121, 116, 104, 105, 110, 103, 32, 116, 104, 97, 116, 32, 121, 111, 117, 32, 110, 101, 101, 100, 32, 116, 111, 32, 100, 111, 32, 98, 101, 102, 111, 114, 101, 32, 116, 104, 101, 32, 117, 115, 101, 114, 10, 9, 9, 9, 47, 47, 32, 115, 116, 97, 114, 116, 115, 32, 101, 120, 101, 99, 117, 116, 105, 110, 103, 32, 99, 111, 109, 109, 97, 110, 100, 115, 46, 10, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 77, 111, 114, 101, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 97, 116, 58, 10, 9, 9, 9, 47, 47, 32, 61, 62, 32, 104, 116, 116, 112, 115, 58, 47, 47, 108, 97, 99, 101, 119, 111, 114, 107, 46, 97, 116, 108, 97, 115, 115, 105, 97, 110, 46, 110, 101, 116, 47, 108, 47, 99, 112, 47, 66, 109, 48, 90, 114, 109, 82, 71, 10, 9, 9, 9, 114, 101, 116, 117, 114, 110, 32, 110, 105, 108, 10, 9, 9, 125, 44, 10, 9, 125, 10, 10, 9, 99, 100, 107, 82, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 9, 85, 115, 101, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 99, 100, 107, 45, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 32, 60, 99, 117, 114, 114, 101, 110, 116, 95, 118, 101, 114, 115, 105, 111, 110, 62, 32, 60, 110, 101, 119, 95, 111, 114, 95, 111, 108, 100, 101, 114, 95, 118, 101, 114, 115, 105, 111, 110, 62, 34, 44, 10, 9, 9, 83, 104, 111, 114, 116, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 105, 110, 116, 101, 114, 110, 97, 108, 32, 117, 115, 101, 32, 111, 110, 108, 121, 34, 44, 10, 9, 9, 76, 111, 110, 103, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 96, 73, 110, 116, 101, 114, 110, 97, 108, 32, 99, 111, 109, 109, 97, 110, 100, 32, 116, 104, 97, 116, 32, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 115, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 119, 104, 101, 110, 32, 105, 116, 32, 105, 115, 32, 117, 112, 100, 97, 116, 101, 100, 32, 111, 114, 32, 100, 111, 119, 110, 103, 114, 97, 100, 101, 100, 46, 96, 44, 10, 9, 9, 72, 105, 100, 100, 101, 110, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 68, 105, 115, 97, 98, 108, 101, 65, 117, 116, 111, 71, 101, 110, 84, 97, 103, 58, 32, 116, 114, 117, 101, 44, 10, 9, 9, 83, 105, 108, 101, 110, 99, 101, 69, 114, 114, 111, 114, 115, 58, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 82, 117, 110, 69, 58, 32, 102, 117, 110, 99, 40, 95, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 95, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 9, 9, 47, 47, 32, 84, 104, 101, 114, 101, 32, 97, 114, 101, 32, 116, 119, 111, 32, 112, 108, 97, 99, 101, 115, 32, 119, 104, 101, 114, 101, 32, 116, 104, 105, 115, 32, 101, 118, 101, 110, 116, 32, 105, 115, 32, 117, 115, 101, 100, 59, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 32, 49, 41, 32, 87, 104, 101, 110, 32, 97, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 105, 115, 32, 117, 112, 100, 97, 116, 101, 100, 32, 97, 110, 100, 44, 10, 9, 9, 9, 47, 47, 32, 32, 50, 41, 32, 87, 104, 101, 110, 32, 97, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 105, 115, 32, 100, 111, 119, 110, 103, 114, 97, 100, 101, 100, 32, 116, 111, 32, 97, 110, 32, 111, 108, 100, 101, 114, 32, 118, 101, 114, 115, 105, 111, 110, 32, 40, 114, 111, 108, 108, 98, 97, 99, 107, 41, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 82, 111, 108, 108, 98, 97, 99, 107, 115, 32, 97, 114, 101, 32, 110, 101, 101, 100, 101, 100, 32, 109, 111, 115, 116, 108, 121, 32, 102, 111, 114, 32, 119, 104, 101, 110, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 115, 32, 97, 114, 101, 32, 117, 112, 100, 97, 116, 101, 100, 32, 116, 111, 32, 97, 32, 118, 101, 114, 115, 105, 111, 110, 32, 119, 105, 116, 104, 32, 97, 32, 98, 117, 103, 32, 111, 114, 10, 9, 9, 9, 47, 47, 32, 115, 111, 109, 101, 116, 104, 105, 110, 103, 32, 116, 104, 97, 116, 32, 112, 114, 101, 118, 101, 110, 116, 115, 32, 116, 104, 101, 32, 117, 115, 101, 114, 32, 116, 111, 32, 99, 111, 110, 116, 105, 110, 117, 101, 32, 117, 115, 105, 110, 103, 32, 116, 104, 101, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 112, 114, 111, 100, 117, 99, 116, 105, 118, 101, 108, 121, 46, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 78, 111, 116, 101, 32, 116, 104, 97, 116, 32, 119, 104, 101, 110, 32, 116, 104, 105, 115, 32, 108, 105, 102, 101, 99, 121, 99, 108, 101, 32, 101, 118, 101, 110, 116, 32, 105, 115, 32, 101, 120, 101, 99, 117, 116, 101, 100, 44, 32, 105, 116, 32, 114, 101, 99, 101, 105, 118, 101, 115, 32, 116, 119, 111, 32, 97, 114, 103, 117, 109, 101, 110, 116, 115, 44, 32, 116, 104, 101, 32, 99, 117, 114, 114, 101, 110, 116, 10, 9, 9, 9, 47, 47, 32, 118, 101, 114, 115, 105, 111, 110, 32, 97, 110, 100, 32, 116, 104, 101, 32, 118, 101, 114, 115, 105, 111, 110, 32, 116, 111, 32, 117, 112, 100, 97, 116, 101, 32, 111, 114, 32, 114, 111, 108, 108, 98, 97, 99, 107, 32, 116, 104, 101, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 46, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 77, 111, 114, 101, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 97, 116, 58, 10, 9, 9, 9, 47, 47, 32, 61, 62, 32, 104, 116, 116, 112, 115, 58, 47, 47, 108, 97, 99, 101, 119, 111, 114, 107, 46, 97, 116, 108, 97, 115, 115, 105, 97, 110, 46, 110, 101, 116, 47, 108, 47, 99, 112, 47, 66, 109, 48, 90, 114, 109, 82, 71, 10, 9, 9, 9, 114, 101, 116, 117, 114, 110, 32, 110, 105, 108, 10, 9, 9, 125, 44, 10, 9, 125, 10, 10, 9, 99, 100, 107, 67, 108, 101, 97, 110, 117, 112, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 9, 85, 115, 101, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 99, 100, 107, 45, 99, 108, 101, 97, 110, 117, 112, 34, 44, 10, 9, 9, 83, 104, 111, 114, 116, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 105, 110, 116, 101, 114, 110, 97, 108, 32, 117, 115, 101, 32, 111, 110, 108, 121, 34, 44, 10, 9, 9, 76, 111, 110, 103, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 96, 73, 110, 116, 101, 114, 110, 97, 108, 32, 99, 111, 109, 109, 97, 110, 100, 32, 116, 104, 97, 116, 32, 99, 108, 101, 97, 110, 115, 32, 97, 110, 121, 116, 104, 105, 110, 103, 32, 116, 104, 97, 116, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 100, 101, 112, 108, 111, 121, 101, 100, 32, 100, 117, 114, 105, 110, 103, 32, 99, 100, 107, 45, 105, 110, 105, 116, 32, 111, 114, 32, 99, 100, 107, 45, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 32, 101, 118, 101, 110, 116, 115, 46, 96, 44, 10, 9, 9, 72, 105, 100, 100, 101, 110, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 68, 105, 115, 97, 98, 108, 101, 65, 117, 116, 111, 71, 101, 110, 84, 97, 103, 58, 32, 116, 114, 117, 101, 44, 10, 9, 9, 83, 105, 108, 101, 110, 99, 101, 69, 114, 114, 111, 114, 115, 58, 32, 32, 32, 32, 32, 116, 114, 117, 101, 44, 10, 9, 9, 82, 117, 110, 69, 58, 32, 102, 117, 110, 99, 40, 95, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 95, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 9, 9, 47, 47, 32, 66, 101, 102, 111, 114, 101, 32, 116, 104, 105, 115, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 105, 115, 32, 114, 101, 109, 111, 118, 101, 100, 32, 40, 117, 110, 105, 110, 115, 116, 97, 108, 108, 101, 100, 41, 44, 32, 116, 104, 105, 115, 32, 108, 105, 102, 101, 99, 121, 99, 108, 101, 32, 101, 118, 101, 110, 116, 32, 105, 115, 32, 101, 120, 101, 99, 117, 116, 101, 100, 46, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 72, 101, 114, 101, 32, 105, 115, 32, 119, 104, 101, 114, 101, 32, 121, 111, 117, 32, 115, 104, 111, 117, 108, 100, 32, 112, 101, 114, 102, 111, 114, 109, 32, 97, 32, 99, 108, 101, 97, 110, 117, 112, 32, 111, 102, 32, 97, 110, 121, 116, 104, 105, 110, 103, 32, 116, 104, 97, 116, 32, 119, 97, 115, 32, 100, 101, 112, 108, 111, 121, 101, 100, 32, 100, 117, 114, 105, 110, 103, 32, 116, 104, 101, 10, 9, 9, 9, 47, 47, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 32, 40, 99, 100, 107, 45, 105, 110, 105, 116, 41, 32, 97, 110, 100, 47, 111, 114, 32, 116, 104, 101, 32, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 97, 116, 105, 111, 110, 32, 40, 99, 100, 107, 45, 114, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 41, 32, 101, 118, 101, 110, 116, 115, 46, 10, 9, 9, 9, 47, 47, 10, 9, 9, 9, 47, 47, 32, 77, 111, 114, 101, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 32, 97, 116, 58, 10, 9, 9, 9, 47, 47, 32, 61, 62, 32, 104, 116, 116, 112, 115, 58, 47, 47, 108, 97, 99, 101, 119, 111, 114, 107, 46, 97, 116, 108, 97, 115, 115, 105, 97, 110, 46, 110, 101, 116, 47, 108, 47, 99, 112, 47, 66, 109, 48, 90, 114, 109, 82, 71, 10, 9, 9, 9, 114, 101, 116, 117, 114, 110, 32, 110, 105, 108, 10, 9, 9, 125, 44, 10, 9, 125, 10, 41, 10, 10, 102, 117, 110, 99, 32, 105, 110, 105, 116, 40, 41, 32, 123, 10, 9, 47, 47, 32, 97, 100, 100, 32, 116, 104, 101, 32, 99, 111, 109, 109, 97, 110, 100, 115, 32, 102, 111, 114, 32, 116, 104, 101, 32, 67, 68, 75, 32, 108, 105, 102, 101, 99, 121, 99, 108, 101, 32, 101, 118, 101, 110, 116, 115, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 65, 100, 100, 67, 111, 109, 109, 97, 110, 100, 40, 99, 100, 107, 73, 110, 105, 116, 67, 109, 100, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 65, 100, 100, 67, 111, 109, 109, 97, 110, 100, 40, 99, 100, 107, 82, 101, 99, 111, 110, 102, 105, 103, 117, 114, 101, 67, 109, 100, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 65, 100, 100, 67, 111, 109, 109, 97, 110, 100, 40, 99, 100, 107, 67, 108, 101, 97, 110, 117, 112, 67, 109, 100, 41, 10, 125, 10}) - box.Add("/scaffoldings/golang/cmd/placeholder.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 99, 109, 100, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 102, 109, 116, 34, 10, 10, 9, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 111, 98, 114, 97, 34, 10, 41, 10, 10, 118, 97, 114, 32, 40, 10, 9, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 9, 85, 115, 101, 58, 32, 32, 32, 34, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 34, 44, 10, 9, 9, 83, 104, 111, 114, 116, 58, 32, 34, 65, 32, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 32, 99, 111, 109, 109, 97, 110, 100, 34, 44, 10, 9, 9, 76, 111, 110, 103, 58, 32, 32, 96, 84, 104, 105, 115, 32, 105, 115, 32, 106, 117, 115, 116, 32, 97, 110, 32, 101, 120, 97, 109, 112, 108, 101, 32, 111, 102, 32, 97, 32, 99, 111, 109, 109, 97, 110, 100, 46, 96, 44, 10, 9, 9, 82, 117, 110, 69, 58, 32, 102, 117, 110, 99, 40, 95, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 95, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 9, 9, 102, 109, 116, 46, 80, 114, 105, 110, 116, 108, 110, 40, 34, 89, 111, 117, 114, 32, 110, 101, 119, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 39, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 39, 32, 105, 115, 32, 114, 101, 97, 100, 121, 33, 34, 41, 10, 9, 9, 9, 114, 101, 116, 117, 114, 110, 32, 110, 105, 108, 10, 9, 9, 125, 44, 10, 9, 125, 10, 41, 10, 10, 102, 117, 110, 99, 32, 105, 110, 105, 116, 40, 41, 32, 123, 10, 9, 47, 47, 32, 97, 100, 100, 32, 116, 104, 101, 32, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 32, 99, 111, 109, 109, 97, 110, 100, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 65, 100, 100, 67, 111, 109, 109, 97, 110, 100, 40, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 67, 109, 100, 41, 10, 125, 10}) - box.Add("/scaffoldings/golang/cmd/root.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 99, 109, 100, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 102, 109, 116, 34, 10, 9, 34, 111, 115, 34, 10, 10, 9, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 111, 98, 114, 97, 34, 10, 9, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 118, 105, 112, 101, 114, 34, 10, 41, 10, 10, 47, 47, 32, 114, 111, 111, 116, 67, 109, 100, 32, 114, 101, 112, 114, 101, 115, 101, 110, 116, 115, 32, 116, 104, 101, 32, 98, 97, 115, 101, 32, 99, 111, 109, 109, 97, 110, 100, 32, 119, 104, 101, 110, 32, 99, 97, 108, 108, 101, 100, 32, 119, 105, 116, 104, 111, 117, 116, 32, 97, 110, 121, 32, 115, 117, 98, 99, 111, 109, 109, 97, 110, 100, 115, 10, 118, 97, 114, 32, 114, 111, 111, 116, 67, 109, 100, 32, 61, 32, 38, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 123, 10, 9, 85, 115, 101, 58, 32, 32, 32, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 34, 44, 10, 9, 83, 104, 111, 114, 116, 58, 32, 34, 65, 32, 98, 114, 105, 101, 102, 32, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 32, 111, 102, 32, 121, 111, 117, 114, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 34, 44, 10, 9, 76, 111, 110, 103, 58, 32, 96, 65, 32, 108, 111, 110, 103, 101, 114, 32, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 32, 116, 104, 97, 116, 32, 115, 112, 97, 110, 115, 32, 109, 117, 108, 116, 105, 112, 108, 101, 32, 108, 105, 110, 101, 115, 32, 97, 110, 100, 32, 108, 105, 107, 101, 108, 121, 32, 99, 111, 110, 116, 97, 105, 110, 115, 10, 101, 120, 97, 109, 112, 108, 101, 115, 32, 97, 110, 100, 32, 117, 115, 97, 103, 101, 32, 111, 102, 32, 117, 115, 105, 110, 103, 32, 121, 111, 117, 114, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 46, 32, 70, 111, 114, 32, 101, 120, 97, 109, 112, 108, 101, 58, 10, 10, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 105, 115, 32, 97, 32, 115, 99, 97, 102, 102, 111, 108, 100, 105, 110, 103, 32, 102, 111, 114, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 67, 68, 75, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 115, 46, 32, 73, 116, 32, 104, 101, 108, 112, 115, 32, 100, 101, 118, 101, 108, 111, 112, 101, 114, 115, 10, 99, 114, 101, 97, 116, 101, 32, 110, 101, 119, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 115, 32, 102, 97, 115, 116, 32, 98, 121, 32, 97, 117, 116, 111, 103, 101, 110, 101, 114, 97, 116, 105, 110, 103, 32, 97, 32, 115, 107, 101, 108, 101, 116, 111, 110, 32, 119, 105, 116, 104, 32, 98, 111, 105, 108, 101, 114, 112, 108, 97, 116, 101, 32, 99, 111, 100, 101, 46, 10, 10, 84, 111, 32, 113, 117, 105, 99, 107, 108, 121, 32, 103, 101, 116, 32, 103, 111, 105, 110, 103, 32, 119, 105, 116, 104, 32, 116, 104, 105, 115, 32, 110, 101, 119, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 44, 32, 115, 116, 97, 114, 116, 32, 109, 111, 100, 105, 102, 121, 105, 110, 103, 32, 116, 104, 101, 32, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 32, 99, 111, 109, 109, 97, 110, 100, 58, 10, 10, 32, 32, 32, 32, 108, 97, 99, 101, 119, 111, 114, 107, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 32, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 10, 96, 44, 10, 10, 9, 47, 47, 32, 78, 79, 84, 69, 58, 32, 73, 102, 32, 121, 111, 117, 114, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 100, 111, 101, 115, 32, 78, 79, 84, 32, 110, 101, 101, 100, 32, 109, 117, 108, 116, 105, 112, 108, 101, 32, 99, 111, 109, 109, 97, 110, 100, 115, 44, 32, 117, 110, 99, 111, 109, 109, 101, 110, 116, 10, 9, 47, 47, 32, 32, 32, 32, 32, 32, 32, 116, 104, 105, 115, 32, 108, 105, 110, 101, 32, 97, 110, 100, 32, 114, 101, 109, 111, 118, 101, 32, 116, 104, 101, 32, 102, 105, 108, 101, 32, 39, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 46, 103, 111, 39, 10, 9, 47, 47, 32, 82, 117, 110, 58, 32, 102, 117, 110, 99, 40, 99, 109, 100, 32, 42, 99, 111, 98, 114, 97, 46, 67, 111, 109, 109, 97, 110, 100, 44, 32, 97, 114, 103, 115, 32, 91, 93, 115, 116, 114, 105, 110, 103, 41, 32, 123, 125, 44, 10, 125, 10, 10, 47, 47, 32, 69, 120, 101, 99, 117, 116, 101, 32, 97, 100, 100, 115, 32, 97, 108, 108, 32, 99, 104, 105, 108, 100, 32, 99, 111, 109, 109, 97, 110, 100, 115, 32, 116, 111, 32, 116, 104, 101, 32, 114, 111, 111, 116, 32, 99, 111, 109, 109, 97, 110, 100, 32, 97, 110, 100, 32, 115, 101, 116, 115, 32, 102, 108, 97, 103, 115, 32, 97, 112, 112, 114, 111, 112, 114, 105, 97, 116, 101, 108, 121, 46, 10, 47, 47, 32, 84, 104, 105, 115, 32, 105, 115, 32, 99, 97, 108, 108, 101, 100, 32, 98, 121, 32, 109, 97, 105, 110, 46, 109, 97, 105, 110, 40, 41, 46, 32, 73, 116, 32, 111, 110, 108, 121, 32, 110, 101, 101, 100, 115, 32, 116, 111, 32, 104, 97, 112, 112, 101, 110, 32, 111, 110, 99, 101, 32, 116, 111, 32, 116, 104, 101, 32, 114, 111, 111, 116, 67, 109, 100, 46, 10, 102, 117, 110, 99, 32, 69, 120, 101, 99, 117, 116, 101, 40, 41, 32, 123, 10, 9, 101, 114, 114, 32, 58, 61, 32, 114, 111, 111, 116, 67, 109, 100, 46, 69, 120, 101, 99, 117, 116, 101, 40, 41, 10, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 111, 115, 46, 69, 120, 105, 116, 40, 49, 41, 10, 9, 125, 10, 125, 10, 10, 102, 117, 110, 99, 32, 105, 110, 105, 116, 40, 41, 32, 123, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 100, 101, 98, 117, 103, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 116, 117, 114, 110, 32, 111, 110, 32, 100, 101, 98, 117, 103, 32, 108, 111, 103, 103, 105, 110, 103, 34, 44, 10, 9, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 110, 111, 99, 111, 108, 111, 114, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 116, 117, 114, 110, 32, 111, 102, 102, 32, 99, 111, 108, 111, 114, 115, 34, 44, 10, 9, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 110, 111, 99, 97, 99, 104, 101, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 116, 117, 114, 110, 32, 111, 102, 102, 32, 99, 97, 99, 104, 105, 110, 103, 34, 44, 10, 9, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 110, 111, 110, 105, 110, 116, 101, 114, 97, 99, 116, 105, 118, 101, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 116, 117, 114, 110, 32, 111, 102, 102, 32, 105, 110, 116, 101, 114, 97, 99, 116, 105, 118, 101, 32, 109, 111, 100, 101, 32, 40, 100, 105, 115, 97, 98, 108, 101, 32, 115, 112, 105, 110, 110, 101, 114, 115, 44, 32, 112, 114, 111, 109, 112, 116, 115, 44, 32, 101, 116, 99, 46, 41, 34, 44, 10, 9, 41, 10, 9, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 66, 111, 111, 108, 40, 34, 106, 115, 111, 110, 34, 44, 32, 102, 97, 108, 115, 101, 44, 10, 9, 9, 34, 115, 119, 105, 116, 99, 104, 32, 99, 111, 109, 109, 97, 110, 100, 115, 32, 111, 117, 116, 112, 117, 116, 32, 102, 114, 111, 109, 32, 104, 117, 109, 97, 110, 45, 114, 101, 97, 100, 97, 98, 108, 101, 32, 116, 111, 32, 106, 115, 111, 110, 32, 102, 111, 114, 109, 97, 116, 34, 44, 10, 9, 41, 10, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 100, 101, 98, 117, 103, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 100, 101, 98, 117, 103, 34, 41, 41, 41, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 110, 111, 99, 111, 108, 111, 114, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 110, 111, 99, 111, 108, 111, 114, 34, 41, 41, 41, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 110, 111, 99, 97, 99, 104, 101, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 110, 111, 99, 97, 99, 104, 101, 34, 41, 41, 41, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 110, 111, 110, 105, 110, 116, 101, 114, 97, 99, 116, 105, 118, 101, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 110, 111, 110, 105, 110, 116, 101, 114, 97, 99, 116, 105, 118, 101, 34, 41, 41, 41, 10, 9, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 118, 105, 112, 101, 114, 46, 66, 105, 110, 100, 80, 70, 108, 97, 103, 40, 34, 106, 115, 111, 110, 34, 44, 32, 114, 111, 111, 116, 67, 109, 100, 46, 80, 101, 114, 115, 105, 115, 116, 101, 110, 116, 70, 108, 97, 103, 115, 40, 41, 46, 76, 111, 111, 107, 117, 112, 40, 34, 106, 115, 111, 110, 34, 41, 41, 41, 10, 10, 9, 47, 47, 32, 72, 101, 114, 101, 32, 121, 111, 117, 32, 119, 105, 108, 108, 32, 100, 101, 102, 105, 110, 101, 32, 121, 111, 117, 114, 32, 102, 108, 97, 103, 115, 32, 97, 110, 100, 32, 99, 111, 110, 102, 105, 103, 117, 114, 97, 116, 105, 111, 110, 32, 115, 101, 116, 116, 105, 110, 103, 115, 46, 10, 125, 10, 10, 47, 47, 32, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 32, 105, 115, 32, 97, 32, 115, 105, 109, 112, 108, 101, 32, 109, 97, 99, 114, 111, 32, 116, 111, 32, 99, 104, 101, 99, 107, 32, 71, 111, 108, 97, 110, 103, 32, 101, 114, 114, 111, 114, 115, 44, 32, 105, 102, 32, 116, 104, 101, 32, 112, 114, 111, 118, 105, 100, 101, 100, 32, 101, 114, 114, 111, 114, 10, 47, 47, 32, 105, 115, 32, 110, 105, 108, 44, 32, 105, 116, 32, 100, 111, 101, 115, 110, 39, 116, 32, 100, 111, 32, 97, 110, 121, 116, 104, 105, 110, 103, 44, 32, 98, 117, 116, 32, 105, 102, 32, 116, 104, 101, 32, 101, 114, 114, 111, 114, 32, 104, 97, 115, 32, 115, 111, 109, 101, 116, 104, 105, 110, 103, 44, 32, 105, 116, 32, 112, 114, 105, 110, 116, 115, 32, 97, 10, 47, 47, 32, 87, 65, 82, 78, 73, 78, 71, 32, 109, 101, 115, 115, 97, 103, 101, 32, 116, 111, 32, 116, 104, 101, 32, 117, 115, 101, 114, 44, 32, 117, 115, 101, 102, 117, 108, 32, 102, 111, 114, 32, 116, 104, 111, 115, 101, 32, 99, 97, 115, 101, 115, 32, 119, 104, 101, 114, 101, 32, 119, 101, 32, 107, 110, 111, 119, 32, 116, 104, 101, 114, 101, 32, 119, 111, 110, 39, 116, 10, 47, 47, 32, 98, 101, 32, 97, 32, 112, 114, 111, 98, 108, 101, 109, 32, 98, 117, 116, 32, 116, 104, 101, 32, 108, 105, 110, 116, 101, 114, 32, 115, 116, 105, 108, 108, 32, 97, 115, 107, 115, 32, 116, 111, 32, 99, 104, 101, 99, 107, 32, 97, 108, 108, 32, 101, 114, 114, 111, 114, 115, 10, 102, 117, 110, 99, 32, 101, 114, 114, 99, 104, 101, 99, 107, 87, 65, 82, 78, 40, 101, 114, 114, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 102, 109, 116, 46, 70, 112, 114, 105, 110, 116, 102, 40, 111, 115, 46, 83, 116, 100, 101, 114, 114, 44, 32, 34, 87, 65, 82, 78, 32, 37, 115, 92, 110, 34, 44, 32, 101, 114, 114, 41, 10, 9, 125, 10, 125, 10}) - box.Add("/scaffoldings/golang/go.mod", []byte{109, 111, 100, 117, 108, 101, 32, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 10, 10, 103, 111, 32, 49, 46, 50, 48, 10, 10, 114, 101, 113, 117, 105, 114, 101, 32, 40, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 111, 98, 114, 97, 32, 118, 49, 46, 54, 46, 49, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 118, 105, 112, 101, 114, 32, 118, 49, 46, 49, 51, 46, 48, 10, 41, 10, 10, 114, 101, 113, 117, 105, 114, 101, 32, 40, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 102, 115, 110, 111, 116, 105, 102, 121, 47, 102, 115, 110, 111, 116, 105, 102, 121, 32, 118, 49, 46, 53, 46, 52, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 104, 97, 115, 104, 105, 99, 111, 114, 112, 47, 104, 99, 108, 32, 118, 49, 46, 48, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 105, 110, 99, 111, 110, 115, 104, 114, 101, 118, 101, 97, 98, 108, 101, 47, 109, 111, 117, 115, 101, 116, 114, 97, 112, 32, 118, 49, 46, 48, 46, 49, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 109, 97, 103, 105, 99, 111, 110, 97, 105, 114, 47, 112, 114, 111, 112, 101, 114, 116, 105, 101, 115, 32, 118, 49, 46, 56, 46, 54, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 109, 105, 116, 99, 104, 101, 108, 108, 104, 47, 109, 97, 112, 115, 116, 114, 117, 99, 116, 117, 114, 101, 32, 118, 49, 46, 53, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 112, 101, 108, 108, 101, 116, 105, 101, 114, 47, 103, 111, 45, 116, 111, 109, 108, 32, 118, 49, 46, 57, 46, 53, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 112, 101, 108, 108, 101, 116, 105, 101, 114, 47, 103, 111, 45, 116, 111, 109, 108, 47, 118, 50, 32, 118, 50, 46, 48, 46, 53, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 97, 102, 101, 114, 111, 32, 118, 49, 46, 56, 46, 50, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 99, 97, 115, 116, 32, 118, 49, 46, 53, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 106, 119, 97, 108, 116, 101, 114, 119, 101, 97, 116, 104, 101, 114, 109, 97, 110, 32, 118, 49, 46, 49, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 112, 102, 49, 51, 47, 112, 102, 108, 97, 103, 32, 118, 49, 46, 48, 46, 53, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 115, 117, 98, 111, 115, 105, 116, 111, 47, 103, 111, 116, 101, 110, 118, 32, 118, 49, 46, 52, 46, 49, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 120, 47, 115, 121, 115, 32, 118, 48, 46, 48, 46, 48, 45, 50, 48, 50, 50, 48, 53, 50, 48, 49, 53, 49, 51, 48, 50, 45, 98, 99, 50, 99, 56, 53, 97, 100, 97, 49, 48, 97, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 120, 47, 116, 101, 120, 116, 32, 118, 48, 46, 51, 46, 55, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 112, 107, 103, 46, 105, 110, 47, 105, 110, 105, 46, 118, 49, 32, 118, 49, 46, 54, 55, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 112, 107, 103, 46, 105, 110, 47, 121, 97, 109, 108, 46, 118, 50, 32, 118, 50, 46, 52, 46, 48, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 9, 103, 111, 112, 107, 103, 46, 105, 110, 47, 121, 97, 109, 108, 46, 118, 51, 32, 118, 51, 46, 48, 46, 49, 32, 47, 47, 32, 105, 110, 100, 105, 114, 101, 99, 116, 10, 41, 10}) - box.Add("/scaffoldings/golang/internal/README.md", []byte{35, 32, 96, 47, 105, 110, 116, 101, 114, 110, 97, 108, 96, 10, 10, 80, 114, 105, 118, 97, 116, 101, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 32, 97, 110, 100, 32, 108, 105, 98, 114, 97, 114, 121, 32, 99, 111, 100, 101, 46, 32, 84, 104, 105, 115, 32, 105, 115, 32, 116, 104, 101, 32, 99, 111, 100, 101, 32, 121, 111, 117, 32, 100, 111, 110, 39, 116, 32, 119, 97, 110, 116, 32, 111, 116, 104, 101, 114, 115, 32, 105, 109, 112, 111, 114, 116, 105, 110, 103, 32, 105, 110, 32, 116, 104, 101, 105, 114, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 115, 32, 111, 114, 32, 108, 105, 98, 114, 97, 114, 105, 101, 115, 46}) - box.Add("/scaffoldings/golang/internal/logger/logger.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 108, 111, 103, 103, 101, 114, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 111, 115, 34, 10, 10, 9, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 47, 103, 111, 45, 115, 100, 107, 47, 108, 119, 108, 111, 103, 103, 101, 114, 34, 10, 41, 10, 10, 47, 47, 32, 76, 111, 103, 32, 97, 108, 108, 111, 119, 115, 32, 116, 104, 105, 115, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 32, 116, 111, 32, 108, 101, 118, 101, 114, 97, 103, 101, 32, 111, 117, 114, 32, 71, 111, 45, 83, 68, 75, 32, 108, 119, 108, 111, 103, 103, 101, 114, 10, 47, 47, 10, 47, 47, 32, 69, 120, 97, 109, 112, 108, 101, 58, 10, 47, 47, 10, 47, 47, 9, 105, 109, 112, 111, 114, 116, 32, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 108, 111, 103, 103, 101, 114, 34, 10, 47, 47, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 73, 110, 102, 111, 40, 34, 97, 110, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 97, 108, 32, 109, 101, 115, 115, 97, 103, 101, 34, 41, 10, 47, 47, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 68, 101, 98, 117, 103, 40, 34, 97, 32, 100, 101, 98, 117, 103, 32, 109, 101, 115, 115, 97, 103, 101, 34, 41, 10, 47, 47, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 73, 110, 102, 111, 119, 40, 34, 105, 110, 102, 111, 32, 109, 101, 115, 115, 97, 103, 101, 32, 119, 105, 116, 104, 32, 118, 97, 114, 105, 97, 98, 108, 101, 115, 34, 44, 32, 34, 102, 111, 111, 34, 44, 32, 34, 98, 97, 114, 34, 41, 10, 118, 97, 114, 32, 76, 111, 103, 32, 61, 32, 108, 119, 108, 111, 103, 103, 101, 114, 46, 78, 101, 119, 40, 111, 115, 46, 71, 101, 116, 101, 110, 118, 40, 34, 76, 87, 95, 76, 79, 71, 34, 41, 41, 46, 83, 117, 103, 97, 114, 40, 41, 10}) - box.Add("/scaffoldings/golang/internal/metric/metric.go", []byte{112, 97, 99, 107, 97, 103, 101, 32, 109, 101, 116, 114, 105, 99, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 99, 111, 110, 116, 101, 120, 116, 34, 10, 9, 34, 111, 115, 34, 10, 10, 9, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 108, 111, 103, 103, 101, 114, 34, 10, 9, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 118, 101, 114, 115, 105, 111, 110, 34, 10, 10, 9, 99, 100, 107, 32, 34, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 47, 103, 111, 45, 115, 100, 107, 47, 99, 108, 105, 47, 99, 100, 107, 47, 103, 111, 47, 112, 114, 111, 116, 111, 47, 118, 49, 34, 10, 9, 34, 103, 111, 111, 103, 108, 101, 46, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 103, 114, 112, 99, 34, 10, 9, 34, 103, 111, 111, 103, 108, 101, 46, 103, 111, 108, 97, 110, 103, 46, 111, 114, 103, 47, 103, 114, 112, 99, 47, 99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 115, 47, 105, 110, 115, 101, 99, 117, 114, 101, 34, 10, 41, 10, 10, 118, 97, 114, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 32, 99, 100, 107, 46, 67, 111, 114, 101, 67, 108, 105, 101, 110, 116, 10, 10, 102, 117, 110, 99, 32, 105, 110, 105, 116, 40, 41, 32, 123, 10, 9, 47, 47, 32, 83, 101, 116, 32, 117, 112, 32, 97, 32, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 32, 116, 111, 32, 116, 104, 101, 32, 67, 68, 75, 32, 115, 101, 114, 118, 101, 114, 10, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 73, 110, 102, 111, 119, 40, 34, 99, 111, 110, 110, 101, 99, 116, 105, 110, 103, 32, 116, 111, 32, 103, 82, 80, 67, 32, 115, 101, 114, 118, 101, 114, 34, 44, 32, 34, 97, 100, 100, 114, 101, 115, 115, 34, 44, 32, 111, 115, 46, 71, 101, 116, 101, 110, 118, 40, 34, 76, 87, 95, 67, 68, 75, 95, 84, 65, 82, 71, 69, 84, 34, 41, 41, 10, 9, 99, 111, 110, 110, 44, 32, 101, 114, 114, 32, 58, 61, 32, 103, 114, 112, 99, 46, 68, 105, 97, 108, 40, 111, 115, 46, 71, 101, 116, 101, 110, 118, 40, 34, 76, 87, 95, 67, 68, 75, 95, 84, 65, 82, 71, 69, 84, 34, 41, 44, 10, 9, 9, 47, 47, 32, 119, 101, 32, 97, 108, 108, 111, 119, 32, 105, 110, 115, 101, 99, 117, 114, 101, 32, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 115, 32, 115, 105, 110, 99, 101, 32, 119, 101, 32, 97, 114, 101, 32, 99, 111, 110, 110, 101, 99, 116, 105, 110, 103, 32, 116, 111, 32, 39, 108, 111, 99, 97, 108, 104, 111, 115, 116, 39, 10, 9, 9, 103, 114, 112, 99, 46, 87, 105, 116, 104, 84, 114, 97, 110, 115, 112, 111, 114, 116, 67, 114, 101, 100, 101, 110, 116, 105, 97, 108, 115, 40, 105, 110, 115, 101, 99, 117, 114, 101, 46, 78, 101, 119, 67, 114, 101, 100, 101, 110, 116, 105, 97, 108, 115, 40, 41, 41, 41, 10, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 67, 97, 110, 110, 111, 116, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 101, 32, 67, 68, 75, 32, 99, 108, 105, 101, 110, 116, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 32, 101, 114, 114, 46, 69, 114, 114, 111, 114, 40, 41, 41, 10, 9, 125, 32, 101, 108, 115, 101, 32, 123, 10, 9, 9, 99, 100, 107, 67, 108, 105, 101, 110, 116, 32, 61, 32, 99, 100, 107, 46, 78, 101, 119, 67, 111, 114, 101, 67, 108, 105, 101, 110, 116, 40, 99, 111, 110, 110, 41, 10, 9, 125, 10, 125, 10, 10, 102, 117, 110, 99, 32, 83, 101, 110, 100, 77, 101, 116, 114, 105, 99, 68, 97, 116, 97, 40, 102, 101, 97, 116, 117, 114, 101, 32, 115, 116, 114, 105, 110, 103, 44, 32, 100, 97, 116, 97, 32, 109, 97, 112, 91, 115, 116, 114, 105, 110, 103, 93, 115, 116, 114, 105, 110, 103, 41, 32, 123, 10, 9, 105, 102, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 32, 61, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 115, 101, 110, 100, 32, 116, 101, 108, 101, 109, 101, 116, 114, 121, 34, 44, 10, 9, 9, 9, 34, 116, 121, 112, 101, 34, 44, 32, 34, 100, 97, 116, 97, 34, 44, 10, 9, 9, 9, 34, 101, 114, 114, 111, 114, 34, 44, 32, 34, 99, 108, 105, 101, 110, 116, 32, 110, 111, 116, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 101, 100, 34, 44, 10, 9, 9, 41, 10, 9, 9, 114, 101, 116, 117, 114, 110, 10, 9, 125, 10, 10, 9, 47, 47, 32, 97, 100, 100, 32, 118, 101, 114, 115, 105, 111, 110, 10, 9, 100, 97, 116, 97, 91, 34, 118, 101, 114, 115, 105, 111, 110, 34, 93, 32, 61, 32, 118, 101, 114, 115, 105, 111, 110, 46, 86, 101, 114, 115, 105, 111, 110, 10, 10, 9, 103, 111, 32, 102, 117, 110, 99, 40, 41, 32, 123, 10, 9, 9, 95, 44, 32, 101, 114, 114, 32, 58, 61, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 46, 72, 111, 110, 101, 121, 118, 101, 110, 116, 40, 99, 111, 110, 116, 101, 120, 116, 46, 66, 97, 99, 107, 103, 114, 111, 117, 110, 100, 40, 41, 44, 32, 38, 99, 100, 107, 46, 72, 111, 110, 101, 121, 118, 101, 110, 116, 82, 101, 113, 117, 101, 115, 116, 123, 10, 9, 9, 9, 70, 101, 97, 116, 117, 114, 101, 58, 32, 102, 101, 97, 116, 117, 114, 101, 44, 32, 70, 101, 97, 116, 117, 114, 101, 68, 97, 116, 97, 58, 32, 100, 97, 116, 97, 44, 10, 9, 9, 125, 41, 10, 9, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 115, 101, 110, 100, 32, 116, 101, 108, 101, 109, 101, 116, 114, 121, 34, 44, 10, 9, 9, 9, 9, 34, 116, 121, 112, 101, 34, 44, 32, 34, 100, 97, 116, 97, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 32, 101, 114, 114, 46, 69, 114, 114, 111, 114, 40, 41, 44, 10, 9, 9, 9, 41, 10, 9, 9, 125, 10, 9, 125, 40, 41, 10, 125, 10, 10, 102, 117, 110, 99, 32, 83, 101, 110, 100, 77, 101, 116, 114, 105, 99, 69, 114, 114, 111, 114, 40, 101, 32, 101, 114, 114, 111, 114, 41, 32, 123, 10, 9, 105, 102, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 32, 61, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 115, 101, 110, 100, 32, 116, 101, 108, 101, 109, 101, 116, 114, 121, 34, 44, 10, 9, 9, 9, 34, 116, 121, 112, 101, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 10, 9, 9, 9, 34, 101, 114, 114, 111, 114, 34, 44, 32, 34, 99, 108, 105, 101, 110, 116, 32, 110, 111, 116, 32, 105, 110, 105, 116, 105, 97, 108, 105, 122, 101, 100, 34, 44, 10, 9, 9, 41, 10, 9, 9, 114, 101, 116, 117, 114, 110, 10, 9, 125, 10, 10, 9, 103, 111, 32, 102, 117, 110, 99, 40, 41, 32, 123, 10, 9, 9, 95, 44, 32, 101, 114, 114, 32, 58, 61, 32, 99, 100, 107, 67, 108, 105, 101, 110, 116, 46, 72, 111, 110, 101, 121, 118, 101, 110, 116, 40, 99, 111, 110, 116, 101, 120, 116, 46, 66, 97, 99, 107, 103, 114, 111, 117, 110, 100, 40, 41, 44, 32, 38, 99, 100, 107, 46, 72, 111, 110, 101, 121, 118, 101, 110, 116, 82, 101, 113, 117, 101, 115, 116, 123, 10, 9, 9, 9, 69, 114, 114, 111, 114, 58, 32, 101, 46, 69, 114, 114, 111, 114, 40, 41, 44, 10, 9, 9, 125, 41, 10, 9, 9, 105, 102, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 9, 108, 111, 103, 103, 101, 114, 46, 76, 111, 103, 46, 87, 97, 114, 110, 40, 34, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 115, 101, 110, 100, 32, 116, 101, 108, 101, 109, 101, 116, 114, 121, 34, 44, 10, 9, 9, 9, 9, 34, 116, 121, 112, 101, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 32, 34, 101, 114, 114, 111, 114, 34, 44, 32, 101, 114, 114, 46, 69, 114, 114, 111, 114, 40, 41, 44, 10, 9, 9, 9, 41, 10, 9, 9, 125, 10, 9, 125, 40, 41, 10, 125, 10}) - box.Add("/scaffoldings/golang/internal/version/version.go", []byte{112, 97, 99, 107, 97, 103, 101, 32, 118, 101, 114, 115, 105, 111, 110, 10, 10, 118, 97, 114, 32, 40, 10, 9, 47, 47, 32, 65, 108, 108, 32, 116, 104, 101, 32, 102, 111, 108, 108, 111, 119, 105, 110, 103, 32, 34, 117, 110, 107, 110, 111, 119, 110, 34, 32, 118, 97, 114, 105, 97, 98, 108, 101, 115, 32, 97, 114, 101, 32, 98, 101, 105, 110, 103, 32, 105, 110, 106, 101, 99, 116, 101, 100, 32, 97, 116, 10, 9, 47, 47, 32, 98, 117, 105, 108, 100, 32, 116, 105, 109, 101, 32, 118, 105, 97, 32, 116, 104, 101, 32, 99, 114, 111, 115, 115, 45, 112, 108, 97, 116, 102, 111, 114, 109, 32, 100, 105, 114, 101, 99, 116, 105, 118, 101, 32, 105, 110, 115, 105, 100, 101, 32, 116, 104, 101, 32, 77, 97, 107, 101, 102, 105, 108, 101, 10, 9, 47, 47, 10, 9, 47, 47, 32, 86, 101, 114, 115, 105, 111, 110, 32, 105, 115, 32, 116, 104, 101, 32, 115, 101, 109, 118, 101, 114, 32, 99, 111, 109, 105, 110, 103, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 86, 69, 82, 83, 73, 79, 78, 32, 102, 105, 108, 101, 10, 9, 86, 101, 114, 115, 105, 111, 110, 32, 61, 32, 34, 117, 110, 107, 110, 111, 119, 110, 34, 10, 10, 9, 47, 47, 32, 71, 105, 116, 83, 72, 65, 32, 105, 115, 32, 116, 104, 101, 32, 103, 105, 116, 32, 114, 101, 102, 32, 116, 104, 97, 116, 32, 116, 104, 101, 32, 99, 108, 105, 32, 119, 97, 115, 32, 98, 117, 105, 108, 116, 32, 102, 114, 111, 109, 10, 9, 71, 105, 116, 83, 72, 65, 32, 61, 32, 34, 117, 110, 107, 110, 111, 119, 110, 34, 10, 10, 9, 47, 47, 32, 66, 117, 105, 108, 100, 84, 105, 109, 101, 32, 105, 115, 32, 97, 32, 104, 117, 109, 97, 110, 45, 114, 101, 97, 100, 97, 98, 108, 101, 32, 116, 105, 109, 101, 32, 119, 104, 101, 110, 32, 116, 104, 101, 32, 99, 108, 105, 32, 119, 97, 115, 32, 98, 117, 105, 108, 116, 32, 97, 116, 10, 9, 66, 117, 105, 108, 100, 84, 105, 109, 101, 32, 61, 32, 34, 117, 110, 107, 110, 111, 119, 110, 34, 10, 41, 10}) - box.Add("/scaffoldings/golang/main.go", []byte{47, 47, 10, 47, 47, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 58, 58, 32, 67, 111, 112, 121, 114, 105, 103, 104, 116, 32, 50, 48, 50, 51, 44, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 73, 110, 99, 46, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 58, 58, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 76, 105, 99, 101, 110, 115, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 65, 112, 97, 99, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 44, 32, 86, 101, 114, 115, 105, 111, 110, 32, 50, 46, 48, 32, 40, 116, 104, 101, 32, 34, 76, 105, 99, 101, 110, 115, 101, 34, 41, 59, 10, 47, 47, 32, 121, 111, 117, 32, 109, 97, 121, 32, 110, 111, 116, 32, 117, 115, 101, 32, 116, 104, 105, 115, 32, 102, 105, 108, 101, 32, 101, 120, 99, 101, 112, 116, 32, 105, 110, 32, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 119, 105, 116, 104, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 32, 89, 111, 117, 32, 109, 97, 121, 32, 111, 98, 116, 97, 105, 110, 32, 97, 32, 99, 111, 112, 121, 32, 111, 102, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 97, 116, 10, 47, 47, 10, 47, 47, 32, 32, 32, 32, 32, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 97, 112, 97, 99, 104, 101, 46, 111, 114, 103, 47, 108, 105, 99, 101, 110, 115, 101, 115, 47, 76, 73, 67, 69, 78, 83, 69, 45, 50, 46, 48, 10, 47, 47, 10, 47, 47, 32, 85, 110, 108, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 32, 98, 121, 32, 97, 112, 112, 108, 105, 99, 97, 98, 108, 101, 32, 108, 97, 119, 32, 111, 114, 32, 97, 103, 114, 101, 101, 100, 32, 116, 111, 32, 105, 110, 32, 119, 114, 105, 116, 105, 110, 103, 44, 32, 115, 111, 102, 116, 119, 97, 114, 101, 10, 47, 47, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 105, 115, 32, 100, 105, 115, 116, 114, 105, 98, 117, 116, 101, 100, 32, 111, 110, 32, 97, 110, 32, 34, 65, 83, 32, 73, 83, 34, 32, 66, 65, 83, 73, 83, 44, 10, 47, 47, 32, 87, 73, 84, 72, 79, 85, 84, 32, 87, 65, 82, 82, 65, 78, 84, 73, 69, 83, 32, 79, 82, 32, 67, 79, 78, 68, 73, 84, 73, 79, 78, 83, 32, 79, 70, 32, 65, 78, 89, 32, 75, 73, 78, 68, 44, 32, 101, 105, 116, 104, 101, 114, 32, 101, 120, 112, 114, 101, 115, 115, 32, 111, 114, 32, 105, 109, 112, 108, 105, 101, 100, 46, 10, 47, 47, 32, 83, 101, 101, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 112, 101, 99, 105, 102, 105, 99, 32, 108, 97, 110, 103, 117, 97, 103, 101, 32, 103, 111, 118, 101, 114, 110, 105, 110, 103, 32, 112, 101, 114, 109, 105, 115, 115, 105, 111, 110, 115, 32, 97, 110, 100, 10, 47, 47, 32, 108, 105, 109, 105, 116, 97, 116, 105, 111, 110, 115, 32, 117, 110, 100, 101, 114, 32, 116, 104, 101, 32, 76, 105, 99, 101, 110, 115, 101, 46, 10, 47, 47, 10, 10, 112, 97, 99, 107, 97, 103, 101, 32, 109, 97, 105, 110, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 9, 34, 91, 91, 46, 67, 111, 109, 112, 111, 110, 101, 110, 116, 93, 93, 47, 99, 109, 100, 34, 10, 41, 10, 10, 102, 117, 110, 99, 32, 109, 97, 105, 110, 40, 41, 32, 123, 10, 9, 99, 109, 100, 46, 69, 120, 101, 99, 117, 116, 101, 40, 41, 10, 125, 10}) - box.Add("/scaffoldings/golang/pkg/README.md", []byte{35, 32, 96, 47, 112, 107, 103, 96, 10, 10, 76, 105, 98, 114, 97, 114, 121, 32, 99, 111, 100, 101, 32, 116, 104, 97, 116, 39, 115, 32, 111, 107, 32, 116, 111, 32, 117, 115, 101, 32, 98, 121, 32, 101, 120, 116, 101, 114, 110, 97, 108, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 115, 32, 40, 101, 46, 103, 46, 44, 32, 96, 47, 112, 107, 103, 47, 109, 121, 112, 117, 98, 108, 105, 99, 108, 105, 98, 96, 41, 46}) - box.Add("/scaffoldings/python/README.md", []byte{}) - box.Add("/scaffoldings/python/src/package/__init__.py", []byte{}) - box.Add("/scaffoldings/python/src/package/__main__.py", []byte{10, 100, 101, 102, 32, 109, 97, 105, 110, 40, 41, 58, 10, 32, 32, 32, 32, 112, 114, 105, 110, 116, 40, 34, 72, 101, 108, 108, 111, 32, 112, 121, 116, 104, 111, 110, 32, 99, 111, 109, 112, 111, 110, 101, 110, 116, 34, 41, 10, 10, 10, 105, 102, 32, 95, 95, 110, 97, 109, 101, 95, 95, 32, 61, 61, 32, 34, 95, 95, 109, 97, 105, 110, 95, 95, 34, 58, 10, 32, 32, 32, 32, 109, 97, 105, 110, 40, 41, 10}) - box.Add("/vuln_assessment.html", []byte{60, 33, 68, 79, 67, 84, 89, 80, 69, 32, 104, 116, 109, 108, 62, 10, 60, 104, 116, 109, 108, 32, 108, 97, 110, 103, 61, 34, 101, 110, 34, 62, 10, 32, 32, 32, 32, 60, 104, 101, 97, 100, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 104, 116, 116, 112, 45, 101, 113, 117, 105, 118, 61, 34, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 116, 101, 120, 116, 47, 104, 116, 109, 108, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 85, 84, 70, 45, 56, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 104, 116, 116, 112, 45, 101, 113, 117, 105, 118, 61, 34, 88, 45, 85, 65, 45, 67, 111, 109, 112, 97, 116, 105, 98, 108, 101, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 73, 69, 61, 101, 100, 103, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 110, 97, 109, 101, 61, 34, 118, 105, 101, 119, 112, 111, 114, 116, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 119, 105, 100, 116, 104, 61, 100, 101, 118, 105, 99, 101, 45, 119, 105, 100, 116, 104, 44, 105, 110, 105, 116, 105, 97, 108, 45, 115, 99, 97, 108, 101, 61, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 110, 97, 109, 101, 61, 34, 103, 111, 111, 103, 108, 101, 34, 32, 118, 97, 108, 117, 101, 61, 34, 110, 111, 116, 114, 97, 110, 115, 108, 97, 116, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 110, 97, 109, 101, 61, 34, 102, 111, 114, 109, 97, 116, 45, 100, 101, 116, 101, 99, 116, 105, 111, 110, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 116, 101, 108, 101, 112, 104, 111, 110, 101, 61, 110, 111, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 110, 97, 109, 101, 61, 34, 114, 101, 102, 101, 114, 114, 101, 114, 34, 32, 99, 111, 110, 116, 101, 110, 116, 61, 34, 110, 111, 45, 114, 101, 102, 101, 114, 114, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 105, 116, 108, 101, 62, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 121, 32, 65, 115, 115, 101, 115, 115, 109, 101, 110, 116, 32, 45, 32, 76, 97, 99, 101, 119, 111, 114, 107, 32, 83, 101, 99, 117, 114, 105, 116, 121, 60, 47, 116, 105, 116, 108, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 110, 107, 32, 114, 101, 108, 61, 34, 115, 104, 111, 114, 116, 99, 117, 116, 32, 105, 99, 111, 110, 34, 32, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 102, 97, 118, 105, 99, 111, 110, 46, 105, 99, 111, 63, 118, 61, 50, 34, 32, 116, 121, 112, 101, 61, 34, 105, 109, 97, 103, 101, 47, 120, 45, 105, 99, 111, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 116, 121, 108, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 102, 114, 111, 109, 58, 32, 47, 117, 105, 47, 115, 116, 121, 108, 101, 115, 104, 101, 101, 116, 115, 47, 100, 97, 121, 45, 97, 110, 116, 46, 99, 115, 115, 32, 42, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 97, 110, 116, 100, 45, 119, 97, 118, 101, 45, 115, 104, 97, 100, 111, 119, 45, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 115, 99, 114, 111, 108, 108, 45, 98, 97, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 118, 97, 114, 105, 97, 110, 116, 58, 116, 97, 98, 117, 108, 97, 114, 45, 110, 117, 109, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 46, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 115, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 34, 116, 110, 117, 109, 34, 44, 34, 116, 110, 117, 109, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 34, 116, 110, 117, 109, 34, 44, 34, 116, 110, 117, 109, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 99, 99, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 51, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 51, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 45, 115, 116, 114, 105, 110, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 45, 111, 114, 105, 103, 105, 110, 58, 48, 32, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 45, 111, 114, 105, 103, 105, 110, 58, 48, 32, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 116, 97, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 118, 97, 114, 105, 97, 110, 116, 58, 116, 97, 98, 117, 108, 97, 114, 45, 110, 117, 109, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 46, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 115, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 34, 116, 110, 117, 109, 34, 44, 34, 116, 110, 117, 109, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 34, 116, 110, 117, 109, 34, 44, 34, 116, 110, 117, 109, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 32, 56, 112, 120, 32, 48, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 32, 55, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 50, 53, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 114, 103, 98, 97, 40, 48, 44, 48, 44, 48, 44, 46, 48, 52, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 52, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 46, 51, 115, 32, 99, 117, 98, 105, 99, 45, 98, 101, 122, 105, 101, 114, 40, 46, 55, 56, 44, 46, 49, 52, 44, 46, 49, 53, 44, 46, 56, 54, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 116, 97, 103, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 46, 56, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 116, 97, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 114, 103, 98, 97, 40, 48, 44, 48, 44, 48, 44, 46, 54, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 102, 114, 111, 109, 58, 32, 47, 117, 105, 47, 115, 116, 121, 108, 101, 115, 104, 101, 101, 116, 115, 47, 100, 97, 121, 46, 99, 115, 115, 32, 42, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 34, 32, 105, 99, 111, 110, 45, 34, 93, 58, 110, 111, 116, 40, 46, 105, 99, 111, 110, 45, 114, 105, 103, 104, 116, 41, 58, 98, 101, 102, 111, 114, 101, 44, 91, 99, 108, 97, 115, 115, 94, 61, 105, 99, 111, 110, 45, 93, 58, 110, 111, 116, 40, 46, 105, 99, 111, 110, 45, 114, 105, 103, 104, 116, 41, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 34, 105, 99, 111, 110, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 112, 101, 97, 107, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 101, 97, 116, 117, 114, 101, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 118, 97, 114, 105, 97, 110, 116, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 115, 109, 111, 111, 116, 104, 105, 110, 103, 58, 97, 110, 116, 105, 97, 108, 105, 97, 115, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 111, 122, 45, 111, 115, 120, 45, 102, 111, 110, 116, 45, 115, 109, 111, 111, 116, 104, 105, 110, 103, 58, 103, 114, 97, 121, 115, 99, 97, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 105, 114, 99, 108, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 48, 51, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 118, 117, 108, 110, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 49, 98, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 100, 111, 119, 110, 108, 111, 97, 100, 45, 112, 100, 102, 45, 49, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 50, 100, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 97, 114, 101, 116, 45, 100, 111, 119, 110, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 51, 53, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 114, 101, 99, 101, 110, 116, 115, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 54, 52, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 115, 101, 97, 114, 99, 104, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 54, 54, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 115, 101, 116, 116, 105, 110, 103, 115, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 54, 55, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 100, 111, 119, 110, 108, 111, 97, 100, 45, 99, 115, 118, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 54, 57, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 56, 101, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 109, 101, 110, 117, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 57, 102, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 99, 111, 108, 117, 109, 110, 115, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 98, 100, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 104, 101, 108, 112, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 99, 50, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 114, 101, 115, 105, 122, 101, 45, 102, 117, 108, 108, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 99, 97, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 114, 105, 103, 104, 116, 45, 100, 105, 114, 58, 110, 111, 116, 40, 46, 105, 99, 111, 110, 45, 114, 105, 103, 104, 116, 41, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 92, 101, 56, 99, 100, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 115, 97, 110, 115, 45, 115, 101, 114, 105, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 116, 101, 120, 116, 45, 115, 105, 122, 101, 45, 97, 100, 106, 117, 115, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 101, 120, 116, 45, 115, 105, 122, 101, 45, 97, 100, 106, 117, 115, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 115, 105, 100, 101, 44, 102, 105, 103, 117, 114, 101, 44, 102, 111, 111, 116, 101, 114, 44, 104, 101, 97, 100, 101, 114, 44, 109, 97, 105, 110, 44, 115, 101, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 45, 115, 107, 105, 112, 58, 111, 98, 106, 101, 99, 116, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 58, 97, 99, 116, 105, 118, 101, 44, 97, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 50, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 46, 54, 55, 101, 109, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 109, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 118, 103, 58, 110, 111, 116, 40, 58, 114, 111, 111, 116, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 103, 117, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 49, 101, 109, 32, 52, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 117, 116, 116, 111, 110, 44, 105, 110, 112, 117, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 117, 116, 116, 111, 110, 44, 105, 110, 112, 117, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 117, 116, 116, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 117, 116, 116, 111, 110, 44, 104, 116, 109, 108, 32, 91, 116, 121, 112, 101, 61, 98, 117, 116, 116, 111, 110, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 97, 112, 112, 101, 97, 114, 97, 110, 99, 101, 58, 98, 117, 116, 116, 111, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 116, 121, 112, 101, 61, 98, 117, 116, 116, 111, 110, 93, 58, 58, 45, 109, 111, 122, 45, 102, 111, 99, 117, 115, 45, 105, 110, 110, 101, 114, 44, 98, 117, 116, 116, 111, 110, 58, 58, 45, 109, 111, 122, 45, 102, 111, 99, 117, 115, 45, 105, 110, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 116, 121, 112, 101, 61, 98, 117, 116, 116, 111, 110, 93, 58, 45, 109, 111, 122, 45, 102, 111, 99, 117, 115, 114, 105, 110, 103, 44, 98, 117, 116, 116, 111, 110, 58, 45, 109, 111, 122, 45, 102, 111, 99, 117, 115, 114, 105, 110, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 49, 112, 120, 32, 100, 111, 116, 116, 101, 100, 32, 66, 117, 116, 116, 111, 110, 84, 101, 120, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 116, 121, 112, 101, 61, 99, 104, 101, 99, 107, 98, 111, 120, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 58, 45, 119, 101, 98, 107, 105, 116, 45, 105, 110, 112, 117, 116, 45, 112, 108, 97, 99, 101, 104, 111, 108, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 46, 53, 52, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 110, 112, 117, 116, 58, 58, 45, 109, 115, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 44, 58, 97, 102, 116, 101, 114, 44, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 102, 111, 99, 117, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 100, 121, 44, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 44, 97, 115, 105, 100, 101, 44, 98, 111, 100, 121, 44, 100, 105, 118, 44, 102, 105, 103, 117, 114, 101, 44, 102, 111, 111, 116, 101, 114, 44, 104, 49, 44, 104, 52, 44, 104, 53, 44, 104, 101, 97, 100, 101, 114, 44, 104, 116, 109, 108, 44, 105, 109, 103, 44, 108, 97, 98, 101, 108, 44, 108, 105, 44, 112, 44, 115, 101, 99, 116, 105, 111, 110, 44, 115, 112, 97, 110, 44, 117, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 98, 97, 115, 101, 108, 105, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 114, 111, 111, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 116, 109, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 49, 102, 50, 102, 52, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 97, 116, 116, 97, 99, 104, 109, 101, 110, 116, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 34, 76, 97, 116, 111, 34, 44, 34, 79, 112, 101, 110, 32, 83, 97, 110, 115, 34, 44, 34, 77, 101, 110, 108, 111, 34, 44, 115, 97, 110, 115, 45, 115, 101, 114, 105, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 120, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 102, 111, 110, 116, 45, 115, 109, 111, 111, 116, 104, 105, 110, 103, 58, 115, 117, 98, 112, 105, 120, 101, 108, 45, 97, 110, 116, 105, 97, 108, 105, 97, 115, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 35, 109, 97, 105, 110, 95, 98, 111, 100, 121, 95, 119, 114, 97, 112, 112, 101, 114, 95, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 45, 119, 101, 98, 107, 105, 116, 45, 102, 117, 108, 108, 45, 115, 99, 114, 101, 101, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 118, 119, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 118, 104, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 65, 112, 112, 67, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 116, 116, 111, 109, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 58, 100, 105, 115, 97, 98, 108, 101, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 58, 97, 99, 116, 105, 118, 101, 44, 97, 58, 102, 111, 99, 117, 115, 44, 97, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 117, 110, 100, 101, 114, 108, 105, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 32, 58, 97, 99, 116, 105, 118, 101, 44, 97, 32, 58, 100, 105, 115, 97, 98, 108, 101, 100, 44, 97, 32, 58, 102, 111, 99, 117, 115, 44, 97, 32, 58, 104, 111, 118, 101, 114, 44, 97, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 97, 99, 116, 105, 118, 101, 44, 97, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 100, 105, 115, 97, 98, 108, 101, 100, 44, 97, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 102, 111, 99, 117, 115, 44, 97, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 48, 32, 46, 53, 114, 101, 109, 32, 50, 114, 101, 109, 32, 45, 46, 53, 114, 101, 109, 32, 114, 103, 98, 97, 40, 49, 52, 48, 44, 49, 53, 51, 44, 49, 53, 57, 44, 46, 55, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 112, 105, 116, 97, 108, 105, 122, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 99, 97, 112, 105, 116, 97, 108, 105, 122, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 101, 110, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 108, 105, 99, 107, 97, 98, 108, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 101, 108, 108, 105, 112, 115, 105, 115, 44, 46, 105, 110, 112, 117, 116, 45, 108, 97, 98, 101, 108, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 115, 112, 97, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 44, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 101, 108, 108, 105, 112, 115, 105, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 100, 105, 115, 97, 98, 108, 101, 100, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 102, 111, 99, 117, 115, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 104, 111, 118, 101, 114, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 100, 105, 115, 97, 98, 108, 101, 100, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 102, 111, 99, 117, 115, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 110, 111, 115, 99, 114, 111, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 105, 100, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 109, 97, 108, 108, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 110, 116, 45, 116, 97, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 114, 103, 98, 97, 40, 48, 44, 48, 44, 48, 44, 46, 49, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 49, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 32, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 117, 112, 112, 101, 114, 99, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 101, 114, 116, 105, 99, 97, 108, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 99, 97, 112, 105, 116, 97, 108, 105, 122, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 101, 114, 116, 105, 99, 97, 108, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 49, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 101, 114, 116, 105, 99, 97, 108, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 46, 51, 55, 50, 53, 114, 101, 109, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 109, 97, 108, 108, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 104, 111, 118, 101, 114, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 97, 99, 116, 105, 118, 101, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 97, 99, 116, 105, 118, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 101, 108, 101, 99, 116, 101, 100, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 45, 93, 58, 98, 101, 102, 111, 114, 101, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 45, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 115, 101, 108, 102, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 44, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 49, 50, 53, 101, 109, 32, 46, 50, 53, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 97, 99, 116, 105, 118, 101, 44, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 58, 110, 111, 116, 40, 46, 110, 111, 45, 117, 110, 100, 101, 114, 108, 105, 110, 101, 41, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 100, 101, 99, 111, 114, 97, 116, 105, 111, 110, 58, 117, 110, 100, 101, 114, 108, 105, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 105, 110, 118, 97, 108, 105, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 112, 117, 116, 45, 108, 97, 98, 101, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 49, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 111, 114, 109, 45, 99, 111, 110, 116, 114, 111, 108, 44, 46, 105, 110, 112, 117, 116, 45, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 49, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 53, 48, 109, 115, 32, 101, 97, 115, 101, 45, 111, 117, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 111, 114, 109, 45, 99, 111, 110, 116, 114, 111, 108, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 44, 46, 105, 110, 112, 117, 116, 45, 116, 101, 120, 116, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 112, 117, 116, 45, 116, 101, 120, 116, 46, 102, 111, 114, 109, 45, 99, 111, 110, 116, 114, 111, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 50, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 108, 97, 98, 101, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 32, 48, 32, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 108, 111, 103, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 51, 53, 54, 98, 98, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 99, 111, 108, 111, 114, 32, 46, 50, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 108, 111, 103, 111, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 99, 111, 108, 111, 114, 32, 46, 50, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 108, 111, 103, 111, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 116, 114, 111, 107, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 105, 99, 111, 110, 32, 46, 108, 111, 103, 111, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 49, 44, 104, 52, 44, 104, 53, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 46, 50, 53, 114, 101, 109, 32, 48, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 52, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 53, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 49, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 46, 114, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 46, 114, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 46, 53, 114, 101, 109, 32, 115, 111, 108, 105, 100, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 32, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 48, 32, 46, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 116, 101, 110, 116, 58, 34, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 45, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 114, 105, 103, 104, 116, 32, 46, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 45, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 114, 111, 116, 97, 116, 101, 40, 45, 57, 48, 100, 101, 103, 41, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 114, 111, 116, 97, 116, 101, 40, 45, 57, 48, 100, 101, 103, 41, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 111, 118, 101, 114, 108, 97, 121, 84, 114, 105, 103, 103, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 111, 118, 101, 114, 108, 97, 121, 84, 114, 105, 103, 103, 101, 114, 32, 46, 99, 104, 105, 108, 100, 114, 101, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 115, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 58, 102, 105, 114, 115, 116, 45, 99, 104, 105, 108, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 108, 101, 102, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 48, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 97, 117, 116, 111, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 97, 117, 116, 111, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 99, 101, 110, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 115, 101, 108, 102, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 115, 112, 97, 99, 101, 45, 101, 118, 101, 110, 108, 121, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 108, 101, 102, 116, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 99, 111, 110, 116, 101, 110, 116, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 104, 115, 108, 97, 40, 48, 44, 48, 37, 44, 49, 48, 48, 37, 44, 46, 50, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 46, 51, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 116, 116, 111, 109, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 115, 46, 104, 111, 118, 101, 114, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 43, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 45, 98, 97, 114, 32, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 45, 98, 97, 114, 32, 46, 97, 99, 116, 105, 111, 110, 58, 102, 105, 114, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 50, 53, 114, 101, 109, 32, 48, 32, 48, 32, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 45, 98, 97, 114, 32, 46, 97, 99, 116, 105, 111, 110, 58, 108, 97, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 48, 32, 46, 50, 53, 114, 101, 109, 32, 46, 50, 53, 114, 101, 109, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 105, 103, 104, 116, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 45, 98, 97, 114, 32, 46, 97, 99, 116, 105, 111, 110, 62, 98, 117, 116, 116, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 117, 110, 115, 101, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 97, 99, 116, 105, 111, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 62, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 51, 56, 55, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 50, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 97, 99, 116, 105, 111, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 62, 91, 99, 108, 97, 115, 115, 42, 61, 98, 117, 116, 116, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 97, 110, 105, 109, 97, 116, 105, 111, 110, 58, 102, 97, 100, 101, 73, 110, 32, 46, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 110, 105, 109, 97, 116, 105, 111, 110, 58, 102, 97, 100, 101, 73, 110, 32, 46, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 111, 112, 97, 99, 105, 116, 121, 32, 46, 53, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 111, 111, 108, 116, 105, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 97, 110, 105, 109, 97, 116, 105, 111, 110, 58, 102, 97, 100, 101, 73, 110, 32, 46, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 110, 105, 109, 97, 116, 105, 111, 110, 58, 102, 97, 100, 101, 73, 110, 32, 46, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 111, 112, 97, 99, 105, 116, 121, 32, 46, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 115, 116, 114, 101, 116, 99, 104, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 114, 101, 109, 32, 49, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 45, 102, 111, 111, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 102, 108, 101, 120, 45, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 102, 108, 101, 120, 45, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 45, 102, 111, 111, 116, 101, 114, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 102, 108, 101, 120, 45, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 45, 102, 111, 111, 116, 101, 114, 45, 105, 110, 102, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 102, 108, 101, 120, 45, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 97, 114, 100, 45, 102, 111, 111, 116, 101, 114, 45, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 105, 116, 97, 108, 105, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 32, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 46, 109, 117, 108, 116, 105, 45, 102, 105, 108, 108, 32, 46, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 110, 99, 104, 111, 114, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 46, 109, 117, 108, 116, 105, 45, 102, 105, 108, 108, 32, 46, 104, 111, 114, 105, 122, 111, 110, 116, 97, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 116, 114, 111, 107, 101, 45, 111, 112, 97, 99, 105, 116, 121, 58, 46, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 116, 114, 111, 107, 101, 45, 119, 105, 100, 116, 104, 58, 49, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 46, 109, 117, 108, 116, 105, 45, 102, 105, 108, 108, 32, 46, 104, 111, 114, 105, 122, 111, 110, 116, 97, 108, 32, 46, 116, 105, 99, 107, 32, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 46, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 112, 97, 116, 104, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 104, 97, 112, 101, 45, 114, 101, 110, 100, 101, 114, 105, 110, 103, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 114, 101, 110, 100, 101, 114, 105, 110, 103, 58, 111, 112, 116, 105, 109, 105, 122, 101, 76, 101, 103, 105, 98, 105, 108, 105, 116, 121, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 114, 101, 99, 116, 46, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 114, 101, 99, 116, 46, 98, 97, 114, 46, 108, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 46, 55, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 114, 101, 99, 116, 46, 98, 97, 114, 58, 104, 111, 118, 101, 114, 58, 110, 111, 116, 40, 46, 100, 105, 115, 97, 98, 108, 101, 100, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 46, 55, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 104, 97, 114, 116, 32, 114, 101, 99, 116, 46, 98, 97, 114, 58, 104, 111, 118, 101, 114, 58, 110, 111, 116, 40, 46, 100, 105, 115, 97, 98, 108, 101, 100, 41, 46, 108, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 45, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 45, 109, 115, 45, 103, 114, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 115, 58, 40, 49, 102, 114, 41, 91, 52, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 115, 58, 109, 105, 110, 109, 97, 120, 40, 49, 54, 114, 101, 109, 44, 97, 117, 116, 111, 41, 91, 53, 48, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 103, 114, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 116, 101, 109, 112, 108, 97, 116, 101, 45, 99, 111, 108, 117, 109, 110, 115, 58, 114, 101, 112, 101, 97, 116, 40, 52, 44, 91, 99, 111, 108, 93, 32, 109, 105, 110, 109, 97, 120, 40, 49, 48, 114, 101, 109, 44, 49, 102, 114, 41, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 97, 117, 116, 111, 45, 114, 111, 119, 115, 58, 109, 105, 110, 109, 97, 120, 40, 49, 54, 114, 101, 109, 44, 97, 117, 116, 111, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 103, 97, 112, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 97, 117, 116, 111, 45, 102, 108, 111, 119, 58, 100, 101, 110, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 115, 58, 40, 49, 102, 114, 41, 91, 50, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 116, 101, 109, 112, 108, 97, 116, 101, 45, 99, 111, 108, 117, 109, 110, 115, 58, 114, 101, 112, 101, 97, 116, 40, 50, 44, 91, 99, 111, 108, 93, 32, 109, 105, 110, 109, 97, 120, 40, 49, 50, 114, 101, 109, 44, 49, 102, 114, 41, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 115, 58, 40, 49, 102, 114, 41, 91, 51, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 115, 58, 40, 57, 46, 53, 114, 101, 109, 41, 91, 53, 48, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 116, 101, 109, 112, 108, 97, 116, 101, 45, 99, 111, 108, 117, 109, 110, 115, 58, 114, 101, 112, 101, 97, 116, 40, 51, 44, 91, 99, 111, 108, 93, 32, 109, 105, 110, 109, 97, 120, 40, 49, 51, 46, 56, 51, 51, 51, 51, 114, 101, 109, 44, 49, 102, 114, 41, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 97, 117, 116, 111, 45, 114, 111, 119, 115, 58, 57, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 115, 58, 40, 49, 102, 114, 41, 91, 50, 93, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 116, 101, 109, 112, 108, 97, 116, 101, 45, 99, 111, 108, 117, 109, 110, 115, 58, 114, 101, 112, 101, 97, 116, 40, 50, 44, 91, 99, 111, 108, 93, 32, 109, 105, 110, 109, 97, 120, 40, 49, 51, 46, 56, 51, 51, 51, 51, 114, 101, 109, 44, 49, 102, 114, 41, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 115, 112, 97, 110, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 45, 115, 112, 97, 110, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 97, 108, 105, 103, 110, 58, 115, 116, 97, 114, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 45, 97, 108, 105, 103, 110, 58, 115, 116, 97, 114, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 114, 111, 119, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 46, 104, 45, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 114, 111, 119, 45, 115, 112, 97, 110, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 114, 111, 119, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 49, 54, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 46, 119, 45, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 115, 112, 97, 110, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 46, 119, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 115, 112, 97, 110, 58, 51, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 51, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 102, 105, 114, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 50, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 51, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 51, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 52, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 52, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 53, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 58, 110, 116, 104, 45, 111, 102, 45, 116, 121, 112, 101, 40, 54, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 54, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 62, 46, 99, 97, 114, 100, 46, 119, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 109, 115, 45, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 115, 112, 97, 110, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 99, 111, 108, 117, 109, 110, 45, 101, 110, 100, 58, 115, 112, 97, 110, 32, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 103, 97, 112, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 103, 114, 105, 100, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 108, 105, 110, 101, 97, 114, 45, 103, 114, 97, 100, 105, 101, 110, 116, 40, 57, 48, 100, 101, 103, 44, 35, 50, 55, 52, 101, 56, 52, 44, 35, 50, 54, 57, 56, 99, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 53, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 62, 46, 97, 99, 116, 105, 111, 110, 58, 110, 111, 116, 40, 58, 102, 105, 114, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 62, 46, 97, 99, 116, 105, 111, 110, 32, 98, 117, 116, 116, 111, 110, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 62, 46, 97, 99, 116, 105, 111, 110, 32, 98, 117, 116, 116, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 99, 111, 108, 111, 114, 32, 46, 49, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 62, 46, 97, 99, 116, 105, 111, 110, 32, 98, 117, 116, 116, 111, 110, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 46, 49, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 97, 99, 116, 105, 111, 110, 115, 46, 114, 105, 103, 104, 116, 58, 108, 97, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 108, 111, 103, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 108, 111, 103, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 88, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 88, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 108, 101, 102, 116, 32, 46, 52, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 50, 55, 57, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 62, 46, 116, 97, 98, 58, 110, 111, 116, 40, 58, 102, 105, 114, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 46, 116, 97, 98, 45, 98, 97, 114, 62, 46, 116, 97, 98, 62, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 45, 109, 101, 103, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 45, 109, 101, 103, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 97, 99, 116, 105, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 51, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 97, 99, 116, 105, 111, 110, 115, 32, 46, 97, 99, 116, 105, 111, 110, 44, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 108, 111, 103, 111, 44, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 44, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 117, 115, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 32, 49, 46, 53, 114, 101, 109, 32, 46, 50, 53, 114, 101, 109, 32, 49, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 51, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 32, 115, 101, 99, 116, 105, 111, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 104, 101, 105, 103, 104, 116, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 98, 111, 116, 116, 111, 109, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 62, 46, 105, 110, 102, 111, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 46, 51, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 104, 101, 105, 103, 104, 116, 58, 50, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 98, 111, 116, 116, 111, 109, 58, 46, 51, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 121, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 52, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 90, 40, 48, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 90, 40, 48, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 97, 99, 116, 105, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 46, 55, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 97, 99, 116, 105, 111, 110, 115, 32, 46, 97, 99, 116, 105, 111, 110, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 54, 50, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 115, 104, 97, 100, 111, 119, 58, 48, 32, 48, 32, 46, 48, 54, 50, 53, 114, 101, 109, 32, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 115, 104, 97, 100, 111, 119, 58, 48, 32, 48, 32, 46, 48, 54, 50, 53, 114, 101, 109, 32, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 53, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 116, 114, 97, 110, 115, 108, 97, 116, 101, 89, 40, 45, 53, 48, 37, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 58, 104, 111, 118, 101, 114, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 58, 97, 99, 116, 105, 118, 101, 91, 99, 108, 97, 115, 115, 42, 61, 105, 99, 111, 110, 93, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 58, 101, 109, 112, 116, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 50, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 98, 111, 100, 121, 45, 102, 111, 111, 116, 101, 114, 45, 111, 117, 116, 115, 105, 100, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 102, 108, 101, 120, 45, 115, 116, 97, 114, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 45, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 115, 111, 108, 105, 100, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 48, 32, 48, 32, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 50, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 32, 46, 116, 97, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 99, 111, 108, 111, 114, 32, 46, 50, 53, 115, 44, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 58, 97, 99, 116, 105, 118, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 58, 58, 45, 109, 111, 122, 45, 115, 101, 108, 101, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 46, 115, 101, 108, 101, 99, 116, 101, 100, 44, 46, 116, 97, 98, 58, 58, 115, 101, 108, 101, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 98, 52, 50, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 58, 58, 45, 109, 111, 122, 45, 115, 101, 108, 101, 99, 116, 105, 111, 110, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 114, 103, 98, 97, 40, 50, 53, 53, 44, 49, 56, 48, 44, 52, 54, 44, 46, 49, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 46, 115, 101, 108, 101, 99, 116, 101, 100, 58, 104, 111, 118, 101, 114, 44, 46, 116, 97, 98, 58, 58, 115, 101, 108, 101, 99, 116, 105, 111, 110, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 114, 103, 98, 97, 40, 50, 53, 53, 44, 49, 56, 48, 44, 52, 54, 44, 46, 49, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 117, 115, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 114, 100, 101, 114, 58, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 108, 101, 102, 116, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 49, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 49, 48, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 32, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 62, 46, 117, 115, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 108, 101, 102, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 105, 110, 102, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 110, 97, 109, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 101, 108, 108, 105, 112, 115, 105, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 110, 97, 109, 101, 32, 46, 97, 99, 99, 111, 117, 110, 116, 45, 110, 97, 109, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 110, 97, 109, 101, 32, 46, 97, 99, 99, 111, 117, 110, 116, 45, 110, 97, 109, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 114, 111, 108, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 48, 32, 48, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 98, 111, 116, 116, 111, 109, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 101, 108, 108, 105, 112, 115, 105, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 105, 109, 97, 103, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 117, 115, 101, 114, 45, 105, 109, 97, 103, 101, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 32, 50, 114, 101, 109, 32, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 46, 56, 55, 53, 114, 101, 109, 32, 52, 46, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 43, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 104, 101, 105, 103, 104, 116, 58, 50, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 50, 51, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 43, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 46, 56, 55, 53, 114, 101, 109, 32, 49, 46, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 46, 118, 105, 101, 119, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 32, 46, 118, 105, 101, 119, 32, 46, 99, 104, 97, 114, 116, 46, 98, 97, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 51, 52, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 109, 101, 100, 105, 97, 32, 111, 110, 108, 121, 32, 115, 99, 114, 101, 101, 110, 32, 97, 110, 100, 32, 40, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 50, 48, 52, 56, 112, 120, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 52, 48, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 116, 97, 98, 108, 101, 45, 105, 99, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 104, 101, 97, 100, 101, 114, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 49, 54, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 32, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 115, 99, 114, 111, 108, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 49, 114, 101, 109, 41, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 44, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 46, 54, 50, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 49, 112, 120, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 104, 101, 97, 100, 101, 114, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 118, 105, 101, 119, 45, 114, 101, 112, 111, 114, 116, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 98, 97, 114, 45, 99, 104, 97, 114, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 55, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 118, 105, 101, 119, 45, 114, 101, 112, 111, 114, 116, 32, 46, 118, 105, 101, 119, 45, 99, 104, 97, 114, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 100, 114, 111, 112, 100, 111, 119, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 50, 52, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 100, 114, 111, 112, 100, 111, 119, 110, 32, 46, 83, 101, 108, 101, 99, 116, 45, 118, 97, 108, 117, 101, 45, 108, 97, 98, 101, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 112, 114, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 114, 111, 119, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 98, 111, 116, 116, 111, 109, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 50, 52, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 118, 105, 101, 119, 45, 100, 101, 116, 97, 105, 108, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 46, 53, 114, 101, 109, 32, 48, 32, 49, 46, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 118, 105, 101, 119, 45, 100, 101, 116, 97, 105, 108, 115, 32, 46, 118, 117, 108, 110, 45, 115, 101, 97, 114, 99, 104, 45, 98, 117, 116, 116, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 49, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 51, 45, 98, 111, 114, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 51, 53, 54, 98, 98, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 50, 45, 98, 111, 114, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 97, 51, 98, 98, 100, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 51, 45, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 51, 53, 54, 98, 98, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 50, 45, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 97, 51, 98, 98, 100, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 51, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 51, 55, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 114, 111, 119, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 50, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 54, 53, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 50, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 55, 48, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 95, 95, 99, 104, 101, 99, 107, 98, 111, 120, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 95, 95, 99, 104, 101, 99, 107, 98, 111, 120, 32, 105, 110, 112, 117, 116, 91, 116, 121, 112, 101, 61, 99, 104, 101, 99, 107, 98, 111, 120, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 99, 111, 108, 117, 109, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 32, 49, 46, 53, 114, 101, 109, 32, 49, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 101, 56, 101, 97, 101, 98, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 104, 101, 97, 100, 101, 114, 32, 104, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 117, 112, 112, 101, 114, 99, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 46, 118, 117, 108, 110, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 101, 110, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 106, 117, 115, 116, 105, 102, 121, 45, 99, 111, 110, 116, 101, 110, 116, 58, 115, 112, 97, 99, 101, 45, 98, 101, 116, 119, 101, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 55, 56, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 46, 118, 117, 108, 110, 45, 104, 101, 97, 100, 101, 114, 46, 112, 97, 100, 100, 105, 110, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 55, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 55, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 43, 46, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 51, 46, 55, 53, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 51, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 97, 103, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 58, 49, 32, 49, 32, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 52, 46, 50, 53, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 52, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 114, 111, 111, 116, 32, 46, 112, 97, 103, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 121, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 55, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 97, 103, 101, 46, 99, 111, 109, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 45, 121, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 97, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 101, 108, 108, 105, 112, 115, 105, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 32, 45, 119, 101, 98, 107, 105, 116, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 108, 105, 110, 101, 45, 99, 108, 97, 109, 112, 58, 32, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 98, 111, 120, 45, 111, 114, 105, 101, 110, 116, 58, 32, 118, 101, 114, 116, 105, 99, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 111, 114, 100, 45, 98, 114, 101, 97, 107, 58, 32, 98, 114, 101, 97, 107, 45, 97, 108, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 32, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 91, 99, 108, 97, 115, 115, 94, 61, 67, 97, 114, 100, 71, 114, 105, 100, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 114, 105, 100, 45, 97, 117, 116, 111, 45, 114, 111, 119, 115, 58, 97, 117, 116, 111, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 99, 97, 114, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 99, 97, 114, 100, 32, 46, 97, 99, 116, 105, 111, 110, 44, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 99, 97, 114, 100, 32, 104, 52, 44, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 99, 97, 114, 100, 32, 115, 101, 99, 116, 105, 111, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 49, 52, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 116, 97, 103, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 102, 108, 111, 119, 58, 114, 111, 119, 32, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 116, 97, 103, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 46, 97, 110, 116, 45, 116, 97, 103, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 98, 111, 116, 116, 111, 109, 58, 53, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 98, 97, 99, 107, 102, 97, 99, 101, 45, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 102, 97, 99, 101, 45, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 62, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 32, 46, 53, 114, 101, 109, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 62, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 105, 103, 110, 45, 105, 116, 101, 109, 115, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 114, 101, 109, 32, 46, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 46, 116, 111, 111, 108, 116, 105, 112, 45, 99, 111, 108, 117, 109, 110, 45, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 105, 115, 105, 98, 105, 108, 105, 116, 121, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 109, 111, 117, 115, 101, 65, 114, 101, 97, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 101, 119, 45, 114, 101, 115, 105, 122, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 45, 46, 51, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 105, 103, 104, 116, 45, 115, 116, 121, 108, 101, 58, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 105, 103, 104, 116, 45, 119, 105, 100, 116, 104, 58, 49, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 104, 105, 100, 100, 101, 110, 69, 108, 101, 109, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 58, 49, 112, 120, 32, 115, 111, 108, 105, 100, 32, 35, 56, 99, 57, 57, 57, 102, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 46, 54, 50, 53, 114, 101, 109, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 49, 112, 120, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 115, 67, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 98, 111, 100, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 98, 111, 100, 121, 82, 111, 119, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 114, 103, 98, 97, 40, 48, 44, 48, 44, 48, 44, 46, 48, 53, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 87, 114, 97, 112, 112, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 48, 50, 56, 52, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 45, 46, 48, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 46, 48, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 91, 99, 108, 97, 115, 115, 42, 61, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 93, 32, 46, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 100, 105, 118, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 118, 105, 115, 105, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 46, 55, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 115, 112, 97, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 105, 110, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 98, 111, 116, 116, 111, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 115, 112, 97, 110, 58, 111, 110, 108, 121, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 99, 97, 108, 99, 40, 49, 48, 48, 37, 32, 45, 32, 46, 53, 114, 101, 109, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 62, 115, 112, 97, 110, 58, 108, 97, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 44, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 49, 46, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 117, 110, 115, 101, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 97, 115, 66, 111, 116, 116, 111, 109, 66, 111, 114, 100, 101, 114, 44, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 45, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 109, 97, 105, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 48, 32, 49, 112, 120, 32, 48, 32, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 115, 67, 111, 110, 116, 97, 105, 110, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 45, 49, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 109, 117, 108, 116, 105, 45, 98, 117, 116, 116, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 102, 108, 101, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 108, 101, 120, 45, 100, 105, 114, 101, 99, 116, 105, 111, 110, 58, 114, 111, 119, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 114, 105, 103, 104, 116, 45, 98, 117, 116, 116, 111, 110, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 51, 56, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 102, 111, 111, 116, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 49, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 98, 117, 116, 116, 111, 110, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 116, 114, 97, 110, 115, 102, 111, 114, 109, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 116, 114, 97, 110, 115, 102, 111, 114, 109, 32, 46, 50, 53, 115, 44, 45, 119, 101, 98, 107, 105, 116, 45, 116, 114, 97, 110, 115, 102, 111, 114, 109, 32, 46, 50, 53, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 98, 111, 108, 100, 45, 116, 101, 120, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 45, 108, 101, 102, 116, 58, 50, 54, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 110, 112, 117, 116, 91, 116, 121, 112, 101, 61, 99, 104, 101, 99, 107, 98, 111, 120, 93, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 105, 122, 105, 110, 103, 58, 98, 111, 114, 100, 101, 114, 45, 98, 111, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 46, 56, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 46, 104, 97, 115, 45, 118, 97, 108, 117, 101, 46, 83, 101, 108, 101, 99, 116, 45, 45, 115, 105, 110, 103, 108, 101, 62, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 32, 46, 83, 101, 108, 101, 99, 116, 45, 118, 97, 108, 117, 101, 32, 46, 83, 101, 108, 101, 99, 116, 45, 118, 97, 108, 117, 101, 45, 108, 97, 98, 101, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 32, 105, 110, 112, 117, 116, 58, 58, 45, 109, 115, 45, 99, 108, 101, 97, 114, 44, 46, 83, 101, 108, 101, 99, 116, 32, 105, 110, 112, 117, 116, 58, 58, 45, 109, 115, 45, 114, 101, 118, 101, 97, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 110, 111, 110, 101, 33, 105, 109, 112, 111, 114, 116, 97, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 97, 114, 114, 111, 119, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 46, 51, 49, 50, 53, 114, 101, 109, 32, 46, 51, 49, 50, 53, 114, 101, 109, 32, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 97, 114, 114, 111, 119, 45, 122, 111, 110, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 116, 97, 98, 108, 101, 45, 99, 101, 108, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 51, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 99, 101, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 46, 53, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 116, 97, 98, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 45, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 108, 97, 112, 115, 101, 58, 115, 101, 112, 97, 114, 97, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 32, 35, 98, 97, 99, 50, 99, 53, 32, 35, 101, 56, 101, 97, 101, 98, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 116, 121, 108, 101, 58, 115, 111, 108, 105, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 119, 105, 100, 116, 104, 58, 49, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 114, 97, 100, 105, 117, 115, 58, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 115, 112, 97, 99, 105, 110, 103, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 51, 48, 52, 56, 53, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 46, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 114, 101, 108, 97, 116, 105, 118, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 48, 32, 49, 112, 120, 32, 48, 32, 114, 103, 98, 97, 40, 50, 44, 51, 50, 44, 52, 52, 44, 46, 48, 54, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 62, 58, 108, 97, 115, 116, 45, 99, 104, 105, 108, 100, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 51, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 32, 46, 83, 101, 108, 101, 99, 116, 45, 105, 110, 112, 117, 116, 58, 102, 111, 99, 117, 115, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 105, 110, 112, 117, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 50, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 104, 101, 105, 103, 104, 116, 58, 50, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 108, 101, 102, 116, 58, 46, 54, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 54, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 101, 114, 116, 105, 99, 97, 108, 45, 97, 108, 105, 103, 110, 58, 109, 105, 100, 100, 108, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 105, 110, 112, 117, 116, 62, 105, 110, 112, 117, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 48, 50, 50, 48, 50, 99, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 110, 111, 110, 101, 32, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 120, 45, 115, 104, 97, 100, 111, 119, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 100, 101, 102, 97, 117, 108, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 105, 115, 112, 108, 97, 121, 58, 105, 110, 108, 105, 110, 101, 45, 98, 108, 111, 99, 107, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 105, 110, 104, 101, 114, 105, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 49, 46, 48, 54, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 114, 103, 105, 110, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 117, 116, 108, 105, 110, 101, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 46, 53, 114, 101, 109, 32, 48, 32, 46, 55, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 119, 101, 98, 107, 105, 116, 45, 97, 112, 112, 101, 97, 114, 97, 110, 99, 101, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 109, 117, 108, 116, 105, 45, 118, 97, 108, 117, 101, 45, 119, 114, 97, 112, 112, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 54, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 45, 45, 115, 105, 110, 103, 108, 101, 62, 46, 83, 101, 108, 101, 99, 116, 45, 99, 111, 110, 116, 114, 111, 108, 32, 46, 83, 101, 108, 101, 99, 116, 45, 118, 97, 108, 117, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 98, 97, 99, 50, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 50, 46, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 97, 120, 45, 119, 105, 100, 116, 104, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 118, 101, 114, 102, 108, 111, 119, 58, 104, 105, 100, 100, 101, 110, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 108, 101, 102, 116, 58, 46, 54, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 45, 114, 105, 103, 104, 116, 58, 46, 54, 49, 50, 53, 114, 101, 109, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 111, 118, 101, 114, 102, 108, 111, 119, 58, 101, 108, 108, 105, 112, 115, 105, 115, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 110, 111, 119, 114, 97, 112, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 97, 98, 115, 111, 108, 117, 116, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 116, 116, 111, 109, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 102, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 105, 103, 104, 116, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 112, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 83, 101, 108, 101, 99, 116, 32, 46, 83, 101, 108, 101, 99, 116, 45, 97, 114, 114, 111, 119, 45, 122, 111, 110, 101, 58, 104, 111, 118, 101, 114, 62, 46, 83, 101, 108, 101, 99, 116, 45, 97, 114, 114, 111, 119, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 116, 111, 112, 45, 99, 111, 108, 111, 114, 58, 35, 53, 101, 55, 49, 55, 56, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 100, 101, 102, 97, 117, 108, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 56, 99, 57, 57, 57, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 99, 114, 105, 116, 105, 99, 97, 108, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 98, 56, 48, 99, 48, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 98, 56, 48, 99, 48, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 104, 105, 103, 104, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 49, 50, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 102, 102, 49, 50, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 109, 101, 100, 105, 117, 109, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 97, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 102, 102, 97, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 108, 111, 119, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 100, 49, 53, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 102, 102, 100, 49, 53, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 99, 116, 105, 111, 110, 32, 46, 97, 108, 101, 114, 116, 45, 105, 110, 102, 111, 45, 105, 99, 111, 110, 46, 97, 99, 116, 105, 118, 101, 58, 98, 101, 102, 111, 114, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 44, 46, 105, 110, 102, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 116, 114, 97, 110, 115, 112, 97, 114, 101, 110, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 32, 46, 105, 110, 102, 111, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 102, 111, 58, 104, 111, 118, 101, 114, 44, 98, 117, 116, 116, 111, 110, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 53, 52, 99, 53, 102, 50, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 102, 102, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 105, 110, 102, 111, 58, 97, 99, 116, 105, 118, 101, 44, 98, 117, 116, 116, 111, 110, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 58, 97, 99, 116, 105, 118, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 35, 49, 101, 55, 54, 57, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 108, 101, 114, 116, 45, 105, 110, 102, 111, 44, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 44, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 105, 108, 108, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 58, 104, 111, 118, 101, 114, 44, 98, 117, 116, 116, 111, 110, 46, 97, 108, 101, 114, 116, 45, 105, 110, 102, 111, 58, 104, 111, 118, 101, 114, 44, 98, 117, 116, 116, 111, 110, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 58, 104, 111, 118, 101, 114, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 50, 54, 57, 56, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 54, 57, 56, 99, 53, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 46, 98, 117, 116, 116, 111, 110, 45, 108, 105, 110, 107, 58, 97, 99, 116, 105, 118, 101, 44, 98, 117, 116, 116, 111, 110, 46, 97, 108, 101, 114, 116, 45, 105, 110, 102, 111, 58, 97, 99, 116, 105, 118, 101, 44, 98, 117, 116, 116, 111, 110, 46, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 46, 105, 99, 111, 110, 45, 111, 110, 108, 121, 58, 97, 99, 116, 105, 118, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 58, 110, 111, 110, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 108, 111, 114, 58, 35, 50, 101, 98, 57, 102, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 117, 114, 115, 111, 114, 58, 112, 111, 105, 110, 116, 101, 114, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 102, 114, 111, 109, 58, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 101, 98, 45, 115, 100, 107, 46, 97, 112, 116, 114, 105, 110, 115, 105, 99, 46, 99, 111, 109, 47, 115, 116, 121, 108, 101, 46, 99, 115, 115, 63, 97, 61, 65, 80, 45, 70, 73, 85, 69, 74, 71, 90, 75, 52, 73, 90, 77, 45, 50, 32, 42, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 112, 116, 45, 103, 117, 105, 100, 101, 45, 111, 118, 101, 114, 108, 97, 121, 45, 116, 111, 112, 44, 46, 97, 112, 116, 45, 103, 117, 105, 100, 101, 45, 111, 118, 101, 114, 108, 97, 121, 45, 98, 111, 116, 116, 111, 109, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 49, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 57, 57, 57, 57, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 97, 112, 116, 45, 103, 117, 105, 100, 101, 45, 111, 118, 101, 114, 108, 97, 121, 45, 108, 101, 102, 116, 44, 46, 97, 112, 116, 45, 103, 117, 105, 100, 101, 45, 111, 118, 101, 114, 108, 97, 121, 45, 114, 105, 103, 104, 116, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 104, 101, 105, 103, 104, 116, 58, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 102, 105, 120, 101, 100, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 97, 110, 115, 105, 116, 105, 111, 110, 58, 97, 108, 108, 32, 49, 115, 32, 101, 97, 115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 122, 45, 105, 110, 100, 101, 120, 58, 57, 57, 57, 57, 57, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 107, 101, 121, 102, 114, 97, 109, 101, 115, 32, 42, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 45, 119, 101, 98, 107, 105, 116, 45, 107, 101, 121, 102, 114, 97, 109, 101, 115, 32, 102, 97, 100, 101, 73, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 48, 37, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 107, 101, 121, 102, 114, 97, 109, 101, 115, 32, 102, 97, 100, 101, 73, 110, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 48, 37, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 112, 97, 99, 105, 116, 121, 58, 46, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 111, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 116, 116, 111, 109, 58, 49, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 42, 33, 32, 67, 83, 83, 32, 85, 115, 101, 100, 32, 102, 111, 110, 116, 102, 97, 99, 101, 115, 32, 42, 47, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 34, 105, 99, 111, 110, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 101, 111, 116, 63, 52, 56, 51, 48, 49, 50, 51, 56, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 101, 111, 116, 63, 52, 56, 51, 48, 49, 50, 51, 56, 35, 105, 101, 102, 105, 120, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 101, 109, 98, 101, 100, 100, 101, 100, 45, 111, 112, 101, 110, 116, 121, 112, 101, 34, 41, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 119, 111, 102, 102, 50, 63, 52, 56, 51, 48, 49, 50, 51, 56, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 119, 111, 102, 102, 50, 34, 41, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 119, 111, 102, 102, 63, 52, 56, 51, 48, 49, 50, 51, 56, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 119, 111, 102, 102, 34, 41, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 116, 116, 102, 63, 52, 56, 51, 48, 49, 50, 51, 56, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 116, 114, 117, 101, 116, 121, 112, 101, 34, 41, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 114, 108, 40, 39, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 99, 104, 97, 108, 108, 121, 45, 97, 114, 116, 105, 102, 97, 99, 116, 115, 46, 115, 51, 45, 117, 115, 45, 119, 101, 115, 116, 45, 50, 46, 97, 109, 97, 122, 111, 110, 97, 119, 115, 46, 99, 111, 109, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 99, 108, 105, 45, 112, 114, 111, 100, 47, 104, 116, 109, 108, 45, 102, 101, 97, 116, 117, 114, 101, 47, 105, 99, 111, 110, 46, 115, 118, 103, 63, 52, 56, 51, 48, 49, 50, 51, 56, 35, 105, 99, 111, 110, 39, 41, 32, 102, 111, 114, 109, 97, 116, 40, 34, 115, 118, 103, 34, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 57, 119, 52, 66, 77, 85, 84, 80, 72, 104, 55, 85, 83, 83, 119, 97, 80, 71, 81, 51, 113, 53, 100, 48, 78, 55, 119, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 57, 119, 52, 66, 77, 85, 84, 80, 72, 104, 55, 85, 83, 83, 119, 105, 80, 71, 81, 51, 113, 53, 100, 48, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 121, 119, 52, 66, 77, 85, 84, 80, 72, 106, 120, 65, 119, 88, 105, 87, 116, 70, 67, 102, 81, 55, 65, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 121, 119, 52, 66, 77, 85, 84, 80, 72, 106, 120, 52, 119, 88, 105, 87, 116, 70, 67, 99, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 57, 119, 52, 66, 77, 85, 84, 80, 72, 104, 54, 85, 86, 83, 119, 97, 80, 71, 81, 51, 113, 53, 100, 48, 78, 55, 119, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 76, 97, 116, 111, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 76, 97, 116, 111, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 108, 97, 116, 111, 47, 118, 49, 55, 47, 83, 54, 117, 57, 119, 52, 66, 77, 85, 84, 80, 72, 104, 54, 85, 86, 83, 119, 105, 80, 71, 81, 51, 113, 53, 100, 48, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 88, 45, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 54, 48, 45, 48, 53, 50, 70, 44, 32, 85, 43, 49, 67, 56, 48, 45, 49, 67, 56, 56, 44, 32, 85, 43, 50, 48, 66, 52, 44, 32, 85, 43, 50, 68, 69, 48, 45, 50, 68, 70, 70, 44, 32, 85, 43, 65, 54, 52, 48, 45, 65, 54, 57, 70, 44, 32, 85, 43, 70, 69, 50, 69, 45, 70, 69, 50, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 86, 117, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 48, 48, 45, 48, 52, 53, 70, 44, 32, 85, 43, 48, 52, 57, 48, 45, 48, 52, 57, 49, 44, 32, 85, 43, 48, 52, 66, 48, 45, 48, 52, 66, 49, 44, 32, 85, 43, 50, 49, 49, 54, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 88, 117, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 49, 70, 48, 48, 45, 49, 70, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 85, 101, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 51, 55, 48, 45, 48, 51, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 88, 101, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 50, 45, 48, 49, 48, 51, 44, 32, 85, 43, 48, 49, 49, 48, 45, 48, 49, 49, 49, 44, 32, 85, 43, 48, 49, 50, 56, 45, 48, 49, 50, 57, 44, 32, 85, 43, 48, 49, 54, 56, 45, 48, 49, 54, 57, 44, 32, 85, 43, 48, 49, 65, 48, 45, 48, 49, 65, 49, 44, 32, 85, 43, 48, 49, 65, 70, 45, 48, 49, 66, 48, 44, 32, 85, 43, 49, 69, 65, 48, 45, 49, 69, 70, 57, 44, 32, 85, 43, 50, 48, 65, 66, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 88, 79, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 51, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 76, 105, 103, 104, 116, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 76, 105, 103, 104, 116, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 95, 114, 56, 79, 85, 117, 104, 112, 75, 75, 83, 84, 106, 119, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 87, 74, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 54, 48, 45, 48, 53, 50, 70, 44, 32, 85, 43, 49, 67, 56, 48, 45, 49, 67, 56, 56, 44, 32, 85, 43, 50, 48, 66, 52, 44, 32, 85, 43, 50, 68, 69, 48, 45, 50, 68, 70, 70, 44, 32, 85, 43, 65, 54, 52, 48, 45, 65, 54, 57, 70, 44, 32, 85, 43, 70, 69, 50, 69, 45, 70, 69, 50, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 85, 90, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 48, 48, 45, 48, 52, 53, 70, 44, 32, 85, 43, 48, 52, 57, 48, 45, 48, 52, 57, 49, 44, 32, 85, 43, 48, 52, 66, 48, 45, 48, 52, 66, 49, 44, 32, 85, 43, 50, 49, 49, 54, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 87, 90, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 49, 70, 48, 48, 45, 49, 70, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 86, 112, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 51, 55, 48, 45, 48, 51, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 87, 112, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 50, 45, 48, 49, 48, 51, 44, 32, 85, 43, 48, 49, 49, 48, 45, 48, 49, 49, 49, 44, 32, 85, 43, 48, 49, 50, 56, 45, 48, 49, 50, 57, 44, 32, 85, 43, 48, 49, 54, 56, 45, 48, 49, 54, 57, 44, 32, 85, 43, 48, 49, 65, 48, 45, 48, 49, 65, 49, 44, 32, 85, 43, 48, 49, 65, 70, 45, 48, 49, 66, 48, 44, 32, 85, 43, 49, 69, 65, 48, 45, 49, 69, 70, 57, 44, 32, 85, 43, 50, 48, 65, 66, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 87, 53, 48, 98, 102, 56, 112, 107, 65, 112, 54, 97, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 52, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 82, 101, 103, 117, 108, 97, 114, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 56, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 70, 86, 90, 48, 98, 102, 56, 112, 107, 65, 103, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 88, 45, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 54, 48, 45, 48, 53, 50, 70, 44, 32, 85, 43, 49, 67, 56, 48, 45, 49, 67, 56, 56, 44, 32, 85, 43, 50, 48, 66, 52, 44, 32, 85, 43, 50, 68, 69, 48, 45, 50, 68, 70, 70, 44, 32, 85, 43, 65, 54, 52, 48, 45, 65, 54, 57, 70, 44, 32, 85, 43, 70, 69, 50, 69, 45, 70, 69, 50, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 86, 117, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 52, 48, 48, 45, 48, 52, 53, 70, 44, 32, 85, 43, 48, 52, 57, 48, 45, 48, 52, 57, 49, 44, 32, 85, 43, 48, 52, 66, 48, 45, 48, 52, 66, 49, 44, 32, 85, 43, 50, 49, 49, 54, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 88, 117, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 49, 70, 48, 48, 45, 49, 70, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 85, 101, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 51, 55, 48, 45, 48, 51, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 88, 101, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 50, 45, 48, 49, 48, 51, 44, 32, 85, 43, 48, 49, 49, 48, 45, 48, 49, 49, 49, 44, 32, 85, 43, 48, 49, 50, 56, 45, 48, 49, 50, 57, 44, 32, 85, 43, 48, 49, 54, 56, 45, 48, 49, 54, 57, 44, 32, 85, 43, 48, 49, 65, 48, 45, 48, 49, 65, 49, 44, 32, 85, 43, 48, 49, 65, 70, 45, 48, 49, 66, 48, 44, 32, 85, 43, 49, 69, 65, 48, 45, 49, 69, 70, 57, 44, 32, 85, 43, 50, 48, 65, 66, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 88, 79, 104, 112, 75, 75, 83, 84, 106, 53, 80, 87, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 49, 48, 48, 45, 48, 50, 52, 70, 44, 32, 85, 43, 48, 50, 53, 57, 44, 32, 85, 43, 49, 69, 48, 48, 45, 49, 69, 70, 70, 44, 32, 85, 43, 50, 48, 50, 48, 44, 32, 85, 43, 50, 48, 65, 48, 45, 50, 48, 65, 66, 44, 32, 85, 43, 50, 48, 65, 68, 45, 50, 48, 67, 70, 44, 32, 85, 43, 50, 49, 49, 51, 44, 32, 85, 43, 50, 67, 54, 48, 45, 50, 67, 55, 70, 44, 32, 85, 43, 65, 55, 50, 48, 45, 65, 55, 70, 70, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 64, 102, 111, 110, 116, 45, 102, 97, 99, 101, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 39, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 115, 116, 121, 108, 101, 58, 110, 111, 114, 109, 97, 108, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 55, 48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 114, 99, 58, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 32, 83, 97, 110, 115, 32, 66, 111, 108, 100, 39, 41, 44, 32, 108, 111, 99, 97, 108, 40, 39, 79, 112, 101, 110, 83, 97, 110, 115, 45, 66, 111, 108, 100, 39, 41, 44, 32, 117, 114, 108, 40, 104, 116, 116, 112, 115, 58, 47, 47, 102, 111, 110, 116, 115, 46, 103, 115, 116, 97, 116, 105, 99, 46, 99, 111, 109, 47, 115, 47, 111, 112, 101, 110, 115, 97, 110, 115, 47, 118, 49, 56, 47, 109, 101, 109, 53, 89, 97, 71, 115, 49, 50, 54, 77, 105, 90, 112, 66, 65, 45, 85, 78, 55, 114, 103, 79, 85, 117, 104, 112, 75, 75, 83, 84, 106, 119, 46, 119, 111, 102, 102, 50, 41, 32, 102, 111, 114, 109, 97, 116, 40, 39, 119, 111, 102, 102, 50, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 117, 110, 105, 99, 111, 100, 101, 45, 114, 97, 110, 103, 101, 58, 85, 43, 48, 48, 48, 48, 45, 48, 48, 70, 70, 44, 32, 85, 43, 48, 49, 51, 49, 44, 32, 85, 43, 48, 49, 53, 50, 45, 48, 49, 53, 51, 44, 32, 85, 43, 48, 50, 66, 66, 45, 48, 50, 66, 67, 44, 32, 85, 43, 48, 50, 67, 54, 44, 32, 85, 43, 48, 50, 68, 65, 44, 32, 85, 43, 48, 50, 68, 67, 44, 32, 85, 43, 50, 48, 48, 48, 45, 50, 48, 54, 70, 44, 32, 85, 43, 50, 48, 55, 52, 44, 32, 85, 43, 50, 48, 65, 67, 44, 32, 85, 43, 50, 49, 50, 50, 44, 32, 85, 43, 50, 49, 57, 49, 44, 32, 85, 43, 50, 49, 57, 51, 44, 32, 85, 43, 50, 50, 49, 50, 44, 32, 85, 43, 50, 50, 49, 53, 44, 32, 85, 43, 70, 69, 70, 70, 44, 32, 85, 43, 70, 70, 70, 68, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 116, 121, 108, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 99, 114, 105, 112, 116, 32, 100, 97, 116, 97, 45, 100, 97, 112, 112, 45, 100, 101, 116, 101, 99, 116, 105, 111, 110, 61, 34, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 40, 102, 117, 110, 99, 116, 105, 111, 110, 40, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 108, 101, 116, 32, 97, 108, 114, 101, 97, 100, 121, 73, 110, 115, 101, 114, 116, 101, 100, 77, 101, 116, 97, 84, 97, 103, 32, 61, 32, 102, 97, 108, 115, 101, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 95, 95, 105, 110, 115, 101, 114, 116, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 101, 100, 40, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 33, 97, 108, 114, 101, 97, 100, 121, 73, 110, 115, 101, 114, 116, 101, 100, 77, 101, 116, 97, 84, 97, 103, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 115, 116, 32, 109, 101, 116, 97, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 39, 109, 101, 116, 97, 39, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 109, 101, 116, 97, 46, 110, 97, 109, 101, 32, 61, 32, 39, 100, 97, 112, 112, 45, 100, 101, 116, 101, 99, 116, 101, 100, 39, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 104, 101, 97, 100, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 109, 101, 116, 97, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 114, 101, 97, 100, 121, 73, 110, 115, 101, 114, 116, 101, 100, 77, 101, 116, 97, 84, 97, 103, 32, 61, 32, 116, 114, 117, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 119, 105, 110, 100, 111, 119, 46, 104, 97, 115, 79, 119, 110, 80, 114, 111, 112, 101, 114, 116, 121, 40, 39, 119, 101, 98, 51, 39, 41, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 78, 111, 116, 101, 32, 97, 32, 99, 108, 111, 115, 117, 114, 101, 32, 99, 97, 110, 39, 116, 32, 98, 101, 32, 117, 115, 101, 100, 32, 102, 111, 114, 32, 116, 104, 105, 115, 32, 118, 97, 114, 32, 98, 101, 99, 97, 117, 115, 101, 32, 115, 111, 109, 101, 32, 115, 105, 116, 101, 115, 32, 108, 105, 107, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 119, 119, 119, 46, 119, 110, 121, 99, 46, 111, 114, 103, 32, 100, 111, 32, 97, 32, 115, 101, 99, 111, 110, 100, 32, 115, 99, 114, 105, 112, 116, 32, 101, 120, 101, 99, 117, 116, 105, 111, 110, 32, 118, 105, 97, 32, 101, 118, 97, 108, 32, 102, 111, 114, 32, 115, 111, 109, 101, 32, 114, 101, 97, 115, 111, 110, 46, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 110, 100, 111, 119, 46, 95, 95, 100, 105, 115, 97, 98, 108, 101, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 105, 111, 110, 73, 110, 115, 101, 114, 116, 105, 111, 110, 32, 61, 32, 116, 114, 117, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 76, 105, 107, 101, 108, 121, 32, 111, 108, 100, 87, 101, 98, 51, 32, 105, 115, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 97, 110, 100, 32, 105, 116, 32, 104, 97, 115, 32, 97, 32, 112, 114, 111, 112, 101, 114, 116, 121, 32, 111, 110, 108, 121, 32, 98, 101, 99, 97, 117, 115, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 119, 101, 32, 100, 101, 102, 105, 110, 101, 100, 32, 105, 116, 46, 32, 83, 111, 109, 101, 32, 115, 105, 116, 101, 115, 32, 108, 105, 107, 101, 32, 119, 110, 121, 99, 46, 111, 114, 103, 32, 97, 114, 101, 32, 101, 118, 97, 108, 105, 110, 103, 32, 97, 108, 108, 32, 115, 99, 114, 105, 112, 116, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 32, 116, 104, 97, 116, 32, 101, 120, 105, 115, 116, 32, 97, 103, 97, 105, 110, 44, 32, 115, 111, 32, 116, 104, 105, 115, 32, 105, 115, 32, 112, 114, 111, 116, 101, 99, 116, 105, 111, 110, 32, 97, 103, 97, 105, 110, 115, 116, 32, 109, 117, 108, 116, 105, 112, 108, 101, 32, 99, 97, 108, 108, 115, 46, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 119, 105, 110, 100, 111, 119, 46, 119, 101, 98, 51, 32, 61, 61, 61, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 101, 116, 117, 114, 110, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 95, 105, 110, 115, 101, 114, 116, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 101, 100, 40, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 32, 101, 108, 115, 101, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 97, 114, 32, 111, 108, 100, 87, 101, 98, 51, 32, 61, 32, 119, 105, 110, 100, 111, 119, 46, 119, 101, 98, 51, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 79, 98, 106, 101, 99, 116, 46, 100, 101, 102, 105, 110, 101, 80, 114, 111, 112, 101, 114, 116, 121, 40, 119, 105, 110, 100, 111, 119, 44, 32, 39, 119, 101, 98, 51, 39, 44, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 111, 110, 102, 105, 103, 117, 114, 97, 98, 108, 101, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 115, 101, 116, 58, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 118, 97, 108, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 33, 119, 105, 110, 100, 111, 119, 46, 95, 95, 100, 105, 115, 97, 98, 108, 101, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 105, 111, 110, 73, 110, 115, 101, 114, 116, 105, 111, 110, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 95, 105, 110, 115, 101, 114, 116, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 101, 100, 40, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 108, 100, 87, 101, 98, 51, 32, 61, 32, 118, 97, 108, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 101, 116, 58, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 33, 119, 105, 110, 100, 111, 119, 46, 95, 95, 100, 105, 115, 97, 98, 108, 101, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 105, 111, 110, 73, 110, 115, 101, 114, 116, 105, 111, 110, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 95, 105, 110, 115, 101, 114, 116, 68, 97, 112, 112, 68, 101, 116, 101, 99, 116, 101, 100, 40, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 114, 101, 116, 117, 114, 110, 32, 111, 108, 100, 87, 101, 98, 51, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 41, 40, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 99, 114, 105, 112, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 99, 114, 105, 112, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 116, 101, 120, 116, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 97, 114, 32, 116, 101, 120, 116, 65, 114, 101, 97, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 116, 101, 120, 116, 97, 114, 101, 97, 34, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 118, 97, 108, 117, 101, 32, 61, 32, 116, 101, 120, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 115, 116, 121, 108, 101, 46, 116, 111, 112, 32, 61, 32, 34, 48, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 115, 116, 121, 108, 101, 46, 108, 101, 102, 116, 32, 61, 32, 34, 48, 34, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 115, 116, 121, 108, 101, 46, 112, 111, 115, 105, 116, 105, 111, 110, 32, 61, 32, 34, 102, 105, 120, 101, 100, 34, 59, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 98, 111, 100, 121, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 116, 101, 120, 116, 65, 114, 101, 97, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 102, 111, 99, 117, 115, 40, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 65, 114, 101, 97, 46, 115, 101, 108, 101, 99, 116, 40, 41, 59, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 114, 121, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 118, 97, 114, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 101, 120, 101, 99, 67, 111, 109, 109, 97, 110, 100, 40, 39, 99, 111, 112, 121, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 102, 32, 40, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 101, 114, 116, 40, 39, 34, 39, 32, 43, 32, 116, 101, 120, 116, 32, 43, 32, 39, 34, 32, 99, 111, 112, 105, 101, 100, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 46, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 32, 101, 108, 115, 101, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 101, 114, 116, 40, 39, 85, 110, 97, 98, 108, 101, 32, 116, 111, 32, 99, 111, 112, 121, 32, 116, 101, 120, 116, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 46, 39, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 32, 99, 97, 116, 99, 104, 32, 40, 101, 114, 114, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 108, 101, 114, 116, 40, 39, 70, 97, 108, 108, 98, 97, 99, 107, 58, 32, 79, 111, 112, 115, 44, 32, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 99, 111, 112, 121, 39, 44, 32, 101, 114, 114, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 98, 111, 100, 121, 46, 114, 101, 109, 111, 118, 101, 67, 104, 105, 108, 100, 40, 116, 101, 120, 116, 65, 114, 101, 97, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 99, 114, 105, 112, 116, 62, 10, 32, 32, 32, 32, 60, 47, 104, 101, 97, 100, 62, 10, 32, 32, 32, 32, 60, 98, 111, 100, 121, 32, 100, 97, 116, 97, 45, 110, 101, 119, 45, 103, 114, 45, 99, 45, 115, 45, 99, 104, 101, 99, 107, 45, 108, 111, 97, 100, 101, 100, 61, 34, 49, 52, 46, 57, 56, 50, 46, 48, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 105, 100, 61, 34, 109, 97, 105, 110, 95, 98, 111, 100, 121, 95, 119, 114, 97, 112, 112, 101, 114, 95, 99, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 65, 112, 112, 67, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 105, 100, 61, 34, 110, 111, 116, 105, 102, 105, 99, 97, 116, 105, 111, 110, 115, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 101, 97, 100, 101, 114, 32, 99, 108, 97, 115, 115, 61, 34, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 108, 111, 103, 111, 32, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 34, 32, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 123, 123, 46, 65, 99, 99, 111, 117, 110, 116, 125, 125, 46, 108, 97, 99, 101, 119, 111, 114, 107, 46, 110, 101, 116, 47, 117, 105, 47, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 47, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 32, 116, 105, 116, 108, 101, 61, 34, 76, 97, 99, 101, 119, 111, 114, 107, 32, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 102, 105, 103, 117, 114, 101, 32, 99, 108, 97, 115, 115, 61, 34, 108, 111, 103, 111, 34, 62, 60, 105, 109, 103, 32, 115, 114, 99, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 123, 123, 46, 65, 99, 99, 111, 117, 110, 116, 125, 125, 46, 108, 97, 99, 101, 119, 111, 114, 107, 46, 110, 101, 116, 47, 117, 105, 47, 105, 109, 97, 103, 101, 115, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 108, 111, 103, 111, 45, 119, 104, 105, 116, 101, 46, 115, 118, 103, 34, 32, 116, 105, 116, 108, 101, 61, 34, 76, 97, 99, 101, 119, 111, 114, 107, 32, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 32, 99, 108, 97, 115, 115, 61, 34, 108, 111, 103, 111, 45, 105, 99, 111, 110, 34, 32, 97, 108, 116, 61, 34, 34, 62, 60, 47, 102, 105, 103, 117, 114, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 97, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 105, 99, 111, 110, 32, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 34, 32, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 123, 123, 46, 65, 99, 99, 111, 117, 110, 116, 125, 125, 46, 108, 97, 99, 101, 119, 111, 114, 107, 46, 110, 101, 116, 47, 117, 105, 47, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 47, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 32, 116, 105, 116, 108, 101, 61, 34, 76, 97, 99, 101, 119, 111, 114, 107, 32, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 102, 105, 103, 117, 114, 101, 32, 99, 108, 97, 115, 115, 61, 34, 108, 111, 103, 111, 34, 62, 60, 105, 109, 103, 32, 115, 114, 99, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 123, 123, 46, 65, 99, 99, 111, 117, 110, 116, 125, 125, 46, 108, 97, 99, 101, 119, 111, 114, 107, 46, 110, 101, 116, 47, 117, 105, 47, 105, 109, 97, 103, 101, 115, 47, 108, 97, 99, 101, 119, 111, 114, 107, 45, 105, 99, 111, 110, 45, 119, 104, 105, 116, 101, 46, 115, 118, 103, 34, 32, 116, 105, 116, 108, 101, 61, 34, 76, 97, 99, 101, 119, 111, 114, 107, 32, 68, 97, 115, 104, 98, 111, 97, 114, 100, 34, 32, 99, 108, 97, 115, 115, 61, 34, 108, 111, 103, 111, 45, 105, 99, 111, 110, 34, 32, 97, 108, 116, 61, 34, 34, 62, 60, 47, 102, 105, 103, 117, 114, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 97, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 115, 32, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 45, 109, 101, 103, 97, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 100, 101, 102, 97, 117, 108, 116, 34, 62, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 99, 108, 101, 97, 114, 32, 105, 110, 102, 111, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 45, 98, 117, 116, 116, 111, 110, 32, 32, 105, 99, 111, 110, 45, 109, 101, 110, 117, 32, 110, 117, 108, 108, 32, 118, 101, 114, 116, 105, 99, 97, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 82, 101, 99, 101, 110, 116, 32, 100, 111, 115, 115, 105, 101, 114, 115, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 62, 77, 101, 110, 117, 60, 47, 98, 117, 116, 116, 111, 110, 62, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 98, 45, 98, 97, 114, 32, 104, 101, 97, 100, 101, 114, 45, 109, 97, 105, 110, 45, 109, 101, 110, 117, 34, 62, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 117, 115, 101, 114, 34, 32, 116, 105, 116, 108, 101, 61, 34, 80, 114, 111, 102, 105, 108, 101, 32, 79, 112, 116, 105, 111, 110, 115, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 117, 115, 101, 114, 45, 105, 110, 102, 111, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 117, 115, 101, 114, 45, 114, 111, 108, 101, 34, 62, 60, 104, 52, 62, 76, 111, 99, 97, 108, 32, 77, 111, 100, 101, 60, 47, 104, 52, 62, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 32, 117, 115, 101, 114, 45, 105, 109, 97, 103, 101, 32, 117, 115, 101, 114, 45, 105, 109, 97, 103, 101, 45, 99, 111, 108, 111, 114, 32, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 45, 99, 105, 114, 99, 108, 101, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 51, 50, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 51, 50, 112, 120, 59, 32, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 32, 51, 50, 112, 120, 59, 32, 102, 111, 110, 116, 45, 115, 105, 122, 101, 58, 32, 49, 114, 101, 109, 59, 34, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 97, 110, 116, 45, 97, 118, 97, 116, 97, 114, 45, 115, 116, 114, 105, 110, 103, 34, 32, 115, 116, 121, 108, 101, 61, 34, 108, 105, 110, 101, 45, 104, 101, 105, 103, 104, 116, 58, 32, 51, 50, 112, 120, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 115, 99, 97, 108, 101, 40, 49, 41, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 88, 40, 45, 53, 48, 37, 41, 59, 34, 62, 67, 76, 73, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 104, 101, 97, 100, 101, 114, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 105, 110, 118, 101, 115, 116, 105, 103, 97, 116, 105, 111, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 99, 111, 109, 112, 32, 112, 97, 103, 101, 32, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 112, 97, 103, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 101, 97, 100, 101, 114, 32, 99, 108, 97, 115, 115, 61, 34, 104, 101, 97, 100, 101, 114, 45, 100, 111, 115, 115, 105, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 105, 110, 102, 111, 45, 98, 97, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 105, 99, 111, 110, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 118, 117, 108, 110, 34, 62, 60, 115, 112, 97, 110, 62, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 121, 32, 65, 115, 115, 101, 115, 115, 109, 101, 110, 116, 38, 110, 98, 115, 112, 59, 38, 110, 98, 115, 112, 59, 38, 103, 116, 59, 38, 110, 98, 115, 112, 59, 38, 110, 98, 115, 112, 59, 73, 109, 97, 103, 101, 60, 47, 115, 112, 97, 110, 62, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 115, 32, 32, 32, 99, 101, 110, 116, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 105, 110, 112, 117, 116, 45, 108, 97, 98, 101, 108, 34, 62, 60, 47, 115, 112, 97, 110, 62, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 100, 101, 102, 97, 117, 108, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 83, 101, 108, 101, 99, 116, 32, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 100, 114, 111, 112, 100, 111, 119, 110, 32, 104, 97, 115, 45, 118, 97, 108, 117, 101, 32, 105, 115, 45, 115, 101, 97, 114, 99, 104, 97, 98, 108, 101, 32, 83, 101, 108, 101, 99, 116, 45, 45, 115, 105, 110, 103, 108, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 105, 110, 112, 117, 116, 32, 110, 97, 109, 101, 61, 34, 102, 111, 114, 109, 45, 102, 105, 101, 108, 100, 45, 110, 97, 109, 101, 34, 32, 116, 121, 112, 101, 61, 34, 104, 105, 100, 100, 101, 110, 34, 32, 118, 97, 108, 117, 101, 61, 34, 99, 98, 53, 99, 52, 52, 100, 97, 97, 54, 49, 98, 53, 51, 48, 53, 48, 52, 57, 57, 50, 50, 50, 48, 56, 52, 55, 52, 52, 98, 52, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 104, 101, 97, 100, 101, 114, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 99, 111, 109, 112, 108, 105, 97, 110, 99, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 103, 114, 105, 100, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 115, 117, 109, 109, 97, 114, 121, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 67, 97, 114, 100, 71, 114, 105, 100, 45, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 82, 101, 112, 111, 115, 105, 116, 111, 114, 121, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 32, 110, 117, 108, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 111, 112, 121, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 39, 123, 123, 46, 82, 101, 112, 111, 115, 105, 116, 111, 114, 121, 125, 125, 39, 41, 34, 62, 60, 47, 98, 117, 116, 116, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 32, 99, 108, 97, 115, 115, 61, 34, 115, 117, 109, 109, 97, 114, 121, 45, 101, 108, 108, 105, 112, 115, 105, 115, 34, 62, 123, 123, 46, 82, 101, 112, 111, 115, 105, 116, 111, 114, 121, 125, 125, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 73, 68, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 32, 110, 117, 108, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 111, 112, 121, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 39, 123, 123, 46, 73, 68, 125, 125, 39, 41, 34, 41, 62, 60, 47, 98, 117, 116, 116, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 32, 99, 108, 97, 115, 115, 61, 34, 115, 117, 109, 109, 97, 114, 121, 45, 101, 108, 108, 105, 112, 115, 105, 115, 34, 62, 123, 123, 46, 73, 68, 125, 125, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 68, 105, 103, 101, 115, 116, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 32, 110, 117, 108, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 111, 112, 121, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 39, 123, 123, 46, 68, 105, 103, 101, 115, 116, 125, 125, 39, 41, 34, 62, 60, 47, 98, 117, 116, 116, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 32, 99, 108, 97, 115, 115, 61, 34, 115, 117, 109, 109, 97, 114, 121, 45, 101, 108, 108, 105, 112, 115, 105, 115, 34, 62, 123, 123, 46, 68, 105, 103, 101, 115, 116, 125, 125, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 73, 109, 97, 103, 101, 32, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109, 101, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 32, 99, 108, 97, 115, 115, 61, 34, 34, 62, 123, 123, 46, 67, 114, 101, 97, 116, 101, 100, 84, 105, 109, 101, 125, 125, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 123, 123, 46, 83, 105, 122, 101, 125, 125, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 53, 32, 99, 108, 97, 115, 115, 61, 34, 34, 62, 73, 109, 97, 103, 101, 32, 83, 105, 122, 101, 60, 47, 104, 53, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 99, 97, 114, 100, 32, 111, 114, 100, 101, 114, 45, 48, 32, 104, 45, 49, 32, 119, 45, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 32, 108, 97, 98, 101, 108, 34, 62, 84, 97, 103, 115, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 116, 97, 103, 115, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 103, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 114, 97, 110, 103, 101, 32, 36, 116, 97, 103, 32, 58, 61, 32, 46, 84, 97, 103, 115, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 97, 110, 116, 45, 116, 97, 103, 34, 62, 123, 123, 32, 36, 116, 97, 103, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 118, 105, 101, 119, 32, 118, 117, 108, 110, 45, 118, 105, 101, 119, 45, 114, 101, 112, 111, 114, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 114, 111, 119, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 103, 114, 111, 117, 112, 45, 104, 101, 97, 100, 101, 114, 34, 62, 123, 123, 46, 84, 111, 116, 97, 108, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 32, 117, 110, 105, 113, 117, 101, 32, 118, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 32, 119, 101, 114, 101, 32, 100, 101, 116, 101, 99, 116, 101, 100, 32, 105, 110, 32, 116, 104, 105, 115, 32, 97, 115, 115, 101, 115, 115, 109, 101, 110, 116, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 52, 62, 60, 115, 112, 97, 110, 62, 123, 123, 46, 70, 105, 120, 97, 98, 108, 101, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 32, 102, 105, 120, 101, 100, 32, 118, 101, 114, 115, 105, 111, 110, 115, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 60, 47, 115, 112, 97, 110, 62, 60, 47, 104, 52, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 118, 105, 101, 119, 45, 99, 104, 97, 114, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 45, 114, 101, 112, 111, 114, 116, 45, 98, 97, 114, 45, 99, 104, 97, 114, 116, 34, 32, 105, 100, 61, 34, 98, 97, 114, 45, 99, 104, 97, 114, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 118, 103, 32, 99, 108, 97, 115, 115, 61, 34, 99, 104, 97, 114, 116, 32, 98, 97, 114, 32, 109, 117, 108, 116, 105, 45, 102, 105, 108, 108, 34, 32, 119, 105, 100, 116, 104, 61, 34, 49, 48, 48, 37, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 55, 48, 34, 32, 112, 114, 101, 115, 101, 114, 118, 101, 65, 115, 112, 101, 99, 116, 82, 97, 116, 105, 111, 61, 34, 120, 77, 105, 110, 89, 77, 105, 110, 32, 109, 101, 101, 116, 34, 32, 118, 105, 101, 119, 66, 111, 120, 61, 34, 49, 48, 48, 37, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 103, 32, 99, 108, 97, 115, 115, 61, 34, 99, 104, 97, 114, 116, 45, 103, 114, 111, 117, 112, 34, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 61, 34, 116, 114, 97, 110, 115, 108, 97, 116, 101, 40, 48, 44, 32, 48, 41, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 103, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 45, 103, 114, 111, 117, 112, 34, 32, 119, 105, 100, 116, 104, 61, 34, 49, 53, 52, 54, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 99, 114, 105, 116, 105, 99, 97, 108, 34, 32, 102, 105, 108, 108, 61, 34, 35, 98, 56, 48, 99, 48, 57, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 48, 37, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 99, 114, 105, 116, 105, 99, 97, 108, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 104, 105, 103, 104, 34, 32, 102, 105, 108, 108, 61, 34, 35, 102, 102, 49, 50, 48, 48, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 88, 32, 34, 104, 105, 103, 104, 34, 32, 46, 125, 125, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 104, 105, 103, 104, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 109, 101, 100, 105, 117, 109, 34, 32, 102, 105, 108, 108, 61, 34, 35, 102, 102, 97, 52, 48, 48, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 88, 32, 34, 109, 101, 100, 105, 117, 109, 34, 32, 46, 125, 125, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 109, 101, 100, 105, 117, 109, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 108, 111, 119, 34, 32, 102, 105, 108, 108, 61, 34, 35, 102, 102, 100, 49, 53, 49, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 88, 32, 34, 108, 111, 119, 34, 32, 46, 125, 125, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 108, 111, 119, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 114, 101, 99, 116, 32, 99, 108, 97, 115, 115, 61, 34, 98, 97, 114, 32, 99, 108, 105, 99, 107, 97, 98, 108, 101, 32, 108, 105, 103, 104, 116, 34, 32, 105, 100, 61, 34, 105, 110, 102, 111, 34, 32, 102, 105, 108, 108, 61, 34, 35, 50, 101, 98, 57, 102, 48, 34, 32, 121, 61, 34, 48, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 88, 32, 34, 105, 110, 102, 111, 34, 32, 46, 125, 125, 34, 32, 104, 101, 105, 103, 104, 116, 61, 34, 52, 48, 34, 32, 119, 105, 100, 116, 104, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 87, 105, 100, 116, 104, 32, 34, 105, 110, 102, 111, 34, 32, 46, 125, 125, 34, 62, 60, 47, 114, 101, 99, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 99, 114, 105, 116, 105, 99, 97, 108, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 99, 114, 105, 116, 105, 99, 97, 108, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 67, 114, 105, 116, 105, 99, 97, 108, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 104, 105, 103, 104, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 104, 105, 103, 104, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 72, 105, 103, 104, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 109, 101, 100, 105, 117, 109, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 109, 101, 100, 105, 117, 109, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 77, 101, 100, 105, 117, 109, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 108, 111, 119, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 108, 111, 119, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 76, 111, 119, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 101, 120, 116, 32, 102, 105, 108, 108, 61, 34, 105, 110, 104, 101, 114, 105, 116, 34, 32, 99, 108, 97, 115, 115, 61, 34, 116, 101, 120, 116, 34, 32, 105, 100, 61, 34, 105, 110, 102, 111, 45, 116, 101, 120, 116, 34, 32, 120, 61, 34, 123, 123, 99, 97, 108, 99, 66, 97, 114, 67, 104, 97, 114, 116, 84, 101, 120, 116, 88, 32, 34, 105, 110, 102, 111, 34, 32, 46, 125, 125, 34, 32, 100, 121, 61, 34, 50, 53, 112, 120, 34, 32, 115, 116, 121, 108, 101, 61, 34, 112, 111, 105, 110, 116, 101, 114, 45, 101, 118, 101, 110, 116, 115, 58, 32, 110, 111, 110, 101, 59, 34, 62, 123, 123, 46, 73, 110, 102, 111, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 125, 125, 60, 47, 116, 101, 120, 116, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 103, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 103, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 118, 103, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 97, 108, 116, 32, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 118, 105, 101, 119, 45, 100, 101, 116, 97, 105, 108, 115, 34, 32, 105, 100, 61, 34, 86, 117, 108, 110, 95, 69, 118, 97, 108, 73, 109, 97, 103, 101, 68, 101, 116, 97, 105, 108, 115, 66, 121, 69, 118, 97, 108, 71, 117, 105, 100, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 123, 123, 46, 84, 97, 98, 108, 101, 72, 101, 105, 103, 104, 116, 125, 125, 112, 120, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 98, 108, 101, 45, 98, 111, 100, 121, 45, 102, 111, 111, 116, 101, 114, 45, 111, 117, 116, 115, 105, 100, 101, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 110, 111, 115, 99, 114, 111, 108, 108, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 48, 48, 37, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 101, 97, 100, 101, 114, 32, 99, 108, 97, 115, 115, 61, 34, 100, 97, 115, 104, 98, 111, 97, 114, 100, 45, 104, 101, 97, 100, 101, 114, 32, 118, 117, 108, 110, 45, 104, 101, 97, 100, 101, 114, 32, 112, 97, 100, 100, 105, 110, 103, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 104, 49, 62, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 32, 60, 47, 104, 49, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 104, 101, 97, 100, 101, 114, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 32, 118, 117, 108, 110, 45, 116, 97, 98, 108, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 45, 98, 111, 100, 121, 34, 32, 115, 116, 121, 108, 101, 61, 34, 34, 62, 10, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 34, 32, 97, 114, 105, 97, 45, 114, 111, 119, 99, 111, 117, 110, 116, 61, 34, 56, 56, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 123, 123, 46, 84, 97, 98, 108, 101, 72, 101, 105, 103, 104, 116, 125, 125, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 115, 67, 111, 110, 116, 97, 105, 110, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 123, 123, 46, 84, 97, 98, 108, 101, 72, 101, 105, 103, 104, 116, 125, 125, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 104, 105, 100, 100, 101, 110, 69, 108, 101, 109, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 95, 109, 97, 105, 110, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 52, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 76, 105, 110, 101, 76, 97, 121, 111, 117, 116, 95, 109, 111, 117, 115, 101, 65, 114, 101, 97, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 52, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 49, 59, 32, 100, 105, 115, 112, 108, 97, 121, 58, 32, 98, 108, 111, 99, 107, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 109, 97, 105, 110, 32, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 104, 101, 97, 100, 101, 114, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 104, 101, 97, 100, 101, 114, 34, 32, 114, 111, 108, 101, 61, 34, 114, 111, 119, 34, 32, 97, 114, 105, 97, 45, 114, 111, 119, 105, 110, 100, 101, 120, 61, 34, 49, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 98, 111, 100, 121, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 50, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 48, 57, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 48, 57, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 48, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 52, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 67, 86, 69, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 67, 86, 69, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 50, 52, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 32, 116, 111, 111, 108, 116, 105, 112, 45, 99, 111, 108, 117, 109, 110, 45, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 83, 69, 86, 69, 82, 73, 84, 89, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 111, 118, 101, 114, 108, 97, 121, 84, 114, 105, 103, 103, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 104, 105, 100, 101, 32, 99, 104, 105, 108, 100, 114, 101, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 97, 115, 105, 100, 101, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 32, 114, 105, 103, 104, 116, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 97, 117, 116, 111, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 97, 117, 116, 111, 59, 32, 112, 97, 100, 100, 105, 110, 103, 58, 32, 49, 48, 112, 120, 32, 53, 112, 120, 59, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 32, 49, 52, 112, 120, 59, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 32, 112, 114, 101, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 32, 45, 49, 52, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 104, 101, 97, 100, 101, 114, 32, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 98, 111, 100, 121, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 45, 110, 97, 109, 101, 34, 62, 83, 101, 118, 101, 114, 105, 116, 121, 32, 108, 101, 118, 101, 108, 32, 97, 116, 116, 114, 105, 98, 117, 116, 101, 100, 32, 116, 111, 32, 68, 105, 115, 116, 114, 111, 32, 115, 111, 117, 114, 99, 101, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 104, 101, 114, 101, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 32, 97, 110, 100, 32, 105, 102, 32, 110, 111, 116, 32, 100, 101, 102, 97, 117, 108, 116, 115, 32, 116, 111, 32, 78, 86, 68, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 97, 115, 105, 100, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 53, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 52, 52, 54, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 32, 116, 111, 111, 108, 116, 105, 112, 45, 99, 111, 108, 117, 109, 110, 45, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 53, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 83, 67, 79, 82, 69, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 111, 118, 101, 114, 108, 97, 121, 84, 114, 105, 103, 103, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 101, 99, 116, 105, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 104, 105, 100, 101, 32, 99, 104, 105, 108, 100, 114, 101, 110, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 97, 115, 105, 100, 101, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 32, 114, 105, 103, 104, 116, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 97, 117, 116, 111, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 97, 117, 116, 111, 59, 32, 112, 97, 100, 100, 105, 110, 103, 58, 32, 49, 48, 112, 120, 32, 53, 112, 120, 59, 32, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 32, 49, 52, 112, 120, 59, 32, 119, 104, 105, 116, 101, 45, 115, 112, 97, 99, 101, 58, 32, 112, 114, 101, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 112, 111, 105, 110, 116, 101, 114, 32, 34, 32, 115, 116, 121, 108, 101, 61, 34, 109, 97, 114, 103, 105, 110, 45, 116, 111, 112, 58, 32, 45, 49, 52, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 104, 101, 97, 100, 101, 114, 32, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 97, 105, 110, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 98, 111, 100, 121, 32, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 116, 111, 111, 108, 116, 105, 112, 45, 105, 116, 101, 109, 45, 110, 97, 109, 101, 34, 62, 68, 101, 102, 97, 117, 108, 116, 115, 32, 116, 111, 32, 67, 86, 83, 83, 118, 51, 32, 115, 99, 111, 114, 101, 32, 97, 110, 100, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 67, 86, 83, 83, 118, 50, 32, 105, 102, 32, 118, 51, 32, 115, 99, 111, 114, 101, 115, 32, 97, 114, 101, 32, 110, 111, 116, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 97, 115, 105, 100, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 54, 57, 54, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 80, 65, 67, 75, 65, 71, 69, 32, 78, 65, 77, 69, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 80, 65, 67, 75, 65, 71, 69, 32, 78, 65, 77, 69, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 56, 57, 57, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 67, 85, 82, 82, 69, 78, 84, 32, 86, 69, 82, 83, 73, 79, 78, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 67, 85, 82, 82, 69, 78, 84, 32, 86, 69, 82, 83, 73, 79, 78, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 49, 48, 50, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 70, 73, 88, 32, 86, 69, 82, 83, 73, 79, 78, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 70, 73, 88, 32, 86, 69, 82, 83, 73, 79, 78, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 99, 111, 108, 117, 109, 110, 104, 101, 97, 100, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 52, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 51, 48, 53, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 111, 108, 117, 109, 110, 82, 101, 115, 105, 122, 101, 114, 75, 110, 111, 98, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 116, 105, 116, 108, 101, 61, 34, 73, 78, 84, 82, 79, 68, 85, 67, 69, 68, 32, 73, 78, 32, 76, 65, 89, 69, 82, 34, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 32, 116, 97, 98, 108, 101, 45, 104, 101, 97, 100, 45, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 52, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 73, 78, 84, 82, 79, 68, 85, 67, 69, 68, 32, 73, 78, 32, 76, 65, 89, 69, 82, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 53, 48, 57, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 50, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 115, 99, 114, 111, 108, 108, 98, 97, 114, 83, 112, 97, 99, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 53, 48, 55, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 114, 97, 110, 103, 101, 32, 36, 118, 117, 108, 110, 32, 58, 61, 32, 46, 86, 117, 108, 110, 101, 114, 97, 98, 105, 108, 105, 116, 105, 101, 115, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 114, 111, 119, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 48, 59, 32, 100, 105, 115, 112, 108, 97, 121, 58, 32, 98, 108, 111, 99, 107, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 123, 123, 32, 36, 118, 117, 108, 110, 46, 82, 111, 119, 72, 101, 105, 103, 104, 116, 32, 125, 125, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 104, 105, 103, 104, 108, 105, 103, 104, 116, 101, 100, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 95, 111, 100, 100, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 98, 111, 100, 121, 82, 111, 119, 34, 32, 114, 111, 108, 101, 61, 34, 114, 111, 119, 34, 32, 97, 114, 105, 97, 45, 114, 111, 119, 105, 110, 100, 101, 120, 61, 34, 51, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 50, 52, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 82, 111, 119, 76, 97, 121, 111, 117, 116, 95, 98, 111, 100, 121, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 50, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 48, 57, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 49, 53, 48, 57, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 48, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 123, 123, 32, 36, 118, 117, 108, 110, 46, 67, 86, 69, 32, 125, 125, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 67, 86, 69, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 50, 52, 51, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 100, 101, 102, 97, 117, 108, 116, 34, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 105, 99, 111, 110, 45, 99, 105, 114, 99, 108, 101, 32, 99, 97, 112, 105, 116, 97, 108, 105, 122, 101, 32, 97, 99, 116, 105, 118, 101, 32, 97, 108, 101, 114, 116, 45, 123, 123, 32, 36, 118, 117, 108, 110, 46, 83, 101, 118, 101, 114, 105, 116, 121, 72, 84, 77, 76, 67, 108, 97, 115, 115, 32, 125, 125, 45, 105, 99, 111, 110, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 83, 101, 118, 101, 114, 105, 116, 121, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 53, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 52, 52, 54, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 105, 102, 32, 36, 118, 117, 108, 110, 46, 85, 115, 101, 86, 51, 83, 99, 111, 114, 101, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 118, 51, 45, 98, 111, 114, 100, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 112, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 49, 32, 118, 51, 45, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 34, 62, 67, 86, 83, 83, 32, 51, 46, 48, 60, 47, 112, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 112, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 50, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 86, 51, 83, 99, 111, 114, 101, 32, 125, 125, 32, 111, 102, 32, 49, 48, 60, 47, 112, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 105, 102, 32, 36, 118, 117, 108, 110, 46, 85, 115, 101, 86, 50, 83, 99, 111, 114, 101, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 32, 118, 50, 45, 98, 111, 114, 100, 101, 114, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 112, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 49, 32, 118, 50, 45, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 34, 62, 67, 86, 83, 83, 32, 50, 46, 48, 60, 47, 112, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 112, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 50, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 86, 50, 83, 99, 111, 114, 101, 32, 125, 125, 32, 111, 102, 32, 49, 48, 60, 47, 112, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 105, 102, 32, 36, 118, 117, 108, 110, 46, 85, 115, 101, 78, 111, 83, 99, 111, 114, 101, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 118, 117, 108, 110, 45, 114, 101, 112, 111, 114, 116, 45, 115, 99, 111, 114, 101, 45, 99, 111, 110, 116, 97, 105, 110, 101, 114, 34, 62, 78, 47, 65, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 54, 57, 54, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 78, 97, 109, 101, 32, 125, 125, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 78, 97, 109, 101, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 56, 57, 57, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 86, 101, 114, 115, 105, 111, 110, 32, 125, 125, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 86, 101, 114, 115, 105, 111, 110, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 51, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 49, 48, 50, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 70, 105, 120, 101, 100, 32, 125, 125, 34, 62, 123, 123, 32, 36, 118, 117, 108, 110, 46, 80, 107, 103, 70, 105, 120, 101, 100, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 109, 97, 105, 110, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 109, 97, 105, 110, 34, 32, 114, 111, 108, 101, 61, 34, 103, 114, 105, 100, 99, 101, 108, 108, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 50, 48, 52, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 51, 48, 53, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 49, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 49, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 50, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 50, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 76, 97, 121, 111, 117, 116, 95, 119, 114, 97, 112, 51, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 119, 114, 97, 112, 51, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 95, 99, 101, 108, 108, 67, 111, 110, 116, 101, 110, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 117, 108, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 115, 32, 32, 104, 111, 118, 101, 114, 32, 108, 101, 102, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 108, 105, 32, 99, 108, 97, 115, 115, 61, 34, 97, 99, 116, 105, 111, 110, 32, 32, 100, 101, 102, 97, 117, 108, 116, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 98, 117, 116, 116, 111, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 117, 116, 116, 111, 110, 45, 112, 114, 105, 109, 97, 114, 121, 32, 105, 99, 111, 110, 45, 111, 110, 108, 121, 32, 117, 110, 100, 101, 102, 105, 110, 101, 100, 32, 105, 99, 111, 110, 45, 99, 111, 112, 121, 45, 116, 111, 45, 99, 108, 105, 112, 98, 111, 97, 114, 100, 32, 110, 117, 108, 108, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 111, 112, 121, 32, 116, 111, 32, 99, 108, 105, 112, 98, 111, 97, 114, 100, 34, 32, 116, 121, 112, 101, 61, 34, 98, 117, 116, 116, 111, 110, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 99, 111, 112, 121, 84, 111, 67, 108, 105, 112, 98, 111, 97, 114, 100, 40, 39, 123, 123, 32, 36, 118, 117, 108, 110, 46, 76, 97, 121, 101, 114, 32, 125, 125, 39, 41, 34, 62, 60, 47, 98, 117, 116, 116, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 108, 105, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 117, 108, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 112, 97, 110, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 101, 108, 108, 105, 112, 115, 105, 115, 34, 32, 116, 105, 116, 108, 101, 61, 34, 32, 123, 123, 32, 108, 97, 121, 101, 114, 80, 114, 105, 110, 116, 32, 36, 118, 117, 108, 110, 46, 76, 97, 121, 101, 114, 32, 125, 125, 34, 62, 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 45, 98, 111, 108, 100, 45, 116, 101, 120, 116, 34, 62, 123, 123, 32, 108, 97, 121, 101, 114, 73, 110, 115, 116, 114, 117, 99, 116, 105, 111, 110, 32, 36, 118, 117, 108, 110, 46, 76, 97, 121, 101, 114, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 32, 32, 123, 123, 32, 108, 97, 121, 101, 114, 80, 114, 105, 110, 116, 32, 36, 118, 117, 108, 110, 46, 76, 97, 121, 101, 114, 32, 125, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 34, 32, 115, 116, 121, 108, 101, 61, 34, 108, 101, 102, 116, 58, 32, 48, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 87, 114, 97, 112, 112, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 53, 48, 57, 112, 120, 59, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 67, 101, 108, 108, 71, 114, 111, 117, 112, 76, 97, 121, 111, 117, 116, 95, 99, 101, 108, 108, 71, 114, 111, 117, 112, 34, 32, 115, 116, 121, 108, 101, 61, 34, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 97, 98, 115, 111, 108, 117, 116, 101, 59, 32, 119, 105, 100, 116, 104, 58, 32, 48, 112, 120, 59, 32, 122, 45, 105, 110, 100, 101, 120, 58, 32, 50, 59, 32, 116, 114, 97, 110, 115, 102, 111, 114, 109, 58, 32, 116, 114, 97, 110, 115, 108, 97, 116, 101, 51, 100, 40, 48, 112, 120, 44, 32, 48, 112, 120, 44, 32, 48, 112, 120, 41, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 115, 99, 114, 111, 108, 108, 98, 97, 114, 83, 112, 97, 99, 101, 114, 34, 32, 115, 116, 121, 108, 101, 61, 34, 119, 105, 100, 116, 104, 58, 32, 49, 53, 112, 120, 59, 32, 104, 101, 105, 103, 104, 116, 58, 32, 52, 48, 112, 120, 59, 32, 108, 101, 102, 116, 58, 32, 49, 53, 48, 55, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 123, 45, 32, 101, 110, 100, 32, 125, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 100, 105, 118, 32, 99, 108, 97, 115, 115, 61, 34, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 76, 97, 121, 111, 117, 116, 95, 98, 111, 116, 116, 111, 109, 83, 104, 97, 100, 111, 119, 32, 112, 117, 98, 108, 105, 99, 95, 102, 105, 120, 101, 100, 68, 97, 116, 97, 84, 97, 98, 108, 101, 95, 98, 111, 116, 116, 111, 109, 83, 104, 97, 100, 111, 119, 34, 32, 115, 116, 121, 108, 101, 61, 34, 116, 111, 112, 58, 32, 52, 48, 50, 112, 120, 59, 34, 62, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 101, 99, 116, 105, 111, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 109, 97, 105, 110, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 100, 105, 118, 62, 10, 32, 32, 32, 32, 60, 47, 98, 111, 100, 121, 62, 10, 60, 47, 104, 116, 109, 108, 62, 10}) -} diff --git a/vendor/github.com/lacework/go-sdk/internal/databox/box.go b/vendor/github.com/lacework/go-sdk/internal/databox/box.go deleted file mode 100644 index 66ac4f71a..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/databox/box.go +++ /dev/null @@ -1,91 +0,0 @@ -//go:generate go run generator/main.go -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package databox - -import ( - "fmt" - "strings" -) - -// Embed a global data box -var box = &data{box: make(map[string][]byte)} - -// Add a file to the global box -func Add(file string, content []byte) { - box.Add(file, content) -} - -// Get a file from the global box -func Get(file string) ([]byte, bool) { - return box.Get(file) -} - -// List all files inside the global box -func ListAll() []string { - return box.List("/") -} - -// List of files from a directory inside the global box -// -// Example: -// ```go -// databox.ListFilesFromDir("/scaffoldings/golang") -// ``` -func ListFilesFromDir(prefix string) []string { - return box.List(prefix) -} - -// Data box definition -type data struct { - box map[string][]byte -} - -// Add a file to the box -func (d *data) Add(file string, content []byte) { - d.box[file] = content -} - -// Get a file from the box -func (d *data) Get(file string) ([]byte, bool) { - if !strings.HasPrefix(file, "/") { - file = fmt.Sprintf("/%s", file) - } - - f, ok := d.box[file] - return f, ok -} - -// List of files inside the box -func (d *data) List(prefix string) []string { - if prefix == "" { - prefix = "/" - } else if !strings.HasPrefix(prefix, "/") { - prefix = fmt.Sprintf("/%s", prefix) - } - - tree := []string{} - for f := range d.box { - if strings.HasPrefix(f, prefix) { - tree = append(tree, f) - } - } - - return tree -} diff --git a/vendor/github.com/lacework/go-sdk/internal/failon/count_operation.go b/vendor/github.com/lacework/go-sdk/internal/failon/count_operation.go deleted file mode 100644 index ebd84ff64..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/failon/count_operation.go +++ /dev/null @@ -1,49 +0,0 @@ -package failon - -import ( - "regexp" - "strconv" - "strings" - - "github.com/pkg/errors" -) - -const operationRE = `^(>|>=|<|<=|={1,2}|!=)\s*(\d+)$` - -type CountOperation struct { - operator string - num int -} - -func (co *CountOperation) Parse(s string) error { - re := regexp.MustCompile(operationRE) - - s = strings.TrimSpace(s) - - var opParts []string - if opParts = re.FindStringSubmatch(s); s == "" || opParts == nil { - return errors.Errorf("count operation (%s) is invalid", s) - } - co.num, _ = strconv.Atoi(opParts[2]) - co.operator = opParts[1] - return nil -} - -func (co CountOperation) IsFail(count int) (bool, error) { - switch co.operator { - case ">": - return count > co.num, nil - case ">=": - return count >= co.num, nil - case "<": - return count < co.num, nil - case "<=": - return count <= co.num, nil - case "=", "==": - return count == co.num, nil - case "!=": - return count != co.num, nil - default: - return true, errors.Errorf("count operation (%s) is invalid", co.operator) - } -} diff --git a/vendor/github.com/lacework/go-sdk/internal/file/file.go b/vendor/github.com/lacework/go-sdk/internal/file/file.go deleted file mode 100644 index a424dbdd2..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/file/file.go +++ /dev/null @@ -1,55 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package file - -import ( - "os" - "path/filepath" -) - -// FileExists checks if a file exists and is not a directory -func FileExists(filename string) bool { - f, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - if f == nil { - return false - } - return !f.IsDir() -} - -// Copy a file -func Copy(src, dst string) error { - srcAbs, err := filepath.Abs(src) - if err != nil { - return err - } - dstAbs, err := filepath.Abs(dst) - if err != nil { - return err - } - - input, err := os.ReadFile(srcAbs) - if err != nil { - return err - } - - return os.WriteFile(dstAbs, input, 0755) -} diff --git a/vendor/github.com/lacework/go-sdk/internal/format/secret.go b/vendor/github.com/lacework/go-sdk/internal/format/secret.go deleted file mode 100644 index 3d383f100..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/format/secret.go +++ /dev/null @@ -1,32 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package format - -func Secret(nToShow int, secret string) string { - secretSize := len(secret) - if secretSize <= nToShow { - return secret - } - - var chars = []byte(secret) - for i := 0; i < (secretSize - nToShow); i++ { - chars[i] = '*' - } - return string(chars) -} diff --git a/vendor/github.com/lacework/go-sdk/internal/format/strings.go b/vendor/github.com/lacework/go-sdk/internal/format/strings.go deleted file mode 100644 index 6424bdcc0..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/format/strings.go +++ /dev/null @@ -1,46 +0,0 @@ -package format - -import ( - "bytes" - "fmt" - "unicode" -) - -// SpaceUpperCase add a space each occurrence of an upper case letter. -// -// res := format.SpaceUpperCase("myExampleString") -// -> my Example String -func SpaceUpperCase(s string) string { - buf := &bytes.Buffer{} - prev := false - spaces := []*unicode.RangeTable{unicode.White_Space, unicode.Space} - for i, rune := range s { - if unicode.IsOneOf(spaces, rune) { - prev = true - } - - // if previous value is a space skip - if prev { - prev = false - continue - } - if unicode.IsUpper(rune) && i > 0 { - buf.WriteRune(' ') - } - buf.WriteRune(rune) - } - return buf.String() -} - -// Truncate reduce a string to specified char limit. Append ellipsis. -// -// res := format.Truncate("myExampleString", 10) -// -// -> myExampleS... -func Truncate(s string, max int) string { - if max > len(s) { - return s - } - - return fmt.Sprintf("%s...", s[:max]) -} diff --git a/vendor/github.com/lacework/go-sdk/internal/intgguid/rand.go b/vendor/github.com/lacework/go-sdk/internal/intgguid/rand.go deleted file mode 100644 index a1444f338..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/intgguid/rand.go +++ /dev/null @@ -1,53 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package intgguid - -// This package generates Globally Unique Identifiers (GUID) -// matching Lacework INTD_GUID that is used for integrations - -import ( - "fmt" - "math/rand" - "time" -) - -var ( - charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - randomSeed *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) -) - -func New() string { - return NewPrefix("MOCK") -} - -func NewPrefix(prefix string) string { - return fmt.Sprintf("%s_%s", prefix, randomString(47)) -} - -func stringFromCharset(length int, charset string) string { - bytes := make([]byte, length) - for i := range bytes { - bytes[i] = charset[randomSeed.Intn(len(charset))] - } - return string(bytes) -} - -func randomString(length int) string { - return stringFromCharset(length, charset) -} diff --git a/vendor/github.com/lacework/go-sdk/internal/lacework/server.go b/vendor/github.com/lacework/go-sdk/internal/lacework/server.go deleted file mode 100644 index 58583b5a1..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/lacework/server.go +++ /dev/null @@ -1,97 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lacework - -import ( - "fmt" - "net/http" - "net/http/httptest" - "time" -) - -// Mock is a quick HTTP server that can be used to mock a Lacework API -// server, you can use it to avoid using a real server in our unit tests -// -// A simple usage: -// -// func TestSomethingNew(t *testing.T) { -// fakeServer := lacework.NewServer() -// fakeServer.MockToken("TOKEN") -// defer fakeServer.Close() -// -// // Make sure to pass the fake API server URL -// c, err := api.NewClient("test", api.WithURL(fakeServer.URL())) -// if assert.Nil(t, err) { -// // The client c is ready to be used -// } -// } -type Mock struct { - Mux *http.ServeMux - Server *httptest.Server - ApiVersion string -} - -// MockServer returns a new mocked http server with a mutex -func MockServer() *Mock { - mux := http.NewServeMux() - return &Mock{ - Mux: mux, - Server: httptest.NewServer(mux), - ApiVersion: "v2", - } -} - -func MockUnstartedServer() *Mock { - mux := http.NewServeMux() - return &Mock{ - Mux: mux, - Server: httptest.NewUnstartedServer(mux), - ApiVersion: "v2", - } -} - -// MockAPI will mock the api path inside the server mutex with the provided handler function -func (m *Mock) MockAPI(p string, handler func(http.ResponseWriter, *http.Request)) { - m.Mux.HandleFunc(fmt.Sprintf("/api/%s/%s", m.ApiVersion, p), handler) -} - -func (s *Mock) MockToken(token string) { - s.MockAPI("access/tokens", func(w http.ResponseWriter, r *http.Request) { - expiration := time.Now().AddDate(0, 0, 1) - fmt.Fprintf(w, ` - { - "expiresAt": "`+expiration.Format(time.RFC3339)+`", - "token": "`+token+`" - } - `) - }) -} - -func (m Mock) URL() string { - if m.Server != nil { - return m.Server.URL - } - return "" -} - -func (m Mock) Close() { - if m.Server != nil { - m.Server.Close() - } -} diff --git a/vendor/github.com/lacework/go-sdk/internal/pointer/bool.go b/vendor/github.com/lacework/go-sdk/internal/pointer/bool.go deleted file mode 100644 index 1706c285d..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/pointer/bool.go +++ /dev/null @@ -1,10 +0,0 @@ -package pointer - -// CompareBoolPtr compares a bool pointer to a bool primitive -// Returns false if bool pointer is nil -func CompareBoolPtr(ptr *bool, b bool) bool { - if ptr == nil { - return false - } - return *ptr == b -} diff --git a/vendor/github.com/lacework/go-sdk/internal/unique/strings.go b/vendor/github.com/lacework/go-sdk/internal/unique/strings.go deleted file mode 100644 index 69dcf9cb2..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/unique/strings.go +++ /dev/null @@ -1,19 +0,0 @@ -package unique - -func StringSlice(slice []string) []string { - _map := make(map[string]string) - - for _, f := range slice { - _map[f] = "" - } - - set := make([]string, len(_map)) - - i := 0 - for key := range _map { - set[i] = key - i++ - } - - return set -} diff --git a/vendor/github.com/lacework/go-sdk/internal/validate/email.go b/vendor/github.com/lacework/go-sdk/internal/validate/email.go deleted file mode 100644 index 7cf6e85ce..000000000 --- a/vendor/github.com/lacework/go-sdk/internal/validate/email.go +++ /dev/null @@ -1,22 +0,0 @@ -package validate - -import ( - "net/mail" - - "github.com/pkg/errors" -) - -// validate email address against RFC 5322 -func EmailAddress(val interface{}) error { - switch value := val.(type) { - case string: - // This validates the email format against RFC 5322 - _, err := mail.ParseAddress(value) - if err != nil { - return errors.Wrap(err, "supplied email address is not a valid email address format") - } - default: - return errors.New("value must be a string") - } - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/helpers/helper.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/helpers/helper.go deleted file mode 100644 index 5398941ec..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/helpers/helper.go +++ /dev/null @@ -1,79 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package helpers - -import ( - "fmt" - "os" - "strings" -) - -const ( - GCP_PROJECT_TYPE = "PROJECT" - GCP_ORGANIZATION_TYPE = "ORGANIZATION" -) - -func SkipEntry(Entry string, skipList, allowList map[string]bool) bool { - if skipList != nil { - if _, skip := skipList[Entry]; skip { - return true - } - } - - if allowList != nil { - if _, allow := allowList[Entry]; allow { - return false - } else { - // skip all other entries - return true - } - } - - return false -} - -func GetGcpFormatedLabel(in string) string { - lower := strings.ToLower(in) - out := strings.ReplaceAll(lower, ":", "-") - out = strings.ReplaceAll(out, ".", "-") - out = strings.ReplaceAll(out, "\"", "-") - out = strings.ReplaceAll(out, "{", "-") - out = strings.ReplaceAll(out, "}", "-") - return out -} - -func CombineErrors(old error, new error) error { - if new == nil { - return old - } - - if old == nil { - return new - } - - return fmt.Errorf("%s, %s", old.Error(), new.Error()) -} - -func IsProjectScanScope() bool { - return os.Getenv("GCP_SCAN_SCOPE") == GCP_PROJECT_TYPE -} - -func IsOrgScanScope() bool { - return os.Getenv("GCP_SCAN_SCOPE") == GCP_ORGANIZATION_TYPE -} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/folders/folders.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/folders/folders.go deleted file mode 100644 index c2e3b4d39..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/folders/folders.go +++ /dev/null @@ -1,107 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package resources - -import ( - "context" - "fmt" - - resourcemanager "cloud.google.com/go/resourcemanager/apiv3" - "github.com/lacework/go-sdk/lwcloud/gcp/helpers" - "google.golang.org/api/iterator" - "google.golang.org/api/option" - - resourcemanagerpb "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" -) - -type FolderInfo struct { - Name string - Parent string - DisplayName string - Ancestory string -} - -func EnumerateFolders(ctx context.Context, - clientOptions option.ClientOption, ParentId string, Ancestory string, - skipList, allowList map[string]bool) ([]FolderInfo, error) { - - var ( - client *resourcemanager.FoldersClient - err error - ) - - if clientOptions != nil { - client, err = resourcemanager.NewFoldersClient(ctx, clientOptions) - } else { - client, err = resourcemanager.NewFoldersClient(ctx) - } - - if err != nil { - return nil, fmt.Errorf("cannot enumerate folders in (%s) due to %s", Ancestory, err.Error()) - } - defer client.Close() - - req := &resourcemanagerpb.ListFoldersRequest{ - Parent: ParentId, - } - - folders := make([]FolderInfo, 0) - - for { - - it := client.ListFolders(ctx, req) - - for { - resp, err := it.Next() - if err == iterator.Done { - break - } - - if err != nil { - return nil, fmt.Errorf("cannot iterate folders in ancestory (%s) due to %s", Ancestory, err.Error()) - } - - if helpers.SkipEntry(resp.Name, skipList, allowList) { - continue - } - - fi := FolderInfo{ - Name: resp.Name, - DisplayName: resp.DisplayName, - Parent: resp.Parent, - Ancestory: Ancestory, - } - folders = append(folders, fi) - - // search for folders recursively; ignore errors - subFolders, _ := EnumerateFolders( - ctx, clientOptions, resp.Name, Ancestory+" -> "+resp.DisplayName+" ("+resp.Name+")", skipList, allowList, - ) - if len(subFolders) != 0 { - folders = append(folders, subFolders...) - } - } - - if req.GetPageToken() == "" { - break - } - } - - return folders, nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/instances/instances.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/instances/instances.go deleted file mode 100644 index 67dec6684..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/instances/instances.go +++ /dev/null @@ -1,238 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package resources - -import ( - "context" - "encoding/json" - "fmt" - "path/filepath" - "strings" - "time" - - helpers "github.com/lacework/go-sdk/lwcloud/gcp/helpers" - projects "github.com/lacework/go-sdk/lwcloud/gcp/resources/projects" - - models "github.com/lacework/go-sdk/lwcloud/gcp/resources/models" - - compute "cloud.google.com/go/compute/apiv1" - "cloud.google.com/go/compute/apiv1/computepb" - "google.golang.org/api/iterator" - "google.golang.org/api/option" -) - -func EnumerateInstancesInProject( - ctx context.Context, clientOption option.ClientOption, region string, ProjectId string, -) ([]models.InstanceDetails, error) { - - var ( - client *compute.InstancesClient - err error - ) - - if clientOption != nil { - client, err = compute.NewInstancesRESTClient(ctx, clientOption) - } else { - client, err = compute.NewInstancesRESTClient(ctx) - } - - if err != nil { - return nil, err - } - defer client.Close() - - var filter string - if region != "" { - filter = fmt.Sprintf("zone eq .*%s.*", region) - } - - req := &computepb.AggregatedListInstancesRequest{ - Project: ProjectId, - Filter: &filter, - } - - instances := make([]models.InstanceDetails, 0) - - for { - it := client.AggregatedList(ctx, req) - - for { - resp, err := it.Next() - if err == iterator.Done { - break - } - if err != nil { - return nil, err - } - - if resp.Value == nil || len(resp.Value.Instances) == 0 { - continue - } - - for _, instance := range resp.Value.Instances { - - launchTime, _ := time.Parse(time.RFC3339, instance.GetCreationTimestamp()) - instanceIdStr := fmt.Sprintf("%d", instance.GetId()) - - diskIds := make([]string, len(instance.GetDisks())) - for i, disk := range instance.GetDisks() { - diskIds[i] = disk.GetSource() - } - - zone := filepath.Base(instance.GetZone()) - zoneStart := strings.LastIndex(zone, "-") - region := zone[:zoneStart] - - tags := make(map[string]string) - - privateIp, externalIp, vpcId := getNetworkInfo(instance.GetNetworkInterfaces(), tags) - - md := getMetadata(instance.GetMetadata()) - - instanceInfo := models.InstanceDetails{ - InstanceID: instanceIdStr, - Type: instance.GetMachineType(), - State: instance.GetStatus(), - Name: instance.GetName(), - Zone: zone, - Region: region, - ImageID: instance.GetSourceMachineImage(), - AccountID: filepath.Base(ProjectId), - VpcID: vpcId, - PublicIP: externalIp, - PrivateIP: privateIp, - LaunchTime: launchTime, - Tags: tags, - Props: md, - } - instances = append(instances, instanceInfo) - } - } - - if req.GetPageToken() == "" { - break - } - } - - return instances, nil -} - -func EnumerateInstancesInOrg( - ctx context.Context, clientOption option.ClientOption, region string, - OrgId string, skipList map[string]bool, allowList map[string]bool, -) (map[string][]models.InstanceDetails, error) { - - projects, err := projects.EnumerateProjects(ctx, clientOption, OrgId, OrgId, skipList, allowList) - if err != nil { - return nil, err - } - - m := make(map[string][]models.InstanceDetails, 0) - - for _, project := range projects { - - if helpers.SkipEntry("projects/"+project.ProjectId, skipList, allowList) { - continue - } - - projectInstances, err := EnumerateInstancesInProject(ctx, clientOption, region, project.ProjectId) - if err != nil { - // TODO log error and continue - continue - } - - m[project.Name] = projectInstances - } - - return m, nil -} - -type NwIntfInfo struct { - Ipaddr string `json:"ipAddr,omitempty"` - Ipv6addr string `json:"ipv6Addr,omitempty"` - Kind string `json:"kind,omitempty"` - Name string `json:"name,omitempty"` - NicType string `json:"nicType,omitempty"` - Network string `json:"network,omitempty"` - SubNetwork string `json:"subNetwork,omitempty"` - AccessConfigs []*computepb.AccessConfig `json:"accessConfigs,omitempty"` -} - -func getNetworkInfo(nwIntfs []*computepb.NetworkInterface, tags map[string]string) (string, string, string) { - privateIp := "" - externalIp := "" - vpcId := "" - - for _, intf := range nwIntfs { - - nwInfo := NwIntfInfo{ - Ipaddr: intf.GetNetworkIP(), - Ipv6addr: intf.GetIpv6Address(), - Kind: intf.GetKind(), - Name: intf.GetName(), - NicType: intf.GetNicType(), - Network: intf.GetNetwork(), - SubNetwork: intf.GetSubnetwork(), - AccessConfigs: intf.GetAccessConfigs(), - } - - if nwInfo.Ipaddr != "" && privateIp == "" { - privateIp = nwInfo.Ipaddr - } - - if nwInfo.Ipv6addr != "" && privateIp == "" { - privateIp = nwInfo.Ipv6addr - } - - if nwInfo.Network != "" && vpcId == "" { - vpcId = nwInfo.Network - } - - accessConfigs := intf.GetAccessConfigs() - if len(accessConfigs) != 0 { - for _, accessConfig := range accessConfigs { - natIp := accessConfig.GetNatIP() - externalIpv6Length := accessConfig.GetExternalIpv6() - - if natIp != "" && externalIp == "" { - externalIp = natIp - } - - if externalIpv6Length != "" && externalIp == "" { - externalIp = externalIpv6Length - } - } - } - - nwIntfJson, err := json.Marshal(nwInfo) - if err == nil { - tags["InterfaceInfo:"+nwInfo.Name] = string(nwIntfJson) - } - } - - return privateIp, externalIp, vpcId -} - -func getMetadata(rawMd *computepb.Metadata) map[string]string { - mappedMd := make(map[string]string) - for _, item := range rawMd.GetItems() { - mappedMd[item.GetKey()] = item.GetValue() - } - return mappedMd -} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/models/models.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/models/models.go deleted file mode 100644 index 2207a5311..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/models/models.go +++ /dev/null @@ -1,70 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package resources - -import "time" - -type InstanceDetails struct { - InstanceID string `json:"INSTANCE_ID"` - Type string `json:"TYPE"` - State string `json:"STATE"` - Name string `json:"NAME"` - Region string `json:"REGION"` - Zone string `json:"ZONE"` - ImageID string `json:"IMAGE_ID"` - VpcID string `json:"VPC_ID"` - AccountID string `json:"ACCOUNT_ID"` - PublicIP string `json:"PUBLIC_IP"` - PrivateIP string `json:"PRIVATE_IP"` - LaunchTime time.Time `json:"LAUNCH_TIME"` - - // HardwareDetails contains the "expanded out" CPU and Memory information for the instance type. - HardwareDetails HardwareDetails `json:"HARDWARE_DETAILS"` - - Tags map[string]string `json:"TAGS"` - Props map[string]string `json:"PROPS"` - ConfigProps ConfigProps `json:"CONFIG_PROPS"` -} - -type HardwareDetails struct { - CpuVendorID string `json:"CPU_VENDOR_ID"` - CpuModelName string `json:"CPU_MODEL_NAME"` - CpuCores int `json:"CPU_CORES"` - MemoryTotalBytes int64 `json:"MEMORY_TOTAL_BYTES"` - MemoryECCType string `json:"MEMORY_ECC_TYPE"` -} - -type ConfigProps struct { - ScanFrequency int64 `json:"SCAN_FREQUENCY"` - ScanContainers bool `json:"SCAN_CONTAINERS"` - ScanHostVulnerabilities bool `json:"SCAN_HOST_VULNERABILITIES"` - - // Cross-account role used for org access and internal access. - CrossAccountCredentials ConfigCredentialsProps `json:"CROSS_ACCOUNT_CREDENTIALS"` - - // Org-specific properties - ManagementAccount string `json:"MANAGEMENT_ACCOUNT"` - MonitoredAccounts string `json:"MONITORED_ACCOUNTS"` - ScanningAccount string `json:"SCANNING_ACCOUNT"` -} - -type ConfigCredentialsProps struct { - RoleARN string `json:"ROLE_ARN"` - ExternalId string `json:"EXTERNAL_ID"` -} diff --git a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/projects/projects.go b/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/projects/projects.go deleted file mode 100644 index 8b8470dec..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcloud/gcp/resources/projects/projects.go +++ /dev/null @@ -1,132 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package resources - -import ( - "context" - "fmt" - - "github.com/lacework/go-sdk/lwcloud/gcp/helpers" - folders "github.com/lacework/go-sdk/lwcloud/gcp/resources/folders" - - resourcemanager "cloud.google.com/go/resourcemanager/apiv3" - resourcemanagerpb "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" - "google.golang.org/api/iterator" - "google.golang.org/api/option" -) - -type ProjectInfo struct { - Name string - Parent string - DisplayName string - ProjectId string -} - -func enumerateTopLevelProjects( - ctx context.Context, clientOption option.ClientOption, ParentId string, - Ancestory string, skipList, allowList map[string]bool, -) ([]ProjectInfo, error) { - - var ( - client *resourcemanager.ProjectsClient - err error - ) - - if clientOption != nil { - client, err = resourcemanager.NewProjectsClient(ctx, clientOption) - } else { - client, err = resourcemanager.NewProjectsClient(ctx) - } - if err != nil { - return nil, fmt.Errorf("cannot enumerate projects in (%s) due to %s", Ancestory, err.Error()) - } - defer client.Close() - - projects := make([]ProjectInfo, 0) - - req := &resourcemanagerpb.ListProjectsRequest{ - Parent: ParentId, - } - - for { - it := client.ListProjects(ctx, req) - - for { - resp, err := it.Next() - if err == iterator.Done { - break - } - if err != nil { - return nil, fmt.Errorf("cannot iterate projects in (%s) due to %s", Ancestory, err.Error()) - } - - if helpers.SkipEntry("projects/"+resp.ProjectId, skipList, allowList) { - continue - } - - pi := ProjectInfo{ - Name: resp.Name, - DisplayName: resp.DisplayName, - Parent: resp.Parent, - ProjectId: resp.ProjectId, - } - projects = append(projects, pi) - } - - if req.GetPageToken() == "" { - break - } - - } - return projects, nil -} - -func EnumerateProjects( - ctx context.Context, clientOptions option.ClientOption, ParentId string, - Ancestory string, skipList, allowList map[string]bool, -) ([]ProjectInfo, error) { - - // find top level projects under Parent first - projects, err := enumerateTopLevelProjects(ctx, clientOptions, ParentId, Ancestory, skipList, allowList) - if err != nil { - return projects, err - } - - // find all sub folders first - subFolders, err := folders.EnumerateFolders(ctx, clientOptions, ParentId, Ancestory, skipList, allowList) - if err != nil { - return projects, err - } - - // list all projects in the nested folders - var retError error - for _, folder := range subFolders { - nested_projects, err := enumerateTopLevelProjects(ctx, clientOptions, folder.Name, - folder.Ancestory+" -> "+folder.DisplayName+" ("+folder.Name+")", skipList, allowList) - if err != nil { - // combine errors - retError = helpers.CombineErrors(retError, err) - continue - } - - projects = append(projects, nested_projects...) - } - - return projects, retError -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/DESIGN.md b/vendor/github.com/lacework/go-sdk/lwcomponent/DESIGN.md deleted file mode 100644 index 3673e9c7e..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/DESIGN.md +++ /dev/null @@ -1,158 +0,0 @@ -# Lacework CDK (Cloud Development Kit) - -Proposed by: [Salim Afiune Maya](https://github.com/afiune) - -As the Lacework platform grows and introduces new products and services, our security ecosystem will -have to grow at the same speed (or even faster) to adopt such new products. This design is a proposal -for adopting a new model to deliver tools and libraries to Lacework users. - -## Motivation - -The Lacework CLI was designed with the goal of providing fast, accurate, and actionable insights into -the Lacework platform. The first release [v0.1.0](https://github.com/lacework/go-sdk/releases/tag/v0.1.0) -was on March 27, 2020, and since then, the Lacework CLI has been broadly adopted. A year later, Lacework -released a couple more binaries, and today, it is clear to us that we will adopt this model of releasing -separate binaries for more products and services in the future. - -This model of modular binaries is flexible and will allow us to release features faster to our users, -but it also lacks a cohesive ecosystem, an easy way for users to discover, install, configure and -manage all of these tools. - -This design aims to solve this problem by introducing the concept of **components** into the Lacework CLI, -with these new components, the Lacework CLI will evolve into the SDK realm and therefore, we are proposing -to name it the Lacework CDK (Cloud Development Kit) since it will now provide truly a combination of -tools and libraries to build a robust and flexible ecosystem for our users. - -## Scope - -The main goal of this design is to introduce the concept of components to build a flexible and robust ecosystem -around the Lacework platform. - -This design also aims to: - -* Unify the installation and configuration of tools provided by Lacework -* Help users discover new tools from new Lacework products -* Provide the same experience to manage tools in the Lacework ecosystem -* Allow users to create new tools to enhance security workflows -* Make it easy to manage and distribute libraries and content - -## High-level Diagram - -![Lacework CDK Diagram](imgs/lacework-cdk-high-level-diagram.jpeg) - -## What Are Components? - -A component can be a command-line tool, a new command that extends the Lacework CLI, or a library that -contains files used by another Lacework component. - -### Component Specifications - -Every component should follow the following specifications: - -| Name | Type | Description -| ------ | --------- | ----------------------------------------------------------------------- -| `name` | `string` | The name of the component -| `description` | `string` | A long description that describes the purpose of the component -| `type` | `enum(Type)` | The component type (read more about types here) -| `version` | `string` | The version of the component in semantic format (`MAJOR.MINOR.PATCH`) | -| `artifacts` | `array(Artifact)` | List of artifacts that the component supports -| `dependencies` | `array(Component)` | A list of components that the component depends on - -#### Component Artifact - -A component could run on multiple platforms. Every component has a list of artifacts. An `Artifact` is the actual -component for specific platforms. The specification of an artifact is: - -| Name | Type | Description -| ------ | --------- | ------------------------------------------------------------- -| `os` | `string` | The operating system (`darwin`, `linux`, `windows`) -| `arch` | `string` | The artifact architecture (`amd64`, `386`, `arm64`) -| `size` | `int64` | The artifact size in bytes -| `signature` | `string` | GPG signature of the artifact -| `url` | `string` | The URL from where to download the component artifact - -These specifications are designed to be extensible since they will change as we expand the usage and purpose of -these components. - -The specification of all components will be provided by a new service (`cdk-store`) which will have a semantic -version (`MAJOR.MINOR.PATCH`) that indicates the version of the specifications. If the specifications change, a -new version will be released and our users will get notified. - -### Components Internal Service (`cdk-store`) - -We should have a very lightweight service that will be the single source of truth of all available Lacework components, -this service should fulfill the following use cases: - -* Provide an API to fetch the current state of all Lacework components and their specifications -* Provide an API to define (create) new components, when new components are added to this service, our users will be notified automatically -* Provide an API to deprecate a component, read more about deprecations below -* Provide an API to trigger a sync of a single component, useful for orchestrating release pipelines -* Configure a batch process that runs every 10 minutes to verify that all components are in sync - -### Component Synchronization - -A component synchronization is a task that the internal components' service does to verify the latest version of one -or multiple components, when there is a new version of a component, this task updates the description, version, size, -signature, and dependencies of the component. - -Note that changing the type of the component is discouraged. - -### Component Signature - -As a security company, we need to ensure that any artifact we install on the users' workstation is coming from us, the -execution, installation, and upgrade process will have a requirement that every component should be signed with Lacework's -PGP key, if the downloaded component doesn't match the PGP signature, we should delete the downloaded binary and notify -the user. - -### Create A New Component - -To create a new component, we need to define the following things: - -* Define the component type (`BINARY`, `COMMAND`, or `LIBRARY`) -* For binary components, provide cross-platform binaries and signatures (support `windows`, `linux`, `darwin`) -* Automate the release process via CD pipelines -* Make the first release of the new component -* Add the component to our components internal service (this is when users will discover the new component) - -## Attribution to third-party tools - -A component can be a third-party tool that helps users enable specific workflows such as infrastructure-as-code. -During the installation and upgrade of these types of components, we need to give attribution by including the -copyright and permissions statement. - -One example of a third-party tool we use today is [Terraform](https://github.com/hashicorp/terraform), which allows -Laceworks' users to follow the GitOps methodology to describe their Lacework accounts as code. - -Lacework believes in a growing open-source community, we envision the adoption of other third-party tools that will -help our users improve their security postures. - -## Deprecations - -We aim to adopt this robust ecosystem of tools to release new products, features, bug fixes, and functionalities to -our users as fast as possible, with that speed, there will be times where we will release pieces of a component, or -even an entire component that might not be well received by our users, in those cases our deprecation policy will be: - -* Mark the flag, command, functionality, or component as deprecated -* Communicate to our users about the deprecation -* Wait for 90 days until removal -* Remove the deprecation and release with a major version bump - -## Telemetry and Observability - -We use [Honeycomb](https://www.honeycomb.io/) as our platform to understand how our customers are adopting tools -and integrations we build. Today, it allows us to detect early on when customers are experiencing issues, what -are the most used commands, what are the most common errors our users are experiencing, and more. - -When implementing this component-based model, we need to add end-to-end observability to trace requests that are -initiated at the command line and go all the way to our backend. This means that every component will need to -have the instrumentation to send telemetry to Honeycomb, as well as passing the correct tracing information to -the underlying API endpoint. - -Internally, our API server should accept this tracing information and propagate it to subsequent requests to internal -services, this will allow us to group all of these requests in a single transaction or trace. - -## Open Questions: - -* Will there be components that don't have cross-platform support? If yes, document examples and additional details about how to handle these cases -* ... - diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/api_info.go b/vendor/github.com/lacework/go-sdk/lwcomponent/api_info.go deleted file mode 100644 index 728b1d690..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/api_info.go +++ /dev/null @@ -1,38 +0,0 @@ -package lwcomponent - -import ( - "github.com/Masterminds/semver" -) - -type ApiInfo struct { - Id int32 `json:"id"` - Name string `json:"name"` - Version *semver.Version `json:"version"` - AllVersions []*semver.Version `json:"allVersions"` - Desc string `json:"desc"` - SizeKB int64 `json:"sizeKB"` - Deprecated bool `json:"deprecated"` - ComponentType Type `json:"componentType"` -} - -func NewAPIInfo( - id int32, - name string, - version *semver.Version, - allVersions []*semver.Version, - desc string, - size int64, - deprecated bool, - componentType Type, -) *ApiInfo { - return &ApiInfo{ - Id: id, - Name: name, - Version: version, - AllVersions: allVersions, - Desc: desc, - SizeKB: size, - Deprecated: deprecated, - ComponentType: componentType, - } -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/catalog.go b/vendor/github.com/lacework/go-sdk/lwcomponent/catalog.go deleted file mode 100644 index 78d852b4f..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/catalog.go +++ /dev/null @@ -1,468 +0,0 @@ -package lwcomponent - -import ( - "encoding/xml" - "fmt" - "os" - "path/filepath" - "runtime" - - "aead.dev/minisign" - "github.com/Masterminds/semver" - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/cache" - "github.com/pkg/errors" -) - -const ( - componentCacheDir string = "components" - cdkCacheName string = "cdk_cache" - featureFlag string = "PUBLIC.cdk.v4" - operatingSystem string = runtime.GOOS - architecture string = runtime.GOARCH -) - -func CatalogV1Enabled(client *api.Client) bool { - if os.Getenv("LW_CLI_INTEGRATION_MODE") != "" { - return true - } - response, err := client.V2.FeatureFlags.GetFeatureFlagsMatchingPrefix(featureFlag) - if err != nil { - return false - } - - return len(response.Data.Flags) >= 1 -} - -// Returns the local directory that Components will be stored in. -func CatalogCacheDir() (string, error) { - cacheDir, err := cache.CacheDir() - if err != nil { - return "", errors.Wrap(err, "unable to locate components directory") - } - - path := filepath.Join(cacheDir, componentCacheDir) - - if _, err = os.Stat(path); err != nil { - if err = os.MkdirAll(path, os.ModePerm); err != nil { - return "", err - } - } - - return path, nil -} - -type Catalog struct { - client *api.Client - - Components map[string]CDKComponent - stageConstructor StageConstructor -} - -func (c *Catalog) ComponentCount() int { - return len(c.Components) -} - -// Return a CDKComponent that is present on the host. -func (c *Catalog) GetComponent(name string) (*CDKComponent, error) { - component, exists := c.Components[name] - if !exists { - return nil, errors.New(fmt.Sprintf("component %s not found", name)) - } - - return &component, nil -} - -func (c *Catalog) ListComponentVersions(component *CDKComponent) ([]*semver.Version, error) { - if component.ApiInfo == nil { - return nil, errors.Errorf("component '%s' api info not available", component.Name) - } - - if component.ApiInfo.AllVersions != nil { - return component.ApiInfo.AllVersions, nil - } - - return listComponentVersions(c.client, component.ApiInfo.Id) -} - -func (c *Catalog) PrintComponents() [][]string { - result := [][]string{} - - for _, component := range c.Components { - result = append(result, component.PrintSummary()) - } - - return result -} - -func (c *Catalog) Stage( - component *CDKComponent, - version string, - progressClosure func(filepath string, sizeB int64)) (stageClose func(), err error) { - var ( - semv *semver.Version - ) - - stageClose = func() {} - - if version == "" { - semv = component.ApiInfo.Version - } else { - semv, err = semver.NewVersion(version) - if err != nil { - return - } - } - - if component.HostInfo != nil { - var installedVersion *semver.Version - - installedVersion, err = component.HostInfo.Version() - if err != nil { - return - } - - if installedVersion.Equal(semv) { - err = errors.Errorf("version '%s' already installed", semv.String()) - return - } - } - - response, err := c.client.V2.Components.FetchComponentArtifact( - component.ApiInfo.Id, - operatingSystem, - architecture, - semv.String()) - if err != nil { - return - } - - if len(response.Data) == 0 { - err = errors.New("Invalid API response") - return - } - - data := response.Data[0] - - component.InstallMessage = data.InstallMessage - component.UpdateMessage = data.UpdateMessage - - stage, err := c.stageConstructor(component.Name, data.ArtifactUrl, data.Size) - if err != nil { - return - } - - if err = stage.Download(progressClosure); err != nil { - stage.Close() - return - } - - err = parseAWSXMLError(filepath.Join(stage.Directory(), stage.Filename())) - if err != nil { - return - } - - if err = stage.Unpack(); err != nil { - stage.Close() - return - } - - if err = stage.Validate(); err != nil { - stage.Close() - return - } - - component.stage = stage - stageClose = stage.Close - - return -} - -func (c *Catalog) Verify(component *CDKComponent) error { - path := filepath.Join(component.stage.Directory(), component.Name) - - if operatingSystem == "windows" { - path = fmt.Sprintf("%s.exe", path) - } - - data, err := os.ReadFile(path) - if err != nil { - return err - } - - sig, err := component.stage.Signature() - if err != nil { - return err - } - - rootPublicKey := minisign.PublicKey{} - if err := rootPublicKey.UnmarshalText([]byte(publicKey)); err != nil { - return errors.Wrap(err, "unable to load root public key") - } - - return verifySignature(rootPublicKey, data, sig) -} - -func (c *Catalog) Install(component *CDKComponent) error { - if component.stage == nil { - return errors.Errorf("component '%s' not staged", component.Name) - } - - componentDir, err := componentDirectory(component.Name) - if err != nil { - return err - } - - err = os.MkdirAll(componentDir, os.ModePerm) - if err != nil { - return err - } - - err = component.stage.Commit(componentDir) - if err != nil { - return err - } - - component.HostInfo, err = NewHostInfo(componentDir, component.Description, component.Type) - if err != nil { - return err - } - - path := filepath.Join(componentDir, component.Name) - - if operatingSystem == "windows" { - path = fmt.Sprintf("%s.exe", path) - } - - if component.ApiInfo != nil && - (component.ApiInfo.ComponentType == BinaryType || component.ApiInfo.ComponentType == CommandType) { - if err := os.Chmod(path, 0744); err != nil { - return errors.Wrap(err, "unable to make component executable") - } - } - - return nil -} - -// Delete a CDKComponent -// -// Remove the Component install directory and all sub-directory. This function will not return an -// error if the Component is not installed. -func (c *Catalog) Delete(component *CDKComponent) error { - componentDir, err := componentDirectory(component.Name) - if err != nil { - return err - } - - _, err = os.Stat(componentDir) - if err != nil { - return errors.Errorf("component not installed. Try running 'lacework component install %s'", component.Name) - } - - return os.RemoveAll(componentDir) -} - -func NewCatalog( - client *api.Client, - stageConstructor StageConstructor, -) (*Catalog, error) { - if stageConstructor == nil { - return nil, errors.New("StageConstructor is not specified to create new catalog") - } - - response, err := client.V2.Components.ListComponents(operatingSystem, architecture) - if err != nil { - return nil, err - } - - var rawComponents []api.LatestComponentVersion - - if len(response.Data) > 0 { - rawComponents = response.Data[0].Components - } - - cdkComponents := make(map[string]CDKComponent, len(rawComponents)) - - for _, c := range rawComponents { - ver, err := semver.NewVersion(c.Version) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("component '%s' version '%s'", c.Name, c.Version)) - } - - var allVersions []*semver.Version - - apiInfo := NewAPIInfo(c.Id, c.Name, ver, allVersions, c.Description, c.Size, c.Deprecated, Type(c.ComponentType)) - cdkComponents[c.Name] = NewCDKComponent(apiInfo, nil) - } - - components, err := mergeComponents(cdkComponents) - if err != nil { - return nil, err - } - - return &Catalog{client, components, stageConstructor}, nil -} - -func NewCachedCatalog( - client *api.Client, - stageConstructor StageConstructor, - cachedComponentsApiInfo map[string]*ApiInfo, -) (*Catalog, error) { - if stageConstructor == nil { - return nil, errors.New("StageConstructor is not specified to create new catalog") - } - - cachedComponents := make(map[string]CDKComponent, len(cachedComponentsApiInfo)) - - for _, apiInfo := range cachedComponentsApiInfo { - cachedComponents[apiInfo.Name] = NewCDKComponent(apiInfo, nil) - } - - components, err := mergeComponents(cachedComponents) - if err != nil { - return nil, err - } - - return &Catalog{client, components, stageConstructor}, nil -} - -// mergeComponents combines the passed in components with the local components -func mergeComponents(components map[string]CDKComponent) (allComponents map[string]CDKComponent, err error) { - localComponents, err := LoadLocalComponents() - if err != nil { - return - } - - allComponents = make(map[string]CDKComponent, len(localComponents)+len(components)) - - for _, c := range components { - var hostInfo *HostInfo - component, ok := localComponents[c.Name] - if ok { - hostInfo = component.HostInfo - delete(localComponents, c.Name) - } - allComponents[c.Name] = NewCDKComponent(c.ApiInfo, hostInfo) - } - - for _, c := range localComponents { - allComponents[c.Name] = c - } - - return -} - -func LoadLocalComponents() (components map[string]CDKComponent, err error) { - cacheDir, err := CatalogCacheDir() - if err != nil { - return - } - - subDir, err := os.ReadDir(cacheDir) - if err != nil { - return - } - - components = make(map[string]CDKComponent, len(subDir)) - - // Prototype backwards compatibility - prototypeState, err := LocalState() - if err != nil { - prototypeState = new(State) - err = nil - } - prototypeComponents := make(map[string]Component, len(prototypeState.Components)) - for _, component := range prototypeState.Components { - prototypeComponents[component.Name] = component - } - - for _, file := range subDir { - if !file.IsDir() { - continue - } - - hostInfo, _ := LoadHostInfo(filepath.Join(cacheDir, file.Name())) - if hostInfo == nil { - component, found := prototypeComponents[file.Name()] - if !found { - continue - } - - hostInfo, err = NewHostInfo(filepath.Join(cacheDir, file.Name()), component.Description, component.Type) - if err != nil { - return nil, err - } - } - - if hostInfo.Development() { - _, err := newDevInfo(hostInfo.Dir) - if err != nil { - return nil, err - } - components[hostInfo.Name] = NewCDKComponent(nil, hostInfo) - } else { - components[hostInfo.Name] = NewCDKComponent(nil, hostInfo) - } - } - - return -} - -func listComponentVersions(client *api.Client, componentId int32) ([]*semver.Version, error) { - response, err := client.V2.Components.ListComponentVersions(componentId, operatingSystem, architecture) - if err != nil { - return nil, err - } - - var rawVersions []string - - if len(response.Data) > 0 { - rawVersions = response.Data[0].Versions - } - - versions := make([]*semver.Version, len(rawVersions)) - - for idx, v := range rawVersions { - ver, err := semver.NewVersion(v) - if err != nil { - return nil, err - } - - versions[idx] = ver - } - - return versions, nil -} - -// Returns the directory that the component executable and configuration is stored in. -func componentDirectory(componentName string) (string, error) { - dir, err := CatalogCacheDir() - if err != nil { - return "", err - } - - return filepath.Join(dir, componentName), nil -} - -type awsXMLError struct { - xml.Name - Code string `xml:"Code"` - Message string `xml:"Message"` -} - -func parseAWSXMLError(path string) error { - data, err := os.ReadFile(path) - if err != nil { - return err - } - - xmlError := &awsXMLError{} - err = xml.Unmarshal(data, xmlError) - if err != nil { - return nil - } - - log.Error(string(data)) - - return errors.Errorf("Code: %s. Message: %s", xmlError.Code, xmlError.Message) -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_component.go b/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_component.go deleted file mode 100644 index 259380496..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_component.go +++ /dev/null @@ -1,239 +0,0 @@ -package lwcomponent - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/Masterminds/semver" - "github.com/fatih/color" - "github.com/lacework/go-sdk/internal/file" - "github.com/pkg/errors" -) - -const ( - DevelopmentEnv = "LW_CDK_DEV_COMPONENT" -) - -type CDKComponent struct { - Name string `json:"name"` - Description string `json:"description"` - Type Type `json:"type"` - Status Status `json:"-"` - InstallMessage string `json:"-"` - UpdateMessage string `json:"-"` - - Exec Executer `json:"-"` - - ApiInfo *ApiInfo `json:"apiInfo,omitempty"` - HostInfo *HostInfo `json:"-"` - stage Stager -} - -func NewCDKComponent(apiInfo *ApiInfo, hostInfo *HostInfo) CDKComponent { - var ( - exec Executer = &nonExecutable{} - ) - - status := status(apiInfo, hostInfo) - - switch status { - case Installed, UpdateAvailable, InstalledDeprecated, Development: - { - dir := hostInfo.Dir - - if hostInfo.ComponentType == BinaryType || hostInfo.ComponentType == CommandType { - exec = NewExecuable(hostInfo.Name, dir) - } - } - default: - { - - } - } - - if apiInfo != nil { - return CDKComponent{ - Name: apiInfo.Name, - Description: apiInfo.Desc, - Type: apiInfo.ComponentType, - Status: status, - Exec: exec, - ApiInfo: apiInfo, - HostInfo: hostInfo, - } - } - - if hostInfo != nil { - return CDKComponent{ - Name: hostInfo.Name, - Description: hostInfo.Desc, - Type: hostInfo.ComponentType, - Status: status, - Exec: exec, - ApiInfo: apiInfo, - HostInfo: hostInfo, - } - } - - return CDKComponent{} -} - -func (c *CDKComponent) Dir() (string, error) { - dir, err := CatalogCacheDir() - if err != nil { - return "", err - } - - return filepath.Join(dir, c.Name), nil -} - -func (c *CDKComponent) EnterDevMode() error { - if c.HostInfo != nil && c.HostInfo.Development() { - return errors.New("component already under development.") - } - - dir, err := c.Dir() - if err != nil { - return errors.New("unable to detect RootPath") - } - - devFile := filepath.Join(dir, DevelopmentFile) - if !file.FileExists(devFile) { - devInfo := &DevInfo{ - ComponentType: c.Type, - Desc: fmt.Sprintf("(dev-mode) %s", c.Description), - Name: c.Name, - Version: "0.0.0-dev", - } - - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(devInfo); err != nil { - return err - } - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return err - } - - return os.WriteFile(devFile, buf.Bytes(), 0644) - } - - return nil -} - -func (c *CDKComponent) InstalledVersion() *semver.Version { - if c.HostInfo != nil { - version, err := c.HostInfo.Version() - if err == nil { - return version - } - - if componentDir, err := c.Dir(); err == nil { - if devInfo, err := newDevInfo(componentDir); err == nil { - version, err = semver.NewVersion(devInfo.Version) - if err == nil { - return version - } - } - } - } - - return nil -} - -func (c *CDKComponent) LatestVersion() *semver.Version { - if c.ApiInfo != nil { - return c.ApiInfo.Version - } - - return nil -} - -func (c *CDKComponent) PrintSummary() []string { - var ( - colorize *color.Color - version *semver.Version - ) - - switch c.Status { - case Installed, InstalledDeprecated, UpdateAvailable, Development, Tainted: - version = c.InstalledVersion() - case NotInstalled, NotInstalledDeprecated: - version = c.ApiInfo.Version - default: - version = &semver.Version{} - } - - colorize = c.Status.Color() - - return []string{ - colorize.Sprintf(c.Status.String()), - c.Name, - version.String(), - c.Description, - } -} - -func status(apiInfo *ApiInfo, hostInfo *HostInfo) Status { - status := UnknownStatus - - if hostInfo != nil { - if hostInfo.Development() { - return Development - } - - if err := hostInfo.Validate(); err != nil { - return UnknownStatus - } - - if apiInfo != nil { - installedVer, err := hostInfo.Version() - if err != nil { - return UnknownStatus - } - - if isTainted(apiInfo, installedVer) { - return Tainted - } - - if apiInfo.Deprecated { - return InstalledDeprecated - } - - latestVer := apiInfo.Version - if latestVer.GreaterThan(installedVer) { - return UpdateAvailable - } else { - return Installed - } - } else { - return InstalledDeprecated - } - } - - if apiInfo != nil && hostInfo == nil { - if apiInfo.Deprecated { - return NotInstalledDeprecated - } - - return NotInstalled - } - - return status -} - -func isTainted(apiInfo *ApiInfo, installedVer *semver.Version) bool { - if len(apiInfo.AllVersions) == 0 { - return false - } - - for _, ver := range apiInfo.AllVersions { - if ver.Equal(installedVer) { - return false - } - } - return true -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_executable.go b/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_executable.go deleted file mode 100644 index 01ef2a3e6..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/cdk_executable.go +++ /dev/null @@ -1,119 +0,0 @@ -package lwcomponent - -import ( - "bytes" - "os" - "os/exec" - "path/filepath" - "runtime" - - "github.com/pkg/errors" -) - -var ( - ErrNonExecutable error = errors.New("component not executable") - ErrRun string = "unable to run component" -) - -type Executer interface { - Executable() bool - - Execute(args []string, envs ...string) (stdout string, stderr string, err error) - - ExecuteInline(args []string, envs ...string) (err error) - - Path() string -} - -type executable struct { - path string -} - -func NewExecuable(name string, dir string) Executer { - path := filepath.Join(dir, name) - if runtime.GOOS == "windows" { - path += ".exe" - } - - return &executable{path: path} -} - -func (e *executable) Path() string { - return e.path -} - -func (e *executable) Executable() bool { - return true -} - -func (e *executable) Execute(args []string, envs ...string) (stdout string, stderr string, err error) { - return execute(e.path, args, envs...) -} - -func (e *executable) ExecuteInline(args []string, envs ...string) (err error) { - return executeInline(e.path, args, envs...) -} - -func execute(path string, args []string, envs ...string) (stdout string, stderr string, err error) { - var outBuf, errBuf bytes.Buffer - - cmd := exec.Command(path, args...) - - cmd.Env = append(os.Environ(), envs...) - - cmd.Stdin = nil - cmd.Stdout = &outBuf - cmd.Stderr = &errBuf - - err = run(cmd) - - stdout, stderr = outBuf.String(), errBuf.String() - - return -} - -func executeInline(path string, args []string, envs ...string) error { - cmd := exec.Command(path, args...) - - cmd.Env = append(os.Environ(), envs...) - - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - return run(cmd) -} - -func run(cmd *exec.Cmd) error { - if err := cmd.Run(); err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - return &RunError{ - Err: err, - Message: ErrRun, - ExitCode: exitError.ExitCode(), - } - } - return errors.Wrap(err, ErrRun) - } - - return nil -} - -type nonExecutable struct { -} - -func (e *nonExecutable) Executable() bool { - return false -} - -func (e *nonExecutable) Execute(args []string, envs ...string) (stdout string, stderr string, err error) { - return "", "", ErrNonExecutable -} - -func (e *nonExecutable) ExecuteInline(args []string, envs ...string) (err error) { - return ErrNonExecutable -} - -func (e *nonExecutable) Path() string { - return "" -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/component.go b/vendor/github.com/lacework/go-sdk/lwcomponent/component.go deleted file mode 100644 index 6d41a8df8..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/component.go +++ /dev/null @@ -1,769 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// A development kit for the cloud based of modular components. -package lwcomponent - -import ( - "bytes" - _ "embed" - "encoding/base64" - "encoding/json" - "fmt" - "os" - "path/filepath" - "runtime" - "sort" - "strings" - "time" - - "aead.dev/minisign" - "github.com/Masterminds/semver" - "github.com/cenkalti/backoff/v4" - dircopy "github.com/otiai10/copy" - "github.com/pkg/errors" - - "github.com/lacework/go-sdk/api" - "github.com/lacework/go-sdk/internal/archive" - "github.com/lacework/go-sdk/internal/cache" - "github.com/lacework/go-sdk/internal/file" -) - -// State holds the components specification -// -// You can load the state from the Lacework API server by passing an `api.Client`. -// -// client, err := api.NewClient(account, opts...) -// cState, err := lwcomponent.LoadState(client) -// -// Or, you can load the state from the local storage. -// -// cState, err := lwcomponent.LocalState() -type State struct { - Version string `json:"version"` - Components []Component `json:"components"` -} - -// LoadState loads the state from the Lacework API server -func LoadState(client *api.Client) (*State, error) { - if client != nil { - s := new(State) - - // load remote components - this involves a network call that may fail due - // to network issues or rate limits, so we retry this if it fails - err := backoff.Retry(func() error { - return client.RequestDecoder("GET", "v2/Components", nil, s) - }, backoffStrategy()) - if err != nil { - return s, err - } - - // load local components - s.loadComponentsFromDisk() - - // load dev components - s.loadDevComponents() - - return s, s.WriteState() - } - return nil, errors.New("invalid api client") -} - -func backoffStrategy() *backoff.ExponentialBackOff { - strategy := backoff.NewExponentialBackOff() - strategy.InitialInterval = 2 * time.Second - strategy.MaxElapsedTime = 1 * time.Minute - return strategy -} - -// loadComponentsFromDisk will load all component from disk (local) -func (s *State) loadComponentsFromDisk() { - if dir, err := Dir(); err == nil { - components, err := os.ReadDir(dir) - if err != nil { - return - } - - // traverse components dir - for _, c := range components { - if !c.IsDir() { - continue - } - - // load components that are not already registered - if _, found := s.GetComponent(c.Name()); !found { - component := Component{Name: c.Name()} - - // verify that the directory is a component, that means that the - // directory contains either a '.dev' file or, both '.version' - // and '.signature' files - // - // TODO @afiune maybe we should deploy a .specs file? - err := component.isVerified() - - if component.UnderDevelopment() || err == nil { - s.Components = append(s.Components, component) - } - } - } - } -} - -// loadDevComponents will load all components that are under development -func (s *State) loadDevComponents() { - for i := range s.Components { - if s.Components[i].UnderDevelopment() { - // existing component being developed - if err := s.Components[i].loadDevSpecs(); err != nil { - s.Components[i].Description = err.Error() - } - } - } - - if devComponent := os.Getenv("LW_CDK_DEV_COMPONENT"); devComponent != "" { - // component is not yet defined, add it to the state - dev := Component{Name: devComponent} - if err := dev.loadDevSpecs(); err != nil { - dev.Description = err.Error() - } - s.Components = append(s.Components, dev) - } -} - -// LocalState loads the state from the local storage ("Dir()/state") -func LocalState() (*State, error) { - state := new(State) - componentsFile, err := Dir() - if err != nil { - return state, err - } - - stateFile := filepath.Join(componentsFile, "state") - - stateBytes, err := os.ReadFile(stateFile) - if err != nil { - return state, err - } - if err := json.Unmarshal(stateBytes, state); err != nil { - return state, err - } - - // load local components - state.loadComponentsFromDisk() - - // load dev components - state.loadDevComponents() - - return state, nil -} - -// GetComponent returns the pointer of a component, if the component is not -// found, this function will return a `nil` pointer and `false` -// -// Usage: -// -// component, found := state.GetComponent(name) -// -// if !found { -// fmt.Println("Component %s not found", name) -// } -func (s State) GetComponent(name string) (*Component, bool) { - for i := range s.Components { - if s.Components[i].Name == name { - return &s.Components[i], true - } - } - return nil, false -} - -// WriteState stores the components state to disk -func (s State) WriteState() error { - dir, err := Dir() - if err != nil { - return err - } - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return err - } - - stateFile := filepath.Join(dir, "state") - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(s); err != nil { - return err - } - - if err := os.WriteFile(stateFile, buf.Bytes(), 0644); err != nil { - return err - } - - return nil -} - -// Dir returns the directory where the components will be stored -func Dir() (string, error) { - cacheDir, err := cache.CacheDir() - if err != nil { - return "", errors.Wrap(err, "unable to locate components directory") - } - return filepath.Join(cacheDir, "components"), nil -} - -func (s State) Install(component *Component, version string, progressClosure func(path string, sizeB int64)) error { - rPath, err := component.RootPath() - if err != nil { - return err - } - - // verify development mode - if component.UnderDevelopment() { - p, _ := component.Path() // @afiune we don't care if the component exists or not - msg := "components under development can't be installed.\n\n" + - "Deploy the component manually at '" + p + "'" - return errors.New(msg) - } - - // @afiune verify if component is in latest - artifact, found := component.ArtifactForRunningHost(version) - if !found { - return errors.Errorf( - "could not find an artifact for version %s on the current platform (%s/%s)", - version, runtime.GOOS, runtime.GOARCH, - ) - } - - path, err := component.Path() - // it is ok if the component is not found yet - if err != nil && !IsNotFound(err) { - return err - } - - // download to temp dir, this must be different from staging dir - // because the archive may have the same name as the extracted folder - downloadDir, err := os.MkdirTemp("", "cdk-component-stage-download") - if err != nil { - return err - } - defer os.RemoveAll(downloadDir) - downloadPath := filepath.Join(downloadDir, component.Name) - - // Stage to temp dir before installing - stagingDir, err := os.MkdirTemp("", "cdk-component-stage-extract") - if err != nil { - return err - } - defer os.RemoveAll(stagingDir) - - stagingPath := filepath.Join(stagingDir, component.Name) - - if _, err = os.Create(downloadPath); err != nil { - return err - } - - // There is no artifact.Size so we pass 0 - go progressClosure(downloadPath, 0) - - err = DownloadFile(downloadPath, artifact.URL) - if err != nil { - return errors.Wrap(err, "unable to download component artifact") - } - - // if the component is a tgz archive unpack it, otherwise leave it alone - if err = archive.DetectTGZAndUnpack(downloadPath, stagingDir); err != nil { - return err - } - - //if the component was not an archive then nothing was created in the staging dir - //we must move it over - if _, err := os.Stat(stagingPath); errors.Is(err, os.ErrNotExist) { - err = file.Copy(downloadPath, stagingPath) - if err != nil { - return err - } - } - - // if the component is not an archive make a dir for it to live in - f, err := os.Stat(stagingPath) - if err != nil { - return err - } - if !f.IsDir() { - if err := os.MkdirAll(rPath, os.ModePerm); err != nil { - return err - } - //move the component from the staging dir to it's path - if err = file.Copy(stagingPath, path); err != nil { - return err - } - } else { - //move the component from the staging dir to it's root path - if err = dircopy.Copy(stagingPath, rPath); err != nil { - return err - } - } - - if err := component.WriteVersion(artifact.Version); err != nil { - return err - } - - // @afiune check 1) cross-platform and 2) correct permissions - // if the file has permissions already, can we avoid this? - if component.IsExecutable() { - if err := os.Chmod(path, 0744); err != nil { - return errors.Wrap(err, "unable to make component executable") - } - } - - return nil -} - -func (s State) Verify(component *Component, version string) error { - artifact, found := component.ArtifactForRunningHost(version) - if !found { - return errors.Errorf( - "could not find an artifact for version %s on the current platform (%s/%s)", - version, runtime.GOOS, runtime.GOARCH, - ) - } - - if err := component.WriteSignature([]byte(artifact.Signature)); err != nil { - return err - } - - rPath, err := component.RootPath() - if err != nil { - return err - } - - // verify component - if err := component.isVerified(); err != nil { - // @afiune notify and remove installed component - defer os.RemoveAll(rPath) - return err - } - - return nil -} - -var ( - baseRunErr string = "unable to run component" -) - -type Artifact struct { - OS string `json:"os"` - ARCH string `json:"arch"` - URL string `json:"url,omitempty"` - Signature string `json:"signature"` - Version string `json:"version"` - UpdateMessage string `json:"updateMessage"` - //Size ? -} - -// Components should leave a trail/crumb after installation or update, -// these messages will be shown by the Lacework CLI -type Breadcrumbs struct { - InstallationMessage string `json:"installationMessage,omitempty"` - UpdateMessage string `json:"updateMessage,omitempty"` -} - -// Component can be a command-line tool, a new command that extends the Lacework CLI, or a library that -// contains files used by another Lacework component. -type Component struct { - Name string `json:"name"` - Description string `json:"description"` - Type Type `json:"type"` - LatestVersion semver.Version `json:"-"` - Artifacts []Artifact `json:"artifacts"` - Breadcrumbs Breadcrumbs `json:"breadcrumbs,omitempty"` - - // @dhazekamp command_name required when CLICommand is true? - CommandName string `json:"command_name,omitempty"` -} - -func (c *Component) UnmarshalJSON(data []byte) error { - type ComponentAlias Component - type T struct { - *ComponentAlias `json:",inline"` - LatestVersionString string `json:"version"` - } - - temp := &T{ComponentAlias: (*ComponentAlias)(c)} - err := json.Unmarshal(data, temp) - if err != nil { - return err - } - - latestVersion, err := semver.NewVersion(temp.LatestVersionString) - if err != nil { - return err - } - c.LatestVersion = *latestVersion - - return nil -} - -func (c Component) MarshalJSON() ([]byte, error) { - type ComponentAlias Component - type T struct { - ComponentAlias `json:",inline"` - LatestVersionString string `json:"version"` - } - - obj := &T{ComponentAlias: (ComponentAlias)(c), LatestVersionString: c.LatestVersion.String()} - return json.Marshal(obj) -} - -// RootPath returns the component's root path ("Dir()/{name}") -func (c Component) RootPath() (string, error) { - dir, err := Dir() - if err != nil { - return "", err - } - - return filepath.Join(dir, c.Name), nil -} - -// Path returns the path to the component ("RootPath()/{name}") -func (c Component) Path() (string, error) { - dir, err := c.RootPath() - if err != nil { - return "", err - } - - // @afiune maybe component/version/bin - // but why would we want older versions? - cPath := filepath.Join(dir, c.Name) - if runtime.GOOS == "windows" { - cPath += ".exe" - } - - if file.FileExists(cPath) { - return cPath, nil - } - return cPath, ErrComponentNotFound -} - -// CurrentVersion returns the current installed version of the component -func (c Component) CurrentVersion() (*semver.Version, error) { - // development mode, avoid loading the current version, - // return latest which is what's inside the '.dev' specs - if c.UnderDevelopment() { - return &c.LatestVersion, nil - } - - dir, err := c.RootPath() - if err != nil { - return nil, err - } - - cvPath := filepath.Join(dir, ".version") - if !file.FileExists(cvPath) { - // @afiune help the user fix this issue with a better error message - return nil, errors.New("component version file does not exist") - } - - dat, err := os.ReadFile(cvPath) - if err != nil { - return nil, errors.Wrap(err, "unable to read component version file") - } - - cv, err := semver.NewVersion(strings.TrimSpace(string(dat))) - if err != nil { - err = errors.New("unable to parse component version") - } - return cv, err -} - -// SignatureFromDisk returns the component signature stored on disk ("RootPath()/.signature") -func (c Component) SignatureFromDisk() ([]byte, error) { - var sig []byte - - dir, err := c.RootPath() - if err != nil { - return nil, err - } - - csPath := filepath.Join(dir, ".signature") - if !file.FileExists(csPath) { - return sig, errors.New("component signature file does not exist") - } - - sig, err = os.ReadFile(csPath) - if err != nil { - return sig, errors.Wrap(err, "unable to read component signature file") - } - - // Artifact signature may or may not be b64encoded - decoded_sig, err := base64.StdEncoding.DecodeString(string(sig)) - if err == nil { - sig = decoded_sig - } - - return sig, nil -} - -// WriteSignature stores the component signature on disk -func (c Component) WriteSignature(signature []byte) error { - dir, err := c.RootPath() - if err != nil { - return err - } - - cvPath := filepath.Join(dir, ".signature") - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return err - } - - return os.WriteFile(cvPath, signature, 0644) -} - -// WriteVersion stores the component version on disk -func (c Component) WriteVersion(installed string) error { - dir, err := c.RootPath() - if err != nil { - return err - } - - cvPath := filepath.Join(dir, ".version") - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return err - } - - if installed == "" { - installed = c.LatestVersion.String() - } - return os.WriteFile(cvPath, []byte(installed), 0644) -} - -// UpdateAvailable returns true if there is a newer version of the component -func (c Component) UpdateAvailable() (bool, error) { - cv, err := c.CurrentVersion() - if err != nil { - return false, err - } - - return c.LatestVersion.GreaterThan(cv), nil -} - -// Status returns the component status -func (c Component) Status() Status { - _, err := c.Path() - if err == nil { - // check if the component has an update - update, err := c.UpdateAvailable() - if err == nil && update { - return UpdateAvailable - - } - return Installed - } - if IsNotFound(err) { - return NotInstalled - } - return UnknownStatus -} - -// ArtifactForRunningHost returns the right component artifact for the running host, -func (c Component) ArtifactForRunningHost(version string) (*Artifact, bool) { - for _, artifact := range c.Artifacts { - if artifact.OS == runtime.GOOS && artifact.ARCH == runtime.GOARCH && artifact.Version == version { - return &artifact, true - } - } - return nil, false -} - -// loadDevSpecs will lookup for the '.dev' specs file under the -// component root path to load it into the component itself -func (c *Component) loadDevSpecs() error { - dir, err := c.RootPath() - if err != nil { - return errors.New("unable to detect RootPath") - } - - devSpecs := filepath.Join(dir, ".dev") - if file.FileExists(devSpecs) { - devSpecsBytes, err := os.ReadFile(devSpecs) - if err != nil { - return errors.Errorf("unable to read %s file", devSpecs) - } - err = json.Unmarshal(devSpecsBytes, c) - if err != nil { - return errors.Errorf("unable to unmarshal %s file", devSpecs) - } - } else { - return errors.Errorf("create dev specs file '%s'", devSpecs) - } - - return nil -} - -// UnderDevelopment returns true if the component is under development -// that is, if the component root path has the '.dev' specs file or, if -// the environment variable 'LW_CDK_DEV_COMPONENT' matches the component name -func (c Component) UnderDevelopment() bool { - if os.Getenv("LW_CDK_DEV_COMPONENT") == c.Name { - return true - } - - dir, err := c.RootPath() - if err != nil { - return false - } - - return file.FileExists(filepath.Join(dir, ".dev")) -} - -// isVerified checks if the component has a valid signature -func (c Component) isVerified() error { - // development mode, avoid verifying - if c.UnderDevelopment() { - return nil - } - - // get component signature - sig, err := c.SignatureFromDisk() - if err != nil { - return err - } - - // get component path - cPath, err := c.Path() - if err != nil { - return err - } - - // open the component - f, err := os.ReadFile(cPath) - if err != nil { - return errors.New("unable to read component file") - } - - // load public key - rootPublicKey := minisign.PublicKey{} - if err := rootPublicKey.UnmarshalText([]byte(publicKey)); err != nil { - return errors.Wrap(err, "unable to load root public key") - } - - // validate the signature - return verifySignature(rootPublicKey, f, sig) -} - -func (c Component) EnterDevelopmentMode() error { - if c.UnderDevelopment() { - return errors.New("component already under development.") - } - - dir, err := c.RootPath() - if err != nil { - return errors.New("unable to detect RootPath") - } - - devSpecs := filepath.Join(dir, ".dev") - if !file.FileExists(devSpecs) { - // remove prod artifacts - c.Artifacts = make([]Artifact, 0) - - // configure dev version - cv, _ := semver.NewVersion("0.0.0-dev") - c.LatestVersion = *cv - - // update description - c.Description = fmt.Sprintf("(dev-mode) %s", c.Description) - - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(c); err != nil { - return err - } - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return err - } - - return os.WriteFile(devSpecs, buf.Bytes(), 0644) - } - - return nil -} - -func (c Component) getVersionsAndBreadcrumbs() ([]*semver.Version, map[semver.Version]string) { - versionToBreadcrumb := make(map[semver.Version]string) - allVersions := make([]*semver.Version, 0) - versionToBreadcrumb[c.LatestVersion] = c.Breadcrumbs.UpdateMessage - allVersions = append(allVersions, &c.LatestVersion) - for _, artifact := range c.Artifacts { - parsedVersion, err := semver.NewVersion(artifact.Version) - if err == nil { - // We don't expect invalid versions from the server, but if we do get them - // let's just recover and ignore that version rather than crashing. - _, alreadySeen := versionToBreadcrumb[*parsedVersion] - if !alreadySeen { - versionToBreadcrumb[*parsedVersion] = artifact.UpdateMessage - allVersions = append(allVersions, parsedVersion) - } - } - } - sort.Sort(semver.Collection(allVersions)) - return allVersions, versionToBreadcrumb -} - -func (c Component) MakeUpdateMessage(from, to semver.Version) string { - if from.LessThan(&to) { - versions, breadcrumbs := c.getVersionsAndBreadcrumbs() - updateMessage := "" - for _, version := range versions { - if version.LessThan(&from) || version.Equal(&from) { - // We're before the breadcrumbs we care about, we don't include this one but keep going - continue - } - if version.GreaterThan(&to) { - // We're past the breadcrumbs we care about, we can stop iterating - break - } - if breadcrumbs[*version] != "" { - // We've found a breadcrumb in the range (from, to] which is one we care about - updateMessage += "\n" - updateMessage += breadcrumbs[*version] - } - } - return fmt.Sprintf("Successfully upgraded component from %s to %s%s", from.String(), to.String(), updateMessage) - } - return fmt.Sprintf("Successfully downgraded component from %s to %s", from.String(), to.String()) -} - -func (c Component) ListVersions(installed *semver.Version) string { - versions, _ := c.getVersionsAndBreadcrumbs() - result := "The following versions of this component are available to install:" - foundInstalled := false - for _, version := range versions { - result += "\n" - result += " - " + version.String() - if installed != nil && version.Equal(installed) { - result += " (installed)" - foundInstalled = true - } - } - if installed != nil && !foundInstalled { - result += fmt.Sprintf( - "\n\nThe currently installed version %s is no longer available to install.", - installed.String(), - ) - } - return result -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/dev_info.go b/vendor/github.com/lacework/go-sdk/lwcomponent/dev_info.go deleted file mode 100644 index 2b05c7846..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/dev_info.go +++ /dev/null @@ -1,41 +0,0 @@ -package lwcomponent - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/Masterminds/semver" - "github.com/pkg/errors" -) - -type DevInfo struct { - ComponentType Type - Desc string - Name string - Version string -} - -func newDevInfo(dir string) (*DevInfo, error) { - path := filepath.Join(dir, DevelopmentFile) - - data, err := os.ReadFile(path) - if err != nil { - return nil, errors.Errorf("unable to read %s file", path) - } - - info := DevInfo{} - - err = json.Unmarshal(data, &info) - if err != nil { - return nil, errors.Errorf("unable to unmarshal %s file", path) - } - - _, err = semver.NewVersion(info.Version) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("development component '%s' version '%s'", info.Name, info.Version)) - } - - return &info, nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/error.go b/vendor/github.com/lacework/go-sdk/lwcomponent/error.go deleted file mode 100644 index 04d010d95..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/error.go +++ /dev/null @@ -1,38 +0,0 @@ -package lwcomponent - -import ( - "fmt" - - "github.com/pkg/errors" -) - -var ( - ErrComponentNotFound = errors.New("component not found on disk") -) - -// IsNotFound returns a boolean indicating whether the error is known to -// have determined the component is not found. It is satisfied by -// ErrNotApplyComment -func IsNotFound(err error) bool { - return errors.Is(err, ErrComponentNotFound) -} - -// RunError is a struct used to pass an error when a component tries to run and -// it fails, a few functions will return this error so that callers (upstream -// packages) can unwrap and identify that the error comes from this package -type RunError struct { - ExitCode int - Message string - Err error -} - -func (e *RunError) Error() string { - if e.ExitCode == 0 { - return "" - } - return fmt.Sprintf("%s: %s", e.Message, e.Err.Error()) -} - -func (e *RunError) Unwrap() error { - return e.Err -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/executable.go b/vendor/github.com/lacework/go-sdk/lwcomponent/executable.go deleted file mode 100644 index a1fb5cbb9..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/executable.go +++ /dev/null @@ -1,99 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lwcomponent - -import ( - "bytes" - "io" - "os" - "os/exec" - - "github.com/pkg/errors" -) - -// RunAndOutput runs the command and outputs to os.Stdout and os.Stderr, -// the provided environment variables will be accessible by the component -func (c Component) RunAndOutput(args []string, envs ...string) error { - loc, err := c.Path() - if err != nil { - return errors.Wrap(err, baseRunErr) - } - - cmd := exec.Command(loc, args...) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, envs...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - return c.run(cmd) -} - -// RunAndReturn runs the command and returns its standard output and standard error, -// the provided environment variables will be accessible by the component -func (c Component) RunAndReturn(args []string, stdin io.Reader, envs ...string) ( - stdout string, - stderr string, - err error, -) { - var outBuff, errBuff bytes.Buffer - - loc, err := c.Path() - if err != nil { - err = errors.Wrap(err, baseRunErr) - return - } - - cmd := exec.Command(loc, args...) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, envs...) - cmd.Stdin = stdin - cmd.Stdout = &outBuff - cmd.Stderr = &errBuff - - err = c.run(cmd) - - stdout, stderr = outBuff.String(), errBuff.String() - return -} - -func (c Component) run(cmd *exec.Cmd) error { - if c.IsExecutable() { - - // verify component - if err := c.isVerified(); err != nil { - return errors.Wrap(err, baseRunErr) - } - - if err := cmd.Run(); err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - return &RunError{ - Err: err, - Message: baseRunErr, - ExitCode: exitError.ExitCode(), - } - } - return errors.Wrap(err, baseRunErr) - } - - return nil - } - - return errors.Errorf("%s: component %s is not a binary", baseRunErr, c.Name) -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/host_info.go b/vendor/github.com/lacework/go-sdk/lwcomponent/host_info.go deleted file mode 100644 index 31930a579..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/host_info.go +++ /dev/null @@ -1,152 +0,0 @@ -package lwcomponent - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/Masterminds/semver" - "github.com/lacework/go-sdk/internal/file" - "github.com/pkg/errors" -) - -var ( - VersionFile = ".version" - SignatureFile = ".signature" - InfoFile = ".info" - DevelopmentFile = ".dev" -) - -type HostInfo struct { - Name string `json:"name"` - ComponentType Type `json:"type"` - Desc string `json:"description"` - Dir string `json:"-"` -} - -func LoadHostInfo(dir string) (*HostInfo, error) { - path := filepath.Join(dir, InfoFile) - - data, err := os.ReadFile(path) - if err != nil { - return nil, errors.Errorf("unable to read %s file", path) - } - - hostInfo := HostInfo{} - - err = json.Unmarshal(data, &hostInfo) - if err != nil { - return nil, errors.Errorf("unable to unmarshal %s file", path) - } - - hostInfo.Dir = dir - - if hostInfo.Name == "" { - hostInfo.Name = filepath.Base(dir) - } - - return &hostInfo, nil -} - -func NewHostInfo(dir string, desc string, componentType Type) (*HostInfo, error) { - path := filepath.Join(dir, InfoFile) - - if !file.FileExists(path) { - info := &HostInfo{ - Name: filepath.Base(dir), - Dir: dir, - ComponentType: componentType, - Desc: desc, - } - - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(info); err != nil { - return nil, err - } - - return info, os.WriteFile(path, buf.Bytes(), 0644) - } - - return LoadHostInfo(dir) -} - -func (h *HostInfo) Delete() error { - return os.RemoveAll(h.Dir) -} - -func (h *HostInfo) Development() bool { - return file.FileExists(filepath.Join(h.Dir, DevelopmentFile)) -} - -func (h *HostInfo) Signature() (sig []byte, err error) { - _, err = os.Stat(h.Dir) - if os.IsNotExist(err) { - return - } - - path := filepath.Join(h.Dir, SignatureFile) - if !file.FileExists(path) { - return - } - - sig, err = os.ReadFile(path) - if err != nil { - return - } - - return -} - -func (h *HostInfo) Version() (version *semver.Version, err error) { - _, err = os.Stat(h.Dir) - if os.IsNotExist(err) { - return - } - - path := filepath.Join(h.Dir, VersionFile) - if !file.FileExists(path) { - return nil, errors.New("missing .version file") - } - - data, err := os.ReadFile(path) - if err != nil { - return - } - - return semver.NewVersion(strings.TrimSpace(string(data))) -} - -func (h *HostInfo) Validate() (err error) { - data, err := os.ReadFile(filepath.Join(h.Dir, VersionFile)) - if err != nil { - return - } - - version := string(data) - - _, err = semver.NewVersion(strings.TrimSpace(version)) - if err != nil { - return - } - - componentName := h.Name - - if !file.FileExists(filepath.Join(h.Dir, SignatureFile)) { - return errors.New(fmt.Sprintf("missing file '%s'", componentName)) - } - - path := filepath.Join(h.Dir, componentName) - - if operatingSystem == "windows" { - path = fmt.Sprintf("%s.exe", path) - } - - if !file.FileExists(path) { - return errors.New(fmt.Sprintf("missing file '%s'", componentName)) - } - - return -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/http.go b/vendor/github.com/lacework/go-sdk/lwcomponent/http.go deleted file mode 100644 index cf08a374d..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/http.go +++ /dev/null @@ -1,81 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lwcomponent - -import ( - "os" - "strconv" - "time" - - "github.com/go-resty/resty/v2" - "github.com/lacework/go-sdk/lwlogger" -) - -const ( - DefaultMaxRetry = 3 -) - -var log = lwlogger.New("INFO").Sugar() - -// Retry 3 times (4 requests total) -// Resty default RetryWaitTime is 100ms -// Exponential backoff to a maximum of RetryWaitTime of 2s -func DownloadFile(path string, url string) error { - client := resty.New() - - download_timeout := os.Getenv("CDK_DOWNLOAD_TIMEOUT_MINUTES") - if download_timeout != "" { - val, err := strconv.Atoi(download_timeout) - - if err == nil { - client.SetTimeout(time.Duration(val) * time.Minute) - } - } - - client.SetRetryCount(DefaultMaxRetry) - - client.OnError(func(req *resty.Request, err error) { - fields := []interface{}{ - "raw_error", err, - } - - if v, ok := err.(*resty.ResponseError); ok { - - fields = append(fields, "response_body", string(v.Response.Body())) - - if v.Response.Request != nil { - trace := v.Response.Request.TraceInfo() - fields = append(fields, "trace_info", trace) - } - - if v.Err != nil { - fields = append(fields, "response_error", v.Err.Error()) - } - } - - log.Warnw("Failed to download component", fields...) - }) - - _, err := client.R(). - EnableTrace(). - SetOutput(path). - Get(url) - - return err -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/library.go b/vendor/github.com/lacework/go-sdk/lwcomponent/library.go deleted file mode 100644 index 3a31b27ac..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/library.go +++ /dev/null @@ -1,34 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lwcomponent - -// A library component provides one or more files that other components use -type Library interface { - - // Install downloads the library and deploys the files and index - Install() error - - // Index returns the index of files that the library contains - Index() []string - - // GetFile returns the content of one file from the library - GetFile(string) ([]byte, error) -} - -// @hazekamp figure out LibraryComponent (if component is a library how do we interact with it) diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/minisign.go b/vendor/github.com/lacework/go-sdk/lwcomponent/minisign.go deleted file mode 100644 index 24cd184ad..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/minisign.go +++ /dev/null @@ -1,72 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// A Lacework component package to help facilitate the loading and execution of components -package lwcomponent - -import ( - "crypto/sha256" - _ "embed" - "encoding/base64" - "strings" - - "aead.dev/minisign" - "github.com/pkg/errors" -) - -// Lacework's public key -const publicKey = "RWTcmYcv9P0yMqHghe1Fu4gg65yoBuZCY7r02rD0N7o2XSK5Jq9h1Quk" - -// Verify will verify a two-level signature. -// TODO(pjm): Can we make the signature format be an argument? -func verifySignature(rootKey minisign.PublicKey, file, sig []byte) error { - h := sha256.New() - if _, err := h.Write(file); err != nil { - return errors.New("unable to compute hash for component") - } - - var parsedSig minisign.Signature - if err := parsedSig.UnmarshalText(sig); err != nil { - return errors.New("unable to parse signature") - } - - sigParts := strings.Split(parsedSig.TrustedComment, ".") - if len(sigParts) != 4 { - return errors.New("invalid signature trusted comment") - } - - parsedRootSig, err := base64.StdEncoding.DecodeString(sigParts[3]) - if err != nil { - return errors.Wrap(err, "unable to parse root signature from trusted comment") - } - - if !minisign.Verify(rootKey, []byte(sigParts[1]), parsedRootSig) { - return errors.New("invalid root signature over signing key") - } - - var signingKey minisign.PublicKey - if err := signingKey.UnmarshalText([]byte(sigParts[1])); err != nil { - return errors.Wrap(err, "unable to parse signing key") - } - - if !minisign.Verify(signingKey, h.Sum(nil), sig) { - return errors.New("invalid signature over component") - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/staging.go b/vendor/github.com/lacework/go-sdk/lwcomponent/staging.go deleted file mode 100644 index 0fa5e0cef..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/staging.go +++ /dev/null @@ -1,253 +0,0 @@ -package lwcomponent - -import ( - "archive/tar" - "compress/gzip" - "encoding/base64" - "fmt" - "io" - "net/url" - "os" - "path/filepath" - "strings" - - "github.com/Masterminds/semver" - "github.com/lacework/go-sdk/internal/file" - dircopy "github.com/otiai10/copy" - "github.com/pkg/errors" -) - -type StageConstructor func(name, artifactUrl string, size int64) (stage Stager, err error) - -type Stager interface { - Close() - - Commit(string) error - - Directory() string - - Download(progressClosure func(filepath string, sizeB int64)) error - - Filename() string - - Signature() (sig []byte, err error) - - Unpack() error - - Validate() error -} - -type stageTarGz struct { - artifactUrl *url.URL - dir string - name string - size int64 -} - -func NewStageTarGz(name, artifactUrl string, size int64) (stage Stager, err error) { - dir, err := os.MkdirTemp("", "cdk-component-stage-tar-gz-") - if err != nil { - return - } - - _url, err := url.Parse(artifactUrl) - if err != nil { - os.RemoveAll(dir) - return - } - - stage = &stageTarGz{artifactUrl: _url, dir: dir, name: name, size: size} - - return -} - -func (s *stageTarGz) Close() { - os.RemoveAll(s.dir) -} - -func (s *stageTarGz) Commit(targetDir string) (err error) { - _, err = os.Stat(s.dir) - if os.IsNotExist(err) { - err = errors.New("component not staged") - return - } - - _, err = os.Stat(targetDir) - if os.IsNotExist(err) { - err = errors.New("target install directory doesn't exist") - return - } - - if err = dircopy.Copy(s.dir, targetDir); err != nil { - return - } - - return -} - -func (s *stageTarGz) Directory() string { - return s.dir -} - -func (s *stageTarGz) Filename() string { - return filepath.Base(s.artifactUrl.Path) -} - -func (s *stageTarGz) Download(progressClosure func(filepath string, sizeB int64)) error { - fileName := filepath.Base(s.artifactUrl.Path) - - path := filepath.Join(s.dir, fileName) - - if _, err := os.Create(path); err != nil { - return err - } - - go progressClosure(path, s.size*1024) - - return DownloadFile(path, s.artifactUrl.String()) -} - -func (s *stageTarGz) Signature() ([]byte, error) { - _, err := os.Stat(s.dir) - if os.IsNotExist(err) { - return nil, errors.New("component not staged") - } - - path := filepath.Join(s.dir, SignatureFile) - if !file.FileExists(path) { - return nil, errors.New("missing .signature file") - } - - sig, err := os.ReadFile(path) - if err != nil { - return sig, err - } - - // Artifact signature may or may not be b64encoded - decoded_sig, err := base64.StdEncoding.DecodeString(string(sig)) - if err == nil { - return decoded_sig, nil - } - return sig, nil -} - -func (s *stageTarGz) Unpack() (err error) { - fileName := filepath.Base(s.artifactUrl.Path) - - gzFile := filepath.Join(s.dir, fileName) - tarball := filepath.Join(s.dir, strings.TrimRight(fileName, ".gz")) - - err = gunzip(gzFile, tarball) - if err != nil { - return - } - - err = unTar(tarball, s.dir) - if err != nil { - return - } - - os.Remove(gzFile) - os.Remove(tarball) - - return nil -} - -func (s *stageTarGz) Validate() error { - data, err := os.ReadFile(filepath.Join(s.dir, VersionFile)) - if err != nil { - return err - } - - version := string(data) - - _, err = semver.NewVersion(strings.TrimSpace(version)) - if err != nil { - return errors.Errorf("invalid staged semantic version '%s' for component '%s'", version, s.name) - } - - if !file.FileExists(filepath.Join(s.dir, SignatureFile)) { - return errors.Errorf("missing file '%s'", s.name) - } - - path := filepath.Join(s.dir, s.name) - - if operatingSystem == "windows" { - path = fmt.Sprintf("%s.exe", path) - } - - if !file.FileExists(path) { - return errors.Errorf("missing file '%s'", path) - } - - return nil -} - -// Inflate GZip file. -// -// Writes decompressed data to target path. -func gunzip(source string, target string) (err error) { - reader, err := os.Open(source) - if err != nil { - return - } - defer reader.Close() - - archive, err := gzip.NewReader(reader) - if err != nil { - return - } - defer archive.Close() - - writer, err := os.Create(target) - if err != nil { - return - } - defer writer.Close() - - _, err = io.Copy(writer, archive) - - return -} - -func unTar(tarball string, dir string) error { - reader, err := os.Open(tarball) - if err != nil { - return err - } - defer reader.Close() - - tarReader := tar.NewReader(reader) - - for { - header, err := tarReader.Next() - if err == io.EOF { - break - } else if err != nil { - return err - } - - path := filepath.Join(dir, header.Name) - - info := header.FileInfo() - if info.IsDir() { - if err := os.MkdirAll(path, info.Mode()); err != nil { - return err - } - continue - } - - file, err := os.Create(path) - if err != nil { - return err - } - defer file.Close() - - _, err = io.Copy(file, tarReader) - if err != nil { - return err - } - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/status.go b/vendor/github.com/lacework/go-sdk/lwcomponent/status.go deleted file mode 100644 index 07636cfc2..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/status.go +++ /dev/null @@ -1,84 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lwcomponent - -import "github.com/fatih/color" - -type Status int - -const ( - UnknownStatus Status = iota - Development - NotInstalled - NotInstalledDeprecated - Installed - InstalledDeprecated - UpdateAvailable - Tainted -) - -func (s Status) Color() *color.Color { - switch s { - case Development: - return color.New(color.FgBlue, color.Bold) - case NotInstalled: - return color.New(color.FgWhite, color.Bold) - case Installed: - return color.New(color.FgGreen, color.Bold) - case UpdateAvailable: - return color.New(color.FgYellow, color.Bold) - case InstalledDeprecated, NotInstalledDeprecated, Tainted: - return color.New(color.FgRed, color.Bold) - default: - return color.New(color.FgRed, color.Bold) - } -} - -func (s Status) String() string { - switch s { - case Development: - return "Development" - case Installed: - return "Installed" - case InstalledDeprecated: - return "Installed (Deprecated)" - case NotInstalled: - return "Not Installed" - case NotInstalledDeprecated: - return "Not Installed (Deprecated)" - case Tainted: - return "Tainted (Please update)" - case UpdateAvailable: - return "Update Available" - default: - return "Unknown" - } -} - -// IsInstalled returns true if the component is installed on disk -// -// TODO: @jon-stewart: remove - is in wrong place -func (c Component) IsInstalled() bool { - switch c.Status() { - case Installed, UpdateAvailable: - return true - default: - return false - } -} diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sh b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sh deleted file mode 100644 index b2f94c336..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -echo "Hello ${LW_TEST}!" diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sig b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sig deleted file mode 100644 index b2e9f166c..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/env-vars.sig +++ /dev/null @@ -1 +0,0 @@ -dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpVktIRE5vR1A3amlNZEJ1WktUWEJLRUZpVFBBRTFidVFVQVlJbUthd1RiWVg4dU1GYXJOc2hQMThVZElySGRhTmxkSE1QeEsxMUsrUEpNdUtoWVE0UTA9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0Ka3BoUVlieFp2UUw0cEJzYTdtRVZWMXZwRUYzU0dXWmEvK29UdUN3TEFxRmVwai9ISzFibmJCdlFGY010Vy9STVJKU2lxSFV6TWwwa0ROcXJoSjBqQkE9PQ== diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sh b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sh deleted file mode 100644 index 67464fb16..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -echo "Hello World!" diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sig b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sig deleted file mode 100644 index 27a910457..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world.sig +++ /dev/null @@ -1 +0,0 @@ -dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpUmwrdm03ZE5wcDdKcnQrcUxBTnN1OW0rVGFIU2dWeUpPOUxkZW81WlB3L0dRSnUxZkpaQnVDcnNNVlc1S2J1QjZkRVB6UXZpOGN0MXpKZk1rZ1ZzZ3M9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0KY0p3aDdYRGN0R1I4ZHBjMU5FSm5QR1U1SVhOdk1OWGE2aGRNQTBCQlp2dm5zS1FIZVZWRWtNYzd6bzcyaUZzSEVLUnhlK0FtWGtyeU5nVmk3R2cwRFE9PQ== \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sh b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sh deleted file mode 100644 index f84fa094c..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -echo "Hello $1!" -read line -echo "Hello $line!" >&2 diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sig b/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sig deleted file mode 100644 index da40d402d..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/test_resources/hello-world2.sig +++ /dev/null @@ -1 +0,0 @@ -dW50cnVzdGVkIGNvbW1lbnQ6IApSV1FueUhuVEt6MVJpVHlSQjZTUnJCTHp0UDdmK2x0UGZ6RTAxdTFMQlE5akE2ei9ESFNXcG5oRFVCQVJTKzJXRzh2RjhMeHRDQmpXd3hvK21ySFI3Y3BYOWZpdWgxRlJlUXM9CnRydXN0ZWQgY29tbWVudDogMS5SV1FueUhuVEt6MVJpZHFNbDQ1T3U1WGJKQ3JvVjd6b2hFMWpwYmlnVFVHWHlRWTk0QUY1dW80di4xLmRXNTBjblZ6ZEdWa0lHTnZiVzFsYm5RNklBcFNWMVJqYlZsamRqbFFNSGxOY0V4eVZuQTNWM2xJZG14MlJVMDJWbGhKYkZoelVHRTBRVlpWTDFNcmFIbHdVQ3RRZEhCRldFVnZjazVNUzNaUmVUSlNWMFF4VG1OS056TmxVMU5NWVVFMlRucExObkJ2Y3pscGIyUmhiRU5NY3poT1p6ZzlDblJ5ZFhOMFpXUWdZMjl0YldWdWREb2dDbTFpTW1wSlJWWkJVVE5QWmxJdkwwcFJjalV4VnpkUVIzRnJXRGhHYldNMVlVRktjQ3QwUTNKWE9IWm1XbVo2WW5WTU0yWnZlamR0UW1WNk1IcFFkbEJsZG1ReFUwRnJVa2xtU1cxRU1rZGpTa1ZsVUVKM1BUMD0KK2NvQ242enpFVkV5MCs0ZENQOUR4bVN4Nk9yaGJUTFZ2RXo5SnR0d3Q0anlYdjFTNk0vTmI2dENBcXNzZ1Rhc1FJNEVBVzJpUnZKVElTcFo3UGx4QWc9PQ== \ No newline at end of file diff --git a/vendor/github.com/lacework/go-sdk/lwcomponent/types.go b/vendor/github.com/lacework/go-sdk/lwcomponent/types.go deleted file mode 100644 index c23025003..000000000 --- a/vendor/github.com/lacework/go-sdk/lwcomponent/types.go +++ /dev/null @@ -1,50 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lwcomponent - -type Type string - -const ( - EmptyType Type = "" - - // the component is a binary - BinaryType = "BINARY" - - // will this component be accessible via the CLI - CommandType = "CLI_COMMAND" - - // the component is a library, only provides content for the CLI or other components - LibraryType = "LIBRARY" - - // the component is standalone, should be available in $PATH - StandaloneType = "STANDALONE" -) - -func (c Component) IsExecutable() bool { - switch c.Type { - case BinaryType, CommandType: - return true - default: - return false - } -} - -func (c Component) IsCommandType() bool { - return c.Type == CommandType -} diff --git a/vendor/github.com/lacework/go-sdk/lwconfig/README.md b/vendor/github.com/lacework/go-sdk/lwconfig/README.md deleted file mode 100644 index a76ec6b4d..000000000 --- a/vendor/github.com/lacework/go-sdk/lwconfig/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Lacework Config - -A Go library to manage the Lacework configuration file (`$HOME/.lacework.toml`) - -## Usage - -Download the library into your `$GOPATH`: - - $ go get github.com/lacework/go-sdk/lwconfig - -Import the library into your tool: - -```go -import "github.com/lacework/go-sdk/lwconfig" -``` - -## Examples - -Load the default Lacework configuration file and detect if there is a profile named `test`: -```go -package main - -import ( - "fmt" - "os" - - "github.com/lacework/go-sdk/lwconfig" -) - -func main() { - profiles, err := lwconfig.LoadProfiles() - if err != nil { - fmt.Printf("Error trying to load profiles: %s\n", err) - os.Exit(1) - } - - config, ok := profiles["test"] - if !ok { - fmt.Println("You have a test profile configured!") - } else { - fmt.Println("'test' profile not found") - } -} -``` - -Look at the [examples/](examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/lwconfig/config.go b/vendor/github.com/lacework/go-sdk/lwconfig/config.go deleted file mode 100644 index 5a0d2b374..000000000 --- a/vendor/github.com/lacework/go-sdk/lwconfig/config.go +++ /dev/null @@ -1,184 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// A package to manage the Lacework configuration file ($HOME/.lacework.toml) -package lwconfig - -import ( - "bytes" - "fmt" - "os" - "path" - - "github.com/BurntSushi/toml" - "github.com/mitchellh/go-homedir" - "github.com/pkg/errors" -) - -// Profiles is the representation of the $HOME/.lacework.toml -// -// Example: -// -// [default] -// account = "example" -// api_key = "EXAMPLE_0123456789" -// api_secret = "_0123456789" -// -// [dev] -// account = "dev" -// api_key = "DEV_0123456789" -// api_secret = "_0123456789" -// -// [prod] -// account = "coolcorp" -// subaccount = "prod-business" -// api_key = "PROD_0123456789" -// api_secret = "_0123456789" -// version = 2 -type Profiles map[string]Profile - -// Profile represents a single profile within a configuration file -type Profile struct { - Account string `toml:"account"` - Subaccount string `toml:"subaccount,omitempty"` - ApiKey string `toml:"api_key" survey:"api_key"` - ApiSecret string `toml:"api_secret" survey:"api_secret"` - Version int `toml:"version,omitzero"` -} - -const ( - ApiKeyMinLength = 55 - ApiSecretMinLength = 30 -) - -// Verify will return an error is there is one required setting missing -func (p *Profile) Verify() error { - if p.Account == "" { - return errors.New("account missing") - } - - if err := p.verifyApiKey(); err != nil { - return err - } - - if err := p.verifyApiSecret(); err != nil { - return err - } - - return nil -} - -func (p *Profile) verifyApiKey() error { - if p.ApiKey == "" { - return errors.New("api_key missing") - } - - if len(p.ApiKey) < ApiKeyMinLength { - return errors.New(fmt.Sprintf("api_key must have more than %d characters", ApiKeyMinLength)) - } - - return nil -} - -func (p *Profile) verifyApiSecret() error { - if p.ApiSecret == "" { - return errors.New("api_secret missing") - } - - if len(p.ApiSecret) < ApiSecretMinLength { - return errors.New(fmt.Sprintf("api_secret must have more than %d characters", ApiSecretMinLength)) - } - - return nil -} - -// DefaultConfigPath returns the default path where the Lacework config file -// is located, which is at $HOME/.lacework.toml -func DefaultConfigPath() (string, error) { - home, err := homedir.Dir() - if err != nil { - return "", err - } - return path.Join(home, ".lacework.toml"), nil -} - -// LoadProfiles loads all the profiles from the default location ($HOME/.lacework.toml) -func LoadProfiles() (Profiles, error) { - configPath, err := DefaultConfigPath() - if err != nil { - return Profiles{}, err - } - - return LoadProfilesFrom(configPath) -} - -// LoadProfilesFrom loads all the profiles from the provided location -func LoadProfilesFrom(configPath string) (Profiles, error) { - if configPath == "" { - return Profiles{}, errors.New("unable to load profiles. Specify a configuration file.") - } - - var profiles Profiles - if _, err := toml.DecodeFile(configPath, &profiles); err != nil { - return profiles, errors.Wrap(err, "unable to decode profiles from config") - } - - return profiles, nil -} - -// StoreProfileAt updates a single profile from the provided configuration file -func StoreProfileAt(configPath, name string, profile Profile) error { - if configPath == "" { - defaultPath, err := DefaultConfigPath() - if err != nil { - return err - } - configPath = defaultPath - } - - var ( - profiles = Profiles{} - err error - ) - if _, err = os.Stat(configPath); err == nil { - if profiles, err = LoadProfilesFrom(configPath); err != nil { - return err - } - } - - profiles[name] = profile - return StoreAt(configPath, profiles) -} - -// StoreAt stores the provided profiles into the selected configuration file -func StoreAt(configPath string, profiles Profiles) error { - if configPath == "" { - defaultPath, err := DefaultConfigPath() - if err != nil { - return err - } - configPath = defaultPath - } - - var buf = new(bytes.Buffer) - if err := toml.NewEncoder(buf).Encode(profiles); err != nil { - return err - } - - return os.WriteFile(configPath, buf.Bytes(), 0600) -} diff --git a/vendor/github.com/lacework/go-sdk/lwdomain/README.md b/vendor/github.com/lacework/go-sdk/lwdomain/README.md deleted file mode 100644 index f7eec8273..000000000 --- a/vendor/github.com/lacework/go-sdk/lwdomain/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Lacework Domain - -Use this package to disseminate a domain URL into account, cluster and whether or not -it is an internal account. - -## Usage - -Download the library into your `$GOPATH`: - - $ go get github.com/lacework/go-sdk/lwdomain - -Import the library into your tool: - -```go -import "github.com/lacework/go-sdk/lwdomain" -``` - -## Examples - -The following URL `https://account.lacework.net` would be disseminated into: -* `account` as the account name - -```go -package main - -import ( - "fmt" - "os" - - "github.com/lacework/go-sdk/lwdomain" -) - -func main() { - domain, err := lwdomain.New("https://account.lacework.net") - if err != nil { - fmt.Printf("Error %s\n", err) - os.Exit(1) - } - - // Output: Lacework Account Name: account - fmt.Println("Lacework Account Name: %s", domain.Account) -} -``` diff --git a/vendor/github.com/lacework/go-sdk/lwdomain/domain.go b/vendor/github.com/lacework/go-sdk/lwdomain/domain.go deleted file mode 100644 index 1df284e29..000000000 --- a/vendor/github.com/lacework/go-sdk/lwdomain/domain.go +++ /dev/null @@ -1,99 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2021, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// A package to disseminate a domain URL into account, cluster and whether or not is internal. -package lwdomain - -import ( - "fmt" - "regexp" - "strings" - - "github.com/pkg/errors" -) - -type domain struct { - Account string - Cluster string - Internal bool -} - -// New returns domain information from the provided URL -// -// For instance, the following URL: -// -// d, err := lwdomain.New("https://account.lacework.net") -// -// Would be disseminated into: -// - `account` as the account name -// - `fra` as the cluster name -func New(url string) (domain, error) { - // for the full url https://ACCOUNT.lacework.net - // remove the prefixes https:// or http:// - rx, err := regexp.Compile(`(http://|https://)`) - if err == nil { - url = rx.ReplaceAllString(url, "") - } - - // for the full domain ACCOUNT[.CUSTER][.corp].lacework.net - // subtract the account and cluster name, also detect if the - // domain is internal (dev, qa, preprod, etc) - rx, err = regexp.Compile(`\.lacework\.net.*`) - if err == nil { - domainSplit := rx.Split(url, -1) - if len(domainSplit) > 1 { - url = domainSplit[0] - } else { - return domain{}, errors.New("domain not supported") - } - } - - domainInfo := strings.Split(url, ".") - switch len(domainInfo) { - case 1: - return domain{Account: domainInfo[0]}, nil - case 2: - return domain{ - Account: domainInfo[0], - Cluster: domainInfo[1], - }, nil - case 3: - if domainInfo[2] != "corp" { - return domain{}, errors.New("unable to detect if domain is internal") - } - return domain{ - Account: domainInfo[0], - Cluster: domainInfo[1], - Internal: true, - }, nil - default: - return domain{}, errors.New("unable to detect domain information") - } -} - -func (d *domain) String() string { - if d.Internal { - return fmt.Sprintf("%s.%s.corp", d.Account, d.Cluster) - } - - if d.Cluster != "" { - return fmt.Sprintf("%s.%s", d.Account, d.Cluster) - } - - return d.Account -} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/aws/aws.go b/vendor/github.com/lacework/go-sdk/lwgenerate/aws/aws.go deleted file mode 100644 index e9ea8a5f9..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/aws/aws.go +++ /dev/null @@ -1,1406 +0,0 @@ -// A package that generates Lacework deployment code for Amazon Web Services. -package aws - -import ( - "encoding/json" - "fmt" - "regexp" - "slices" - "strings" - - "github.com/google/uuid" - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/pkg/errors" - - "github.com/lacework/go-sdk/lwgenerate" -) - -type ExistingIamRoleDetails struct { - // Existing IAM Role ARN - Arn string - - // Existing IAM Role Name - Name string - - // Existing IAM Role External Id - ExternalId string -} - -func (e *ExistingIamRoleDetails) IsEmpty() bool { - if e == nil { - return true - } - return e.Arn == "" && e.Name == "" && e.ExternalId == "" -} - -func (e *ExistingIamRoleDetails) IsPartial() bool { - // If nil, return false - if e == nil { - return false - } - - // If all values are empty, return false - if e.Arn == "" && e.Name == "" && e.ExternalId == "" { - return false - } - - // If all values are populated, return false - if e.Arn != "" && e.Name != "" && e.ExternalId != "" { - return false - } - - return true -} - -// NewExistingIamRoleDetails Create new existing IAM role details -func NewExistingIamRoleDetails(name string, arn string, externalId string) *ExistingIamRoleDetails { - return &ExistingIamRoleDetails{ - Arn: arn, - Name: name, - ExternalId: externalId, - } -} - -type OrgAccountMapping struct { - DefaultLaceworkAccount string `json:"default_lacework_account"` - Mapping []OrgAccountMap `json:"mapping"` -} - -func (orgMap *OrgAccountMapping) IsEmpty() bool { - return len(orgMap.Mapping) == 0 && orgMap.DefaultLaceworkAccount == "" -} - -func (orgMap *OrgAccountMapping) ToMap() (map[string]any, error) { - var mappings []map[string]any - mappingsJsonString, err := json.Marshal(orgMap.Mapping) - if err != nil { - return nil, err - } - err = json.Unmarshal(mappingsJsonString, &mappings) - if err != nil { - return nil, err - } - - orgMap.Mapping = []OrgAccountMap{} - - result := make(map[string]any) - jsonString, err := json.Marshal(orgMap) - if err != nil { - return nil, err - } - - err = json.Unmarshal(jsonString, &result) - if err != nil { - return nil, err - } - - result["mapping"] = mappings - return result, nil -} - -type OrgAccountMap struct { - LaceworkAccount string `json:"lacework_account"` - AwsAccounts []string `json:"aws_accounts"` -} - -type AwsSubAccount struct { - // The name of the AwsProfile to use (in AWS configuration) - AwsProfile string - - // The AwsRegion this profile should use if any resources are created - AwsRegion string - - // The Alias of the provider block - Alias string -} - -// Create a new AWS sub account -// -// A subaccount consists of the profile name (which needs to match the executing machines aws configuration) and a -// region for any new resources to be created in -func NewAwsSubAccount(profile string, region string, alias ...string) AwsSubAccount { - subaccount := AwsSubAccount{AwsProfile: profile, AwsRegion: region} - if len(alias) > 0 { - subaccount.Alias = alias[0] - } - return subaccount -} - -type GenerateAwsTfConfigurationArgs struct { - // Should we enable AWS organization integration? - AwsOrganization bool - - // Should we configure Agentless integration in LW? - Agentless bool - - // Agentless management AWS account ID - AgentlessManagementAccountID string - - // Agentless monitored AWS account IDs, OUs, or the organization root. - AgentlessMonitoredAccountIDs []string - - // Agentless monitored AWS accounts - AgentlessMonitoredAccounts []AwsSubAccount - - // Agentless scanning AWS accounts - AgentlessScanningAccounts []AwsSubAccount - - // Is the AWS organization using Control Tower? - ControlTower bool - - // AWS Control Tower Audit account - ControlTowerAuditAccount *AwsSubAccount - - // AWS Control Tower Log Archive account - ControlTowerLogArchiveAccount *AwsSubAccount - - // AWS Control Tower custom KMS key ARN - ControlTowerKmsKeyArn string - - // Should we configure Cloudtrail integration in LW? - Cloudtrail bool - - // Optional name for CloudTrail - CloudtrailName string - - // Should we configure AWS organization mappings? - AwsOrganizationMappings bool - - // Cloudtrail organization account mappings - OrgAccountMappings OrgAccountMapping - - // OrgAccountMapping json used for flag input - OrgAccountMappingsJson string - - // Use exisiting CloudTrail - CloudtrailUseExistingTrail bool - - // Use exisiting CloudTrail SNS topic - CloudtrailUseExistingSNSTopic bool - - // Should we configure CSPM integration in LW? - Config bool - - // Optional name for config - ConfigName string - - // Config additional AWS accounts - ConfigAdditionalAccounts []AwsSubAccount - - // Config Lacework account - ConfigOrgLWAccount string - - // Config Lacework sub-account - ConfigOrgLWSubaccount string - - // Config Lacework access key ID - ConfigOrgLWAccessKeyId string - - // Config Lacework secret key - ConfigOrgLWSecretKey string - - // Config organization ID - ConfigOrgId string - - // Config organization unit - ConfigOrgUnits []string - - // Config resource prefix - ConfigOrgCfResourcePrefix string - - // Custom outputs - CustomOutputs []lwgenerate.HclOutput - - // Supply an AWS region for where to find the cloudtrail resources - // TODO @ipcrm future: support split regions for resources (s3 one place, sns another, etc) - AwsRegion string - - // Supply an AWS Profile name for the main account, only asked if configuring multiple - AwsProfile string - - // Supply an AWS Assume Role for the main account - AwsAssumeRole string - - // Existing S3 Bucket ARN (Required when using existing cloudtrail) - ExistingCloudtrailBucketArn string - - // Optionally supply existing IAM role details - ExistingIamRole *ExistingIamRoleDetails - - // Existing SNS Topic - ExistingSnsTopicArn string - - // Consolidated Trail - ConsolidatedCloudtrail bool - - // Should we force destroy the bucket if it has stuff in it? (only relevant on new Cloudtrail creation) - // DEPRECATED - ForceDestroyS3Bucket bool - - // Enable encryption of bucket if it is created - BucketEncryptionEnabled bool - - // Indicates that the Bucket Encryption flag has been actively set - // this is needed to show this it was set actively to false, rather - // than default value for bool - BucketEncryptionEnabledSet bool - - // Optional name of bucket if creating a new one - BucketName string - - // Arn of the KMS encryption key for S3, required when bucket encryption in enabled - BucketSseKeyArn string - - // Enable S3 bucket notification - S3BucketNotification bool - - // SNS Topic name if creating one and not using an existing one - SnsTopicName string - - // Enable encryption of SNS if it is created - SnsTopicEncryptionEnabled bool - - // Indicates that the SNS Encryption flag has been actively set - // this is needed to show this it was set actively to false, rather - // than default value for bool - SnsEncryptionEnabledSet bool - - // Arn of the KMS encryption key for SNS, required when SNS encryption in enabled - SnsTopicEncryptionKeyArn string - - // SSQ Queue name if creating one and not using an existing one - SqsQueueName string - - // Enable encryption of SQS if it is created - SqsEncryptionEnabled bool - - // Indicates that the SQS Encryption flag has been actively set - // this is needed to show this it was set actively to false, rather - // than default value for bool - SqsEncryptionEnabledSet bool - - // Arn of the KMS encryption key for SQS, required when SQS encryption in enabled - SqsEncryptionKeyArn string - - // For AWS Subaccounts in consolidated CT setups - // TODO @ipcrm future: what about many individual ct/config integrations together? - SubAccounts []AwsSubAccount - - // Lacework Profile to use - LaceworkProfile string - - // The Lacework AWS Root Account ID - LaceworkAccountID string - - // Lacework Organization - LaceworkOrganizationLevel bool - - // Use random Cloudtrail name - UseCloudTrailRandomName bool - - // Default AWS Provider Tags - ProviderDefaultTags map[string]interface{} - - // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc - ExtraBlocksRootTerraform []*hclwrite.Block - - // ExtraProviderArguments allows adding more arguments to the provider block as needed (custom use cases) - ExtraProviderArguments map[string]interface{} - - // ExtraBlocks allows adding more hclwrite.Block to the root terraform document (advanced use cases) - ExtraBlocks []*hclwrite.Block -} - -func (args *GenerateAwsTfConfigurationArgs) IsEmpty() bool { - if args.AwsProfile == "" && args.AwsRegion == "" && !args.Agentless && !args.Config && !args.Cloudtrail { - return true - } - return false -} - -// Ensure all combinations of inputs our valid for supported spec -func (args *GenerateAwsTfConfigurationArgs) Validate() error { - if !args.Agentless && !args.Cloudtrail && !args.Config { - return errors.New("Agentless, CloudTrail or Config integration must be enabled") - } - - if args.AwsRegion == "" { - return errors.New("Main AWS account region must be set") - } - - if args.ExistingIamRole.IsPartial() { - return errors.New("when using an existing IAM role, existing role ARN, name, and external ID all must be set") - } - - if args.AwsOrganization { - if args.Agentless { - if args.AgentlessManagementAccountID == "" { - return errors.New("must specify a management account ID for Agentless organization integration") - } - if len(args.AgentlessMonitoredAccountIDs) == 0 { - return errors.New("must specify monitored account ID list for Agentless organization integration") - } - if len(args.AgentlessMonitoredAccounts) == 0 { - // profile/region is required for single accounts - for _, accountID := range args.AgentlessMonitoredAccountIDs { - regex, _ := regexp.Compile(`^\d{12}$`) - if regex.MatchString(accountID) { - return errors.New("must specify profile/region for single monitored accounts" + - " for Agentless organization integration") - } - } - } - if len(args.AgentlessScanningAccounts) == 0 { - return errors.New("must specify scanning accounts for Agentless organization integration") - } - } - - if args.ControlTower && args.Cloudtrail { - if args.ControlTowerAuditAccount == nil { - return errors.New("must specify audit account for CloudTrail Control Tower integration") - } - if args.ControlTowerLogArchiveAccount == nil { - return errors.New("must specify log archive account for CloudTrail Control Tower integration") - } - if args.ExistingCloudtrailBucketArn == "" { - return errors.New("must specify S3 bucket ARN for CloudTrail Control Tower integration") - } - if args.ExistingSnsTopicArn == "" { - return errors.New("must specify SNS topic ARN for CloudTrail Control Tower integration") - } - } - } - - if args.Cloudtrail { - if args.CloudtrailUseExistingTrail { - if args.CloudtrailName == "" { - return errors.New("must specify the existing trail name when integrating with existing CloudTrail") - } - if args.ExistingCloudtrailBucketArn == "" { - return errors.New( - fmt.Sprintf( - "must specify the S3 bucket ARN associated with the trail %s "+ - " when integrating with existing CloudTrail", args.CloudtrailName, - ), - ) - } - if args.ExistingSnsTopicArn == "" && !args.S3BucketNotification { - return errors.New( - fmt.Sprintf( - "must either specify the SNS topic ARN associated with the trail %s "+ - "or enable S3 notification when integrating with existing CloudTrail", args.CloudtrailName, - ), - ) - } - } - } - - return nil -} - -type AwsTerraformModifier func(c *GenerateAwsTfConfigurationArgs) - -type AwsGenerateCommandExtraState struct { - CloudtrailAdvanced bool - Output string - AwsSubAccounts []string - AgentlessMonitoredAccounts []string - AgentlessScanningAccounts []string - ControlTowerAuditAccount string - ControlTowerLogArchiveAccount string - TerraformApply bool -} - -func (a *AwsGenerateCommandExtraState) IsEmpty() bool { - return a.Output == "" && len(a.AwsSubAccounts) == 0 && !a.TerraformApply -} - -// NewTerraform returns an instance of the GenerateAwsTfConfigurationArgs struct with the provided region and enabled -// settings (config/cloudtrail). -// -// Note: Additional configuration details may be set using modifiers of the AwsTerraformModifier type -// -// Basic usage: Initialize a new AwsTerraformModifier struct, with a non-default AWS profile set. Then use generate to -// -// create a string output of the required HCL. -// -// hcl, err := aws.NewTerraform("us-east-1", true, true, -// aws.WithAwsProfile("mycorp-profile")).Generate() -func NewTerraform( - enableAwsOrganization bool, - enableAgentless bool, - enableConfig bool, - enableCloudtrail bool, - mods ...AwsTerraformModifier, -) *GenerateAwsTfConfigurationArgs { - config := &GenerateAwsTfConfigurationArgs{ - AwsOrganization: enableAwsOrganization, - Agentless: enableAgentless, - Cloudtrail: enableCloudtrail, - Config: enableConfig, - } - for _, m := range mods { - m(config) - } - return config -} - -// WithProviderDefaultTags adds default_tags to the provider configuration for AWS (if tags are present) -func WithProviderDefaultTags(tags map[string]interface{}) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ProviderDefaultTags = tags - } -} - -// WithAwsProfile Set the AWS Profile to utilize for the main AWS provider -func WithAwsProfile(name string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.AwsProfile = name - } -} - -// WithAwsRegion Set the AWS region to utilize for the main AWS provider -func WithAwsRegion(region string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.AwsRegion = region - } -} - -// WithAwsAssumeRole Set the AWS Assume Role to utilize for the main AWS provider -func WithAwsAssumeRole(assumeRole string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.AwsAssumeRole = assumeRole - } -} - -// WithLaceworkProfile Set the Lacework Profile to utilize when integrating -func WithLaceworkProfile(name string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.LaceworkProfile = name - } -} - -// WithLaceworkAccountID Set the Lacework AWS root account ID to use -func WithLaceworkAccountID(accountID string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.LaceworkAccountID = accountID - } -} - -// WithAgentlessManagementAccountID Set Agentless management account ID -func WithAgentlessManagementAccountID(accountID string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.AgentlessManagementAccountID = accountID - } -} - -// WithAgentlessMonitoredAccountIDs Set Agentless monitored account IDs -func WithAgentlessMonitoredAccountIDs(accountIDs []string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.AgentlessMonitoredAccountIDs = accountIDs - } -} - -// WithAgentlessMonitoredAccounts Set Agentless monitored accounts -func WithAgentlessMonitoredAccounts(accounts ...AwsSubAccount) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.AgentlessMonitoredAccounts = accounts - } -} - -// WithAgentlessScanningAccounts Set Agentless scanning accounts -func WithAgentlessScanningAccounts(accounts ...AwsSubAccount) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.AgentlessScanningAccounts = accounts - } -} - -// WithConfigAdditionalAccounts Set Config additional accounts -func WithConfigAdditionalAccounts(accounts ...AwsSubAccount) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ConfigAdditionalAccounts = accounts - } -} - -// WithConfigOrgLWAccount Set Config org LW account -func WithConfigOrgLWAccount(account string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ConfigOrgLWAccount = account - } -} - -// WithConfigOrgLWSubaccount Set Config org LW sub-account -func WithConfigOrgLWSubaccount(subaccount string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ConfigOrgLWSubaccount = subaccount - } -} - -// WithConfigOrgLWAccessKeyId Set Config org LW access key ID -func WithConfigOrgLWAccessKeyId(accessKeyId string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ConfigOrgLWAccessKeyId = accessKeyId - } -} - -// WithConfigOrgLWSecretKey Set Config org LW secret key -func WithConfigOrgLWSecretKey(secretKey string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ConfigOrgLWSecretKey = secretKey - } -} - -// WithConfigOrgId Set Config org ID -func WithConfigOrgId(orgId string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ConfigOrgId = orgId - } -} - -// WithConfigOrgUnits Set Config org units -func WithConfigOrgUnits(orgUnits []string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ConfigOrgUnits = orgUnits - } -} - -// WithConfigOutputs Set Custom Terraform Outputs -func WithCustomOutputs(outputs []lwgenerate.HclOutput) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.CustomOutputs = outputs - } -} - -// WithConfigOrgCfResourcePrefix Set Config org resource prefix -func WithConfigOrgCfResourcePrefix(resourcePrefix string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ConfigOrgCfResourcePrefix = resourcePrefix - } -} - -// WithControlTower Set ControlTower -func WithControlTower(controlTower bool) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ControlTower = controlTower - } -} - -// WithControlTowerAuditAccount Set ControlTower audit account -func WithControlTowerAuditAccount(auditAccount *AwsSubAccount) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ControlTowerAuditAccount = auditAccount - } -} - -// WithControlTowerLogArchiveAccount Set ControlTower log archive account -func WithControlTowerLogArchiveAccount(LogArchiveAccount *AwsSubAccount) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ControlTowerLogArchiveAccount = LogArchiveAccount - } -} - -// WithUseCloudTrailRandomName CloudTrail random name -func WithUseCloudTrailRandomName(useCloudTrailRandomName bool) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.UseCloudTrailRandomName = useCloudTrailRandomName - } -} - -// WithControlTowerKmsKeyArn Set ControlTower custom KMS key ARN -func WithControlTowerKmsKeyArn(kmsKeyArn string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ControlTowerKmsKeyArn = kmsKeyArn - } -} - -// WithCloudtrailUseExistingTrail Use the existing Cloudtrail S3 bucket -func WithCloudtrailUseExistingTrail(useExistingS3 bool) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.CloudtrailUseExistingTrail = useExistingS3 - } -} - -// WithCloudtrailUseExistingSNSTopic Use the existing Cloudtrail SNS topic -func WithCloudtrailUseExistingSNSTopic(useExistingSNSTopic bool) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.CloudtrailUseExistingSNSTopic = useExistingSNSTopic - } -} - -// WithExistingCloudtrailBucketArn Set the bucket ARN of an existing Cloudtrail setup -func WithExistingCloudtrailBucketArn(arn string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ExistingCloudtrailBucketArn = arn - } -} - -// WithExistingSnsTopicArn Set the SNS Topic ARN of an existing Cloudtrail setup -func WithExistingSnsTopicArn(arn string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ExistingSnsTopicArn = arn - } -} - -// WithConsolidatedCloudtrail Enable Consolidated Cloudtrail use -func WithConsolidatedCloudtrail(consolidatedCloudtrail bool) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ConsolidatedCloudtrail = consolidatedCloudtrail - } -} - -// WithExistingIamRole Set an existing IAM role configuration to use with the created Terraform code -func WithExistingIamRole(iamDetails *ExistingIamRoleDetails) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ExistingIamRole = iamDetails - } -} - -// WithSubaccounts Supply additional AWS Profiles to integrate -func WithSubaccounts(subaccounts ...AwsSubAccount) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.SubAccounts = subaccounts - } -} - -// WithCloudtrailName add optional name for CloudTrail integration -func WithCloudtrailName(cloudtrailName string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.CloudtrailName = cloudtrailName - } -} - -// WithOrgAccountMappings add optional name for Organization account mappings -// Sets lacework org level to true -func WithOrgAccountMappings(mapping OrgAccountMapping) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.OrgAccountMappings = mapping - if !mapping.IsEmpty() { - c.LaceworkOrganizationLevel = true - } - } -} - -// WithBucketName add bucket name for CloudTrail integration -func WithBucketName(bucketName string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.BucketName = bucketName - } -} - -// WithBucketEncryptionEnabled Enable encryption on a newly created bucket -func WithBucketEncryptionEnabled(enableBucketEncryption bool) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.BucketEncryptionEnabled = enableBucketEncryption - c.BucketEncryptionEnabledSet = true - } -} - -// WithBucketSSEKeyArn Set existing KMS encryption key arn for bucket -func WithBucketSSEKeyArn(bucketSseKeyArn string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.BucketSseKeyArn = bucketSseKeyArn - } -} - -// WithSnsTopicName Set SNS Topic Name if creating new one -func WithSnsTopicName(snsTopicName string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.SnsTopicName = snsTopicName - } -} - -// WithSnsTopicEncryptionEnabled Enable encryption on SNS Topic when created -func WithSnsTopicEncryptionEnabled(snsTopicEncryptionEnabled bool) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.SnsTopicEncryptionEnabled = snsTopicEncryptionEnabled - c.SnsEncryptionEnabledSet = true - } -} - -// WithSnsTopicEncryptionKeyArn Set existing KMS encryption key arn for SNS topic -func WithSnsTopicEncryptionKeyArn(snsTopicEncryptionKeyArn string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.SnsTopicEncryptionKeyArn = snsTopicEncryptionKeyArn - } -} - -// WithSqsQueueName Set SQS Queue Name if creating new one -func WithSqsQueueName(sqsQueueName string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.SqsQueueName = sqsQueueName - } -} - -// WithSqsEncryptionEnabled Enable encryption on SQS queue when created -func WithSqsEncryptionEnabled(sqsEncryptionEnabled bool) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.SqsEncryptionEnabled = sqsEncryptionEnabled - c.SqsEncryptionEnabledSet = true - } -} - -// WithSqsEncryptionKeyArn Set existing KMS encryption key arn for SQS queue -func WithSqsEncryptionKeyArn(ssqEncryptionKeyArn string) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.SqsEncryptionKeyArn = ssqEncryptionKeyArn - } -} - -func WithS3BucketNotification(s3BucketNotifiaction bool) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.S3BucketNotification = s3BucketNotifiaction - } -} - -// WithExtraRootBlocks allows adding generic hcl blocks to the root `terraform{}` block -// this enables custom use cases -func WithExtraRootBlocks(blocks []*hclwrite.Block) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ExtraBlocksRootTerraform = blocks - } -} - -// WithExtraProviderArguments enables adding additional arguments into the `aws` provider block -// this enables custom use cases -func WithExtraProviderArguments(arguments map[string]interface{}) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ExtraProviderArguments = arguments - } -} - -// WithExtraBlocks enables adding additional arbitrary blocks to the root hcl document -func WithExtraBlocks(blocks []*hclwrite.Block) AwsTerraformModifier { - return func(c *GenerateAwsTfConfigurationArgs) { - c.ExtraBlocks = blocks - } -} - -// Generate new Terraform code based on the supplied args. -func (args *GenerateAwsTfConfigurationArgs) Generate() (string, error) { - // Validate inputs - if err := args.Validate(); err != nil { - return "", errors.Wrap(err, "invalid inputs") - } - - // Create blocks - requiredProviders, err := createRequiredProviders(args.ExtraBlocksRootTerraform) - if err != nil { - return "", errors.Wrap(err, "failed to generate required providers") - } - - awsProvider, err := createAwsProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate aws provider") - } - - laceworkProvider, err := createLaceworkProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate lacework provider") - } - - configModule, err := createConfig(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate aws config module") - } - - cloudTrailModule, err := createCloudtrail(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate aws cloudtrail module") - } - - agentlessModule, err := createAgentless(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate aws agentless global module") - } - - outputBlocks := []*hclwrite.Block{} - for _, output := range args.CustomOutputs { - outputBlock, err := output.ToBlock() - if err != nil { - return "", errors.Wrap(err, "failed to add custom output") - } - outputBlocks = append(outputBlocks, outputBlock) - } - - // Render - hclBlocks := lwgenerate.CreateHclStringOutput( - lwgenerate.CombineHclBlocks( - requiredProviders, - awsProvider, - laceworkProvider, - configModule, - cloudTrailModule, - agentlessModule, - outputBlocks, - args.ExtraBlocks, - ), - ) - return hclBlocks, nil -} - -func createRequiredProviders(extraBlocks []*hclwrite.Block) (*hclwrite.Block, error) { - return lwgenerate.CreateRequiredProvidersWithCustomBlocks( - extraBlocks, - lwgenerate.NewRequiredProvider("lacework", - lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), - lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion))) -} - -func createAwsProvider(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, error) { - blocks := []*hclwrite.Block{} - - attributes := map[string]interface{}{} - - // set custom args before the required ones below to ensure expected behavior (i.e., no overrides) - for k, v := range args.ExtraProviderArguments { - attributes[k] = v - } - - // required defaults - attributes["alias"] = "main" - attributes["region"] = args.AwsRegion - if args.AwsProfile != "" { - attributes["profile"] = args.AwsProfile - } - - modifiers := []lwgenerate.HclProviderModifier{ - lwgenerate.HclProviderWithAttributes(attributes), - } - - if len(args.ProviderDefaultTags) != 0 { - defaultTagsBlock, err := lwgenerate.HclCreateGenericBlock( - "default_tags", - nil, - map[string]interface{}{"tags": args.ProviderDefaultTags}, - ) - if err != nil { - return nil, err - } - modifiers = append(modifiers, lwgenerate.HclProviderWithGenericBlocks(defaultTagsBlock)) - } - - if args.AwsAssumeRole != "" { - assumeRoleBlock, err := lwgenerate.HclCreateGenericBlock( - "assume_role", - nil, - map[string]interface{}{"role_arn": args.AwsAssumeRole}, - ) - if err != nil { - return nil, err - } - modifiers = append(modifiers, lwgenerate.HclProviderWithGenericBlocks(assumeRoleBlock)) - } - - provider, err := lwgenerate.NewProvider("aws", modifiers...).ToBlock() - if err != nil { - return nil, err - } - - blocks = append(blocks, provider) - - accounts := []AwsSubAccount{} - accounts = append(accounts, args.AgentlessMonitoredAccounts...) - accounts = append(accounts, args.AgentlessScanningAccounts...) - accounts = append(accounts, args.ConfigAdditionalAccounts...) - if args.ControlTower { - accounts = append(accounts, *args.ControlTowerAuditAccount) - accounts = append(accounts, *args.ControlTowerLogArchiveAccount) - } - seenAccounts := []string{} - - for _, account := range accounts { - alias := account.AwsRegion - if account.Alias != "" { - alias = account.Alias - } else if account.AwsProfile != "" { - alias = fmt.Sprintf("%s-%s", account.AwsProfile, account.AwsRegion) - } - // Skip duplicate account - if slices.Contains(seenAccounts, alias) { - continue - } - seenAccounts = append(seenAccounts, alias) - - attributes := map[string]interface{}{} - // set `access_key`, `secret_key` and `token` for single-account multiple-region Agentless - if args.Agentless { - for k, v := range args.ExtraProviderArguments { - attributes[k] = v - } - } - attributes["alias"] = alias - attributes["region"] = account.AwsRegion - if args.AwsProfile != "" { - attributes["profile"] = account.AwsProfile - } - - providerBlock, err := lwgenerate.NewProvider( - "aws", - lwgenerate.HclProviderWithAttributes(attributes), - ).ToBlock() - if err != nil { - return nil, err - } - - blocks = append(blocks, providerBlock) - } - - return blocks, nil -} - -func createLaceworkProvider(args *GenerateAwsTfConfigurationArgs) (*hclwrite.Block, error) { - lwProviderAttributes := map[string]any{} - - if args.LaceworkProfile != "" { - lwProviderAttributes["profile"] = args.LaceworkProfile - } - - if args.LaceworkOrganizationLevel { - lwProviderAttributes["organization"] = true - } - - if len(lwProviderAttributes) > 0 { - return lwgenerate.NewProvider( - "lacework", lwgenerate.HclProviderWithAttributes(lwProviderAttributes), - ).ToBlock() - } - return nil, nil -} - -func createConfig(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, error) { - if !args.Config { - return nil, nil - } - - blocks := []*hclwrite.Block{} - - if args.AwsOrganization { - block, err := lwgenerate.NewModule( - "aws_org_configuration", - lwgenerate.AwsConfigOrgSource, - lwgenerate.HclModuleWithVersion(lwgenerate.AwsConfigOrgVersion), - lwgenerate.HclModuleWithProviderDetails(map[string]string{"aws": "aws.main"}), - lwgenerate.HclModuleWithAttributes( - map[string]interface{}{ - "lacework_account": args.ConfigOrgLWAccount, - "lacework_subaccount": args.ConfigOrgLWSubaccount, - "lacework_access_key_id": args.ConfigOrgLWAccessKeyId, - "lacework_secret_key": args.ConfigOrgLWSecretKey, - "organization_id": args.ConfigOrgId, - "organization_unit": args.ConfigOrgUnits, - "cf_resource_prefix": args.ConfigOrgCfResourcePrefix, - }, - ), - ).ToBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, block) - return blocks, nil - } - - moduleDetails := []lwgenerate.HclModuleModifier{ - lwgenerate.HclModuleWithVersion(lwgenerate.AwsConfigVersion), - lwgenerate.HclModuleWithProviderDetails(map[string]string{"aws": "aws.main"}), - } - if args.LaceworkAccountID != "" { - moduleDetails = append( - moduleDetails, - lwgenerate.HclModuleWithAttributes( - map[string]interface{}{"lacework_aws_account_id": args.LaceworkAccountID}, - ), - ) - } - - moduleBlock, err := lwgenerate.NewModule( - "aws_config", - lwgenerate.AwsConfigSource, - moduleDetails..., - ).ToBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, moduleBlock) - - for _, account := range args.ConfigAdditionalAccounts { - configModule, err := lwgenerate.NewModule( - fmt.Sprintf("aws_config_%s", account.Alias), - lwgenerate.AwsConfigSource, - lwgenerate.HclModuleWithVersion(lwgenerate.AwsConfigVersion), - lwgenerate.HclModuleWithProviderDetails(map[string]string{ - "aws": fmt.Sprintf("aws.%s", account.Alias), - }), - ).ToBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, configModule) - } - - return blocks, nil -} - -func createCloudtrail(args *GenerateAwsTfConfigurationArgs) (*hclwrite.Block, error) { - if !args.Cloudtrail { - return nil, nil - } - - source := lwgenerate.AwsCloudTrailSource - version := lwgenerate.AwsCloudTrailVersion - if args.ControlTower { - source = lwgenerate.AwsCloudTrailControlTowerSource - version = lwgenerate.AwsCloudTrailControlTowerVersion - } - attributes := map[string]interface{}{} - providerDetails := map[string]string{"aws": "aws.main"} - - if args.LaceworkAccountID != "" { - attributes["lacework_aws_account_id"] = args.LaceworkAccountID - } - - if args.AwsOrganization && args.ControlTower { - attributes["s3_bucket_arn"] = args.ExistingCloudtrailBucketArn - attributes["sns_topic_arn"] = args.ExistingSnsTopicArn - if args.ControlTowerKmsKeyArn != "" { - attributes["kms_key_arn"] = args.ControlTowerKmsKeyArn - } - providerDetails = map[string]string{ - "aws.audit": fmt.Sprintf("aws.%s", args.ControlTowerAuditAccount.Alias), - "aws.log_archive": fmt.Sprintf("aws.%s", args.ControlTowerLogArchiveAccount.Alias), - } - } else { - if args.ConsolidatedCloudtrail { - attributes["consolidated_trail"] = true - } - - if args.UseCloudTrailRandomName { - uid := uuid.New().String()[:8] - attributes["cloudtrail_name"] = fmt.Sprintf("lacework-cloudtrail-%s", uid) - } - - // S3 Bucket attributes - if args.CloudtrailUseExistingTrail { - attributes["use_existing_cloudtrail"] = true - attributes["cloudtrail_name"] = args.CloudtrailName - attributes["bucket_arn"] = args.ExistingCloudtrailBucketArn - } else { - if args.BucketName != "" { - attributes["bucket_name"] = args.BucketName - } - } - if args.BucketEncryptionEnabledSet { - if args.BucketEncryptionEnabled { - if args.BucketSseKeyArn != "" { - attributes["bucket_sse_key_arn"] = args.BucketSseKeyArn - } - } else { - attributes["bucket_encryption_enabled"] = false - } - } - if args.S3BucketNotification { - attributes["use_s3_bucket_notification"] = true - } - // SNS Attributes - if args.CloudtrailUseExistingSNSTopic { - attributes["use_existing_sns_topic"] = true - if args.ExistingSnsTopicArn != "" { - attributes["sns_topic_arn"] = args.ExistingSnsTopicArn - } - } else { - if args.SnsTopicName != "" { - attributes["sns_topic_name"] = args.SnsTopicName - } - if args.SnsEncryptionEnabledSet { - if args.SnsTopicEncryptionEnabled { - if args.SnsTopicEncryptionKeyArn != "" { - attributes["sns_topic_encryption_key_arn"] = args.SnsTopicEncryptionKeyArn - } - } else { - attributes["sns_topic_encryption_enabled "] = false - } - } - } - // SQS Attributes - if args.SqsQueueName != "" { - attributes["sqs_queue_name"] = args.SqsQueueName - } - if args.SqsEncryptionEnabledSet { - if args.SqsEncryptionEnabled { - if args.SqsEncryptionKeyArn != "" { - attributes["sqs_encryption_key_arn"] = args.SqsEncryptionKeyArn - } - } else { - attributes["sqs_encryption_enabled "] = false - } - } - } - - // Aws Organization CloudTrail - if args.AwsOrganization { - if !args.ControlTower { - attributes["is_organization_trail"] = true - } - if !args.OrgAccountMappings.IsEmpty() { - orgAccountMappings, err := args.OrgAccountMappings.ToMap() - if err != nil { - return nil, errors.Wrap(err, "unable to parse 'org_account_mappings'") - } - attributes["org_account_mappings"] = []map[string]any{orgAccountMappings} - } - } - - if args.ExistingIamRole.IsEmpty() && args.Config && !args.AwsOrganization { - attributes["use_existing_iam_role"] = true - attributes["iam_role_name"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "aws_config", "iam_role_name"}) - attributes["iam_role_arn"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "aws_config", "iam_role_arn"}) - attributes["iam_role_external_id"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "aws_config", "external_id"}) - } - - if !args.ExistingIamRole.IsEmpty() { - attributes["use_existing_iam_role"] = true - attributes["iam_role_name"] = args.ExistingIamRole.Name - attributes["iam_role_arn"] = args.ExistingIamRole.Arn - attributes["iam_role_external_id"] = args.ExistingIamRole.ExternalId - } - - return lwgenerate.NewModule( - "main_cloudtrail", - source, - lwgenerate.HclModuleWithVersion(version), - lwgenerate.HclModuleWithProviderDetails(providerDetails), - lwgenerate.HclModuleWithAttributes(attributes), - ).ToBlock() -} - -func createAgentless(args *GenerateAwsTfConfigurationArgs) ([]*hclwrite.Block, error) { - if !args.Agentless { - return nil, nil - } - - blocks := []*hclwrite.Block{} - - if args.AwsOrganization { - // Create Agenetless integration for organization - - // Add management module - managementModule, err := lwgenerate.NewModule( - "lacework_aws_agentless_management_scanning_role", - lwgenerate.AwsAgentlessSource, - lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), - lwgenerate.HclModuleWithProviderDetails(map[string]string{ - "aws": "aws.main", - }), - lwgenerate.HclModuleWithAttributes(map[string]interface{}{ - "snapshot_role": true, - "global_module_reference": lwgenerate.CreateSimpleTraversal( - []string{"module", "lacework_aws_agentless_scanning_global"}, - ), - }), - ).ToBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, managementModule) - - // Add global scanning module - monitoredAccountIDs := []string{} - for _, accountID := range args.AgentlessMonitoredAccountIDs { - monitoredAccountIDs = append(monitoredAccountIDs, fmt.Sprintf("\"%s\"", accountID)) - } - globalModule, err := lwgenerate.NewModule( - "lacework_aws_agentless_scanning_global", - lwgenerate.AwsAgentlessSource, - lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), - lwgenerate.HclModuleWithAttributes(map[string]interface{}{ - "global": true, - "regional": true, - "organization": lwgenerate.CreateMapTraversalTokens(map[string]string{ - "management_account": fmt.Sprintf("\"%s\"", args.AgentlessManagementAccountID), - "monitored_accounts": fmt.Sprintf("[%s]", strings.Join(monitoredAccountIDs, ", ")), - }), - }), - lwgenerate.HclModuleWithProviderDetails( - map[string]string{"aws": fmt.Sprintf("aws.%s", args.AgentlessScanningAccounts[0].Alias)}, - ), - ).ToBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, globalModule) - - // Add regional scanning modules - for _, account := range args.AgentlessScanningAccounts[1:] { - regionModule, err := lwgenerate.NewModule( - fmt.Sprintf("lacework_aws_agentless_scanning_region_%s", account.Alias), - lwgenerate.AwsAgentlessSource, - lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), - lwgenerate.HclModuleWithProviderDetails(map[string]string{ - "aws": fmt.Sprintf("aws.%s", account.Alias), - }), - lwgenerate.HclModuleWithAttributes( - map[string]interface{}{ - "regional": true, - "global_module_reference": lwgenerate.CreateSimpleTraversal( - []string{"module", "lacework_aws_agentless_scanning_global"}, - ), - }, - ), - ).ToBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, regionModule) - } - - // Add monitored modules - for _, monitoredAccount := range args.AgentlessMonitoredAccounts { - monitoredModule, err := lwgenerate.NewModule( - fmt.Sprintf("lacework_aws_agentless_monitored_scanning_role_%s", monitoredAccount.Alias), - lwgenerate.AwsAgentlessSource, - lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), - lwgenerate.HclModuleWithProviderDetails(map[string]string{ - "aws": fmt.Sprintf("aws.%s", monitoredAccount.Alias), - }), - lwgenerate.HclModuleWithAttributes( - map[string]interface{}{ - "snapshot_role": true, - "global_module_reference": lwgenerate.CreateSimpleTraversal( - []string{"module", "lacework_aws_agentless_scanning_global"}, - ), - }, - ), - ).ToBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, monitoredModule) - } - - autoDeploymentBlock, err := lwgenerate.HclCreateGenericBlock( - "auto_deployment", - nil, - map[string]interface{}{"enabled": true, "retain_stacks_on_account_removal": false}, - ) - if err != nil { - return nil, err - } - lifecycleBlock, err := lwgenerate.HclCreateGenericBlock( - "lifecycle", - nil, - map[string]interface{}{ - "ignore_changes": lwgenerate.CreateSimpleTraversal([]string{"[administration_role_arn]"}), - }, - ) - if err != nil { - return nil, err - } - - stacksetResource, err := lwgenerate.NewResource( - "aws_cloudformation_stack_set", - "snapshot_role", - lwgenerate.HclResourceWithAttributesAndProviderDetails( - map[string]interface{}{ - "capabilities": lwgenerate.CreateSimpleTraversal([]string{"[\"CAPABILITY_NAMED_IAM\"]"}), - "description": "Lacework AWS Agentless Workload Scanning Organization Roles", - "name": "lacework-agentless-scanning-stackset", - "permission_model": "SERVICE_MANAGED", - "template_url": "https://agentless-workload-scanner.s3.amazonaws.com" + - "/cloudformation-lacework/latest/snapshot-role.json", - "parameters": lwgenerate.CreateMapTraversalTokens(map[string]string{ - "ExternalId": "module.lacework_aws_agentless_scanning_global.external_id", - "ECSTaskRoleArn": "module.lacework_aws_agentless_scanning_global.agentless_scan_ecs_task_role_arn", - "ResourceNamePrefix": "module.lacework_aws_agentless_scanning_global.prefix", - "ResourceNameSuffix": "module.lacework_aws_agentless_scanning_global.suffix", - }), - }, - []string{"aws.main"}, - ), - lwgenerate.HclResourceWithGenericBlocks(autoDeploymentBlock, lifecycleBlock), - ).ToResourceBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, stacksetResource) - - // Get OU IDs for the organizational_unit_ids attribute - OUIDs := []string{} - for _, accountID := range args.AgentlessMonitoredAccountIDs { - if strings.HasPrefix(accountID, "ou-") || strings.HasPrefix(accountID, "r-") { - OUIDs = append(OUIDs, fmt.Sprintf("\"%s\"", accountID)) - } - } - - deploymentTargetsBlock, err := lwgenerate.HclCreateGenericBlock( - "deployment_targets", - nil, - map[string]interface{}{"organizational_unit_ids": lwgenerate.CreateSimpleTraversal( - []string{fmt.Sprintf("[%s]", strings.Join(OUIDs, ","))}, - )}, - ) - if err != nil { - return nil, err - } - stacksetInstanceResource, err := lwgenerate.NewResource( - "aws_cloudformation_stack_set_instance", - "snapshot_role", - lwgenerate.HclResourceWithAttributesAndProviderDetails( - map[string]interface{}{ - "stack_set_name": lwgenerate.CreateSimpleTraversal( - []string{"aws_cloudformation_stack_set", "snapshot_role", "name"}, - ), - }, - []string{"aws.main"}, - ), - lwgenerate.HclResourceWithGenericBlocks(deploymentTargetsBlock), - ).ToResourceBlock() - - if err != nil { - return nil, err - } - - blocks = append(blocks, stacksetInstanceResource) - } else { - // Create Agenetless integration for single account - globalModule, err := lwgenerate.NewModule( - "lacework_aws_agentless_scanning_global", - lwgenerate.AwsAgentlessSource, - lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), - lwgenerate.HclModuleWithAttributes(map[string]interface{}{"global": true, "regional": true}), - lwgenerate.HclModuleWithProviderDetails( - map[string]string{"aws": "aws.main"}, - ), - ).ToBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, globalModule) - - for _, account := range args.AgentlessScanningAccounts { - regionModule, err := lwgenerate.NewModule( - fmt.Sprintf("lacework_aws_agentless_scanning_region_%s", account.Alias), - lwgenerate.AwsAgentlessSource, - lwgenerate.HclModuleWithVersion(lwgenerate.AwsAgentlessVersion), - lwgenerate.HclModuleWithProviderDetails(map[string]string{ - "aws": fmt.Sprintf("aws.%s", account.Alias), - }), - lwgenerate.HclModuleWithAttributes( - map[string]interface{}{ - "regional": true, - "global_module_reference": lwgenerate.CreateSimpleTraversal( - []string{"module", "lacework_aws_agentless_scanning_global"}, - ), - }, - ), - ).ToBlock() - if err != nil { - return nil, err - } - blocks = append(blocks, regionModule) - } - } - - return blocks, nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/aws_controltower/aws_controltower.go b/vendor/github.com/lacework/go-sdk/lwgenerate/aws_controltower/aws_controltower.go deleted file mode 100644 index 9a91b3f3a..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/aws_controltower/aws_controltower.go +++ /dev/null @@ -1,477 +0,0 @@ -package aws_controltower - -import ( - "encoding/json" - - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/lacework/go-sdk/lwgenerate" - "github.com/pkg/errors" -) - -type GenerateAwsControlTowerTfConfigurationArgs struct { - // For AWS Subaccounts in consolidated CT setups - SubAccounts []AwsSubAccount - - // ARN for the S3 bucket for consolidated CloudTrail logging - S3BucketArn string - - // The SNS topic ARN - SNSTopicArn string - - // The Aws profile of the log archive account - LogArchiveProfile string - - // The Aws region of the log archive account - LogArchiveRegion string - - // The Aws profile of the audit account - AuditProfile string - - // The Aws region of the audit account - AuditRegion string - - // The audit account flag input in the format profile:region - AuditAccount string - - // The log archive account flag input in the format profile:region - LogArchiveAccount string - - // A name for the cross account policy - CrossAccountPolicyName string - - // Whether cloudtrail log file integrity validation is enabled - EnableLogFileValidation bool - - // The length of the external ID to generate. Max length is 1224. Ignored when use_existing_iam_role is set to true - ExternalIdLength int - - // The IAM role ARN is required when setting use_existing_iam_role to true - IamRoleArn string - - // The external ID configured inside the IAM role is required when setting use_existing_iam_role to true - IamRoleExternalID string - - // The IAM role name. Required to match with iam_role_arn if use_existing_iam_role is set to true - IamRoleName string - - //The Lacework AWS account that the IAM role will grant access - LaceworkAwsAccountID string - - // The name of the integration in Lacework. - LaceworkIntegrationName string - - // The prefix that will be used at the beginning of every generated resource - Prefix string - - // The SQS queue name - SqsQueueName string - - // A map/dictionary of Tags to be assigned to created resources - Tags map[string]string - - // Set this to true to use an existing IAM role from the log_archive AWS Account - UseExistingIamRole bool - - // Amount of time to wait before the next resource is provisioned - WaitTime int - - // The KMS key arn, if Control Tower was deployed with custom KMS key - KmsKeyArn string - - // Mapping of AWS accounts to Lacework accounts within a Lacework organization - OrgAccountMappings OrgAccountMapping - - // OrgAccountMapping json used for flag input - OrgAccountMappingsJson string - - // Lacework Profile to use - LaceworkProfile string - - // Lacework Organization - LaceworkOrganizationLevel bool - - // The Lacework AWS Root Account ID - LaceworkAccountID string -} - -type OrgAccountMapping struct { - DefaultLaceworkAccount string `json:"default_lacework_account"` - Mapping []OrgAccountMap `json:"mapping"` -} - -type OrgAccountMap struct { - LaceworkAccount string `json:"lacework_account"` - AwsAccounts []string `json:"aws_accounts"` -} - -func (args GenerateAwsControlTowerTfConfigurationArgs) GetSubAccounts() []AwsSubAccount { - return args.SubAccounts -} - -func (args *GenerateAwsControlTowerTfConfigurationArgs) GetLaceworkProfile() string { - return args.LaceworkProfile -} - -func (args *GenerateAwsControlTowerTfConfigurationArgs) Generate() (string, error) { - // Validate inputs - if err := args.validate(); err != nil { - return "", errors.Wrap(err, "invalid inputs") - } - - // Create blocks - requiredProviders, err := createRequiredProviders() - if err != nil { - return "", errors.Wrap(err, "failed to generate required providers") - } - - awsProvider, err := createAwsProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate aws provider") - } - - laceworkProvider, err := createLaceworkProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate lacework provider") - } - - // controlTower - controlTowerModule, err := createCloudTrailControlTower(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate aws controlTower module") - } - - // Render - hclBlocks := lwgenerate.CreateHclStringOutput( - lwgenerate.CombineHclBlocks( - requiredProviders, - awsProvider, - laceworkProvider, - controlTowerModule, - ), - ) - return hclBlocks, nil -} - -type AwsSubAccount struct { - // The name of the AwsProfile to use (in AWS configuration) - AwsProfile string - - // The AwsRegion this profile should use if any resources are created - AwsRegion string - - // The Alias of the provider block - Alias string -} - -func NewAwsSubAccount(profile string, region string, alias ...string) AwsSubAccount { - subaccount := AwsSubAccount{AwsProfile: profile, AwsRegion: region} - if len(alias) > 0 { - subaccount.Alias = alias[0] - } - return subaccount -} - -func (args GenerateAwsControlTowerTfConfigurationArgs) validate() error { - // Validate s3 bucket arn has been set - if args.S3BucketArn == "" { - return errors.New("s3 bucket arn must be set") - } - // Validate sns topic arn has been set - if args.SNSTopicArn == "" { - return errors.New("sns topic arn must be set") - } - // Validate log and audit accounts archive - if len(args.SubAccounts) == 0 { - return errors.New("log archive and audit accounts must be set") - } - - // Validate existing role IAM values, if set - if args.UseExistingIamRole { - if args.IamRoleArn == "" || - args.IamRoleName == "" || - args.IamRoleExternalID == "" { - return errors.New("when using an existing IAM role, existing role ARN, name, and external ID all must be set") - } - } - - return nil -} - -type AwsControlTowerTerraformModifier func(c *GenerateAwsControlTowerTfConfigurationArgs) - -// NewTerraform returns an instance of the GenerateAwsControlTowerTfConfigurationArgs struct. -// -// Note: Additional configuration details may be set using modifiers of the AwsControlTowerTerraformModifier type -// -// Basic usage: Initialize a new AwsControlTowerTerraformModifier struct, with a non-default AWS profile set. Then -// use generate to create a string output of the required HCL. -// -// hcl, err := aws_controltower.NewTerraform("us-east-1") -// .WithAwsProfile("mycorp-profile")).Generate() -func NewTerraform(s3BucketArn string, snsTopicArn string, - mods ...AwsControlTowerTerraformModifier) *GenerateAwsControlTowerTfConfigurationArgs { - config := &GenerateAwsControlTowerTfConfigurationArgs{ - S3BucketArn: s3BucketArn, - SNSTopicArn: snsTopicArn, - } - for _, m := range mods { - m(config) - } - return config -} - -func WithCrossAccountPolicyName(name string) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.CrossAccountPolicyName = name - } -} - -func WithLaceworkAccountID(account string) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.LaceworkAccountID = account - } -} - -func WithLaceworkProfile(profile string) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.LaceworkProfile = profile - } -} - -func WithEnableLogFileValidation() AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.EnableLogFileValidation = true - } -} - -func WithExternalIdLength(length int) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.ExternalIdLength = length - } -} - -func WithWaitTime(waitTime int) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.WaitTime = waitTime - } -} - -func WithTags(tags map[string]string) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.Tags = tags - } -} - -func WithKmsKeyArn(arn string) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.KmsKeyArn = arn - } -} - -func WithExisitingIamRole(arn string, name string, externalID string) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.IamRoleArn = arn - c.IamRoleExternalID = externalID - c.IamRoleName = name - c.UseExistingIamRole = true - } -} - -func WithPrefix(prefix string) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.Prefix = prefix - } -} - -func WithSqsQueueName(name string) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.SqsQueueName = name - } -} - -func WithOrgAccountMappings(mapping OrgAccountMapping) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.OrgAccountMappings = mapping - } -} - -func WithLaceworkIntegrationName(name string) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.LaceworkIntegrationName = name - } -} - -func WithLaceworkOrgLevel() AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.LaceworkOrganizationLevel = true - } -} - -func WithSubaccounts(subaccounts ...AwsSubAccount) AwsControlTowerTerraformModifier { - return func(c *GenerateAwsControlTowerTfConfigurationArgs) { - c.SubAccounts = subaccounts - } -} - -func createCloudTrailControlTower(args *GenerateAwsControlTowerTfConfigurationArgs) (*hclwrite.Block, error) { - attributes := map[string]interface{}{} - modDetails := []lwgenerate.HclModuleModifier{ - lwgenerate.HclModuleWithVersion(lwgenerate.AwsCloudTrailControlTowerVersion)} - - //required args - if args.S3BucketArn != "" { - attributes["s3_bucket_arn"] = args.S3BucketArn - } - if args.SNSTopicArn != "" { - attributes["sns_topic_arn"] = args.SNSTopicArn - } - - // optional args - if args.LaceworkAccountID != "" { - attributes["lacework_aws_account_id"] = args.LaceworkAccountID - } - - if args.CrossAccountPolicyName != "" { - attributes["cross_account_policy_name"] = args.CrossAccountPolicyName - } - if args.EnableLogFileValidation { - attributes["enable_log_file_validation"] = args.EnableLogFileValidation - } - if args.ExternalIdLength != 0 { - attributes["external_id_length"] = args.ExternalIdLength - } - if args.WaitTime != 0 { - attributes["wait_time"] = args.WaitTime - } - if len(args.Tags) != 0 { - attributes["tags"] = args.Tags - } - if args.SqsQueueName != "" { - attributes["sqs_queue_name"] = args.SqsQueueName - } - if args.Prefix != "" { - attributes["prefix"] = args.Prefix - } - if !args.OrgAccountMappings.IsEmpty() { - orgAccountMappings, err := args.OrgAccountMappings.ToMap() - if err != nil { - return nil, errors.Wrap(err, "unable to parse 'org_account_mappings'") - } - attributes["org_account_mappings"] = []map[string]any{orgAccountMappings} - } - if args.LaceworkIntegrationName != "" { - attributes["lacework_integration_name"] = args.LaceworkIntegrationName - } - if args.KmsKeyArn != "" { - attributes["kms_key_arn"] = args.KmsKeyArn - } - - // existing iam role - if args.UseExistingIamRole { - attributes["use_existing_iam_role"] = true - attributes["iam_role_arn"] = args.IamRoleArn - attributes["iam_role_name"] = args.IamRoleName - attributes["iam_role_external_id"] = args.IamRoleExternalID - } - - modDetails = append(modDetails, lwgenerate.HclModuleWithProviderDetails( - map[string]string{"aws.audit": "aws.audit", "aws.log_archive": "aws.log_archive"})) - - modDetails = append(modDetails, - lwgenerate.HclModuleWithAttributes(attributes), - ) - - return lwgenerate.NewModule( - "lacework_aws_controltower", - lwgenerate.AwsCloudTrailControlTowerSource, - modDetails..., - ).ToBlock() - -} - -func createRequiredProviders() (*hclwrite.Block, error) { - return lwgenerate.CreateRequiredProviders( - lwgenerate.NewRequiredProvider("lacework", - lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), - lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion))) -} - -func createLaceworkProvider(args *GenerateAwsControlTowerTfConfigurationArgs) (*hclwrite.Block, error) { - lwProviderAttributes := map[string]any{} - - if args.LaceworkProfile != "" { - lwProviderAttributes["profile"] = args.LaceworkProfile - } - - if args.LaceworkOrganizationLevel { - lwProviderAttributes["organization"] = true - } - - if len(lwProviderAttributes) > 0 { - return lwgenerate.NewProvider( - "lacework", lwgenerate.HclProviderWithAttributes(lwProviderAttributes)).ToBlock() - } - - return nil, nil -} - -func createAwsProvider(args *GenerateAwsControlTowerTfConfigurationArgs) ([]*hclwrite.Block, error) { - var blocks []*hclwrite.Block - if len(args.SubAccounts) > 0 { - for _, subaccount := range args.SubAccounts { - alias := subaccount.AwsProfile - if subaccount.Alias != "" { - alias = subaccount.Alias - } - attrs := map[string]interface{}{ - "alias": alias, - "profile": subaccount.AwsProfile, - "region": subaccount.AwsRegion, - } - providerBlock, err := lwgenerate.NewProvider( - "aws", - lwgenerate.HclProviderWithAttributes(attrs), - ).ToBlock() - - if err != nil { - return nil, err - } - - blocks = append(blocks, providerBlock) - } - } - - return blocks, nil -} - -func (orgMap *OrgAccountMapping) IsEmpty() bool { - return len(orgMap.Mapping) == 0 && orgMap.DefaultLaceworkAccount == "" -} - -func (orgMap *OrgAccountMapping) ToMap() (map[string]any, error) { - var mappings []map[string]any - mappingsJsonString, err := json.Marshal(orgMap.Mapping) - if err != nil { - return nil, err - } - err = json.Unmarshal(mappingsJsonString, &mappings) - if err != nil { - return nil, err - } - - orgMap.Mapping = []OrgAccountMap{} - - result := make(map[string]any) - jsonString, err := json.Marshal(orgMap) - if err != nil { - return nil, err - } - - err = json.Unmarshal(jsonString, &result) - if err != nil { - return nil, err - } - - result["mapping"] = mappings - return result, nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/aws_eks_audit/aws_eks_audit.go b/vendor/github.com/lacework/go-sdk/lwgenerate/aws_eks_audit/aws_eks_audit.go deleted file mode 100644 index b9a0cf384..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/aws_eks_audit/aws_eks_audit.go +++ /dev/null @@ -1,697 +0,0 @@ -package aws_eks_audit - -import ( - "fmt" - "sort" - - "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/lacework/go-sdk/lwgenerate" - "github.com/pkg/errors" - "github.com/zclconf/go-cty/cty" -) - -type ExistingCrossAccountIamRoleDetails struct { - // Existing IAM Role ARN - Arn string - - // Existing IAM Role External ID - ExternalId string -} - -func (e *ExistingCrossAccountIamRoleDetails) IsPartial() bool { - // If nil, return false - if e == nil { - return false - } - - // If all values are empty, return false - if e.Arn == "" && e.ExternalId == "" { - return false - } - - // If all values are populated, return false - if e.Arn != "" && e.ExternalId != "" { - return false - } - - return true -} - -// NewExistingCrossAccountIamRoleDetails Create new existing IAM role details -func NewExistingCrossAccountIamRoleDetails(arn string, externalId string) *ExistingCrossAccountIamRoleDetails { - return &ExistingCrossAccountIamRoleDetails{ - Arn: arn, - ExternalId: externalId, - } -} - -type GenerateAwsEksAuditTfConfigurationArgs struct { - - // Use Existing Required Providers - // disable writing required_providers block - UseExistingRequiredProviders bool - - // Add prefix to provider alias names - ProviderAliasPrefix string - - // Supply an AWS Profile name - AwsProfile string - - // Should we require MFA for object deletion? - BucketEnableMfaDelete bool - - // Should we enable bucket encryption? - BucketEnableEncryption bool - - // Should we force destroy the bucket if it has stuff in it? - // DEPRECATED - BucketForceDestroy bool - - // The lifetime, in days, of the bucket objects. The value must be a non-zero positive integer - BucketLifecycleExpirationDays int - - // The encryption algorithm to use for S3 bucket server-side encryption - BucketSseAlgorithm string - - // Should we use an existing KMS key for the bucket? - ExistingBucketKmsKey bool - - // The ARN of the KMS encryption key to be used for S3 - // (Required when bucket_sse_algorithm is aws:kms and using an existing kms key) - BucketSseKeyArn string - - // Should we enable bucket versioning? - BucketVersioning bool - - // The name of the AWS EKS Audit Log integration in Lacework. Defaults to "TF AWS EKS Audit Log" - EksAuditIntegrationName string - - // Optionally supply existing cloudwatch IAM role ARN - ExistingCloudWatchIamRoleArn string - - // Optionally supply existing cross account IAM role details - ExistingCrossAccountIamRole *ExistingCrossAccountIamRoleDetails - - // Should we allow the user to configure an existing Firehose IAM role? - ExistingFirehoseIam bool - - // Optionally supply existing firehose role ARN if ExistingFirehoseIam is true - ExistingFirehoseIamRoleArn string - - // The Cloudwatch Log Subscription Filter pattern - FilterPattern string - - // Should encryption be enabled on the created firehose? Defaults to true. - FirehoseEncryptionEnabled bool - - // The ARN of an existing KMS encryption key to be used for the Kinesis Firehose - FirehoseEncryptionKeyArn string - - // The waiting period, specified in number of days. Defaults to 30. - KmsKeyDeletionDays int - - // Whether the KMS key is a multi-region or regional key - KmsKeyMultiRegion bool - - // Enable KMS automatic key rotation - KmsKeyRotation bool - - // The prefix that will be used at the beginning of every generated resource. Defaults to "lw-eks-al" - Prefix string - - // Parsed version of RegionClusterMap - RegionClusterMap map[string]string - - // Parsed version of RegionClusterMap - ParsedRegionClusterMap map[string][]string - - // Parsed Regions list - ParsedRegionsList []string - - // Should encryption be enabled for the sns topic? Defaults to true - SnsTopicEncryptionEnabled bool - - // The ARN of an existing KMS encryption key to be used for the SNS topic - SnsTopicEncryptionKeyArn string - - // Lacework Profile to use - LaceworkProfile string - - // The Lacework AWS Root Account ID - LaceworkAccountID string - - // Should we use an existing customer supplied bucket? Defaults to false - UseExistinglBucket bool - - // Existing S3 Bucket ARN (Required when using existing bucket) - ExistinglBucketArn string -} - -// Ensure all combinations of inputs our valid for supported spec -func (args *GenerateAwsEksAuditTfConfigurationArgs) validate() error { - - // Validate that at least one region with clusters was set - if len(args.ParsedRegionClusterMap) == 0 { - return errors.New("At least one region with a list of clusters must be set") - } - - // Validate, at least 1 cluster must be supplied per region - for _, clusters := range args.ParsedRegionClusterMap { - if len(clusters) == 0 { - return errors.New("At least one cluster must be supplied per region") - } - } - - // Validate existing role IAM values, if set - if args.ExistingCrossAccountIamRole != nil { - if args.ExistingCrossAccountIamRole.Arn == "" || - args.ExistingCrossAccountIamRole.ExternalId == "" { - return errors.New( - "when using an existing cross account IAM role, existing role ARN and external ID all must be set", - ) - } - } - - return nil -} - -type AwsEksAuditTerraformModifier func(c *GenerateAwsEksAuditTfConfigurationArgs) - -// NewTerraform returns an instance of the GenerateAwsEksAuditTfConfigurationArgs struct. -// -// Note: Additional configuration details may be set using modifiers of the AwsEksAuditTerraformModifier type -// -// Basic usage: Initialize a new AwsEksAuditTerraformModifier struct, with a non-default AWS profile set. Then -// use generate to create a string output of the required HCL. -// -// hcl, err := aws.NewTerraform({"us-east-1": ["cluster1", "cluster2"], "us-east-2": ["cluster3"]} -// aws.WithAwsProfile("mycorp-profile")).Generate() -func NewTerraform(mods ...AwsEksAuditTerraformModifier) *GenerateAwsEksAuditTfConfigurationArgs { - config := &GenerateAwsEksAuditTfConfigurationArgs{ - BucketVersioning: true, - BucketEnableEncryption: true, - FirehoseEncryptionEnabled: true, - KmsKeyMultiRegion: true, - KmsKeyRotation: true, - SnsTopicEncryptionEnabled: true, - } - for _, m := range mods { - m(config) - } - return config -} - -// WithLaceworkAccountID Set the Lacework AWS root account ID to use -func WithLaceworkAccountID(accountID string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.LaceworkAccountID = accountID - } -} - -// WithAwsProfile Set the AWS Profile to utilize when integrating -func WithAwsProfile(name string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.AwsProfile = name - } -} - -// EnableBucketMfaDelete Set the S3 MfaDelete parameter to true for newly created buckets -func EnableBucketMfaDelete() AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.BucketEnableMfaDelete = true - } -} - -// EnableBucketEncryption Set the S3 Encryption parameter to true for newly created buckets -func EnableBucketEncryption(enable bool) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.BucketEnableEncryption = enable - } -} - -// WithBucketLifecycleExpirationDays Set the S3 Lifecycle Expiration Days parameter for newly created buckets -func WithBucketLifecycleExpirationDays(days int) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.BucketLifecycleExpirationDays = days - } -} - -// WithBucketSseAlgorithm Set the encryption algorithm to use for S3 bucket server-side encryption -func WithBucketSseAlgorithm(algorithm string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.BucketSseAlgorithm = algorithm - } -} - -// WithBucketSseKeyArn Set the ARN of the KMS encryption key to be used for S3 -// (Required when bucket_sse_algorithm is aws:kms and using an existing aws_kms_key) -func WithBucketSseKeyArn(arn string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.BucketSseKeyArn = arn - } -} - -// EnableBucketVersioning Set the S3 Bucket versioning parameter to true for newly created buckets -func EnableBucketVersioning(enable bool) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.BucketVersioning = enable - } -} - -// WithEksAuditIntegrationName Set the name of the EKS audit integration -func WithEksAuditIntegrationName(name string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.EksAuditIntegrationName = name - } -} - -// WithExistingCloudWatchIamRoleArn Set an existing cloudwatch IAM role ARN -func WithExistingCloudWatchIamRoleArn(arn string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.ExistingCloudWatchIamRoleArn = arn - } -} - -// WithExistingCrossAccountIamRole Set an existing cross account IAM role configuration to use with -// the created Terraform code -func WithExistingCrossAccountIamRole(iamDetails *ExistingCrossAccountIamRoleDetails) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.ExistingCrossAccountIamRole = iamDetails - } -} - -// WithExistingFirehoseIamRoleArn Set an existing firehose IAM role ARN -func WithExistingFirehoseIamRoleArn(arn string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.ExistingFirehoseIamRoleArn = arn - } -} - -// WithFilterPattern Set the filter pattern for the Cloudwatch subscription filter -func WithFilterPattern(pattern string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.FilterPattern = pattern - } -} - -// EnableFirehoseEncryption Set the firehose encryption parameter to true for newly created firehose -func EnableFirehoseEncryption(enable bool) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.FirehoseEncryptionEnabled = enable - } -} - -// WithFirehoseEncryptionKeyArn Set the ARN of an existing KMS encryption key to be used -// with the Kinesis Firehose -func WithFirehoseEncryptionKeyArn(arn string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.FirehoseEncryptionKeyArn = arn - } -} - -// WithKmsKeyDeletionDays Set the KMS deletion waiting period, specified in number of days -func WithKmsKeyDeletionDays(days int) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.KmsKeyDeletionDays = days - } -} - -// EnableKmsKeyMultiRegion Set whether the KMS key is a multi-region or regional key -func EnableKmsKeyMultiRegion(enable bool) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.KmsKeyMultiRegion = enable - } -} - -// EnableKmsKeyRotation Set KMS automatic key rotation to true -func EnableKmsKeyRotation(enable bool) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.KmsKeyRotation = enable - } -} - -// WithPrefix Set the prefix that will be used at the beginning of every generated resource -func WithPrefix(prefix string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.Prefix = prefix - } -} - -// WithExistingRequiredProviders disables writing required_providers in output -func WithExistingRequiredProviders() AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.UseExistingRequiredProviders = true - } -} - -// WithProviderAliasPrefix set the prefix to prepend to provider alias names -func WithProviderAliasPrefix(prefix string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.ProviderAliasPrefix = prefix - } -} - -// WithParsedRegionClusterMap Set the region cluster map. -// This is a list of clusters per AWS region -func WithParsedRegionClusterMap(regionClusterMap map[string][]string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.ParsedRegionClusterMap = regionClusterMap - } -} - -// EnableSnsTopicEncryption Set whether encryption should be enabled for the sns topic -func EnableSnsTopicEncryption(enable bool) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.SnsTopicEncryptionEnabled = enable - } -} - -// WithSnsTopicEncryptionKeyArn Set the ARN of an existing KMS encryption key to be used -// with the SNS Topic -func WithSnsTopicEncryptionKeyArn(arn string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.SnsTopicEncryptionKeyArn = arn - } -} - -// WithLaceworkProfile Set the Lacework Profile to utilize when integrating -func WithLaceworkProfile(name string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.LaceworkProfile = name - } -} - -// WithExistingBucketArn Set the Lacework Profile to utilize when integrating -func WithExistingBucketArn(name string) AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.ExistinglBucketArn = name - } -} - -// EnableUseExistingBucket Set the S3 ForceDestroy parameter to true for newly created buckets -func EnableUseExistingBucket() AwsEksAuditTerraformModifier { - return func(c *GenerateAwsEksAuditTfConfigurationArgs) { - c.UseExistinglBucket = true - } -} - -// Generate new Terraform code based on the supplied args. -func (args *GenerateAwsEksAuditTfConfigurationArgs) Generate() (string, error) { - // Validate inputs - if err := args.validate(); err != nil { - return "", errors.Wrap(err, "invalid inputs") - } - - // Create blocks - requiredProviders, err := createRequiredProviders(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate required providers") - } - - populateParsedRegionsList(args) - - awsProvider, err := createAwsProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate aws provider") - } - - laceworkProvider, err := createLaceworkProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate lacework provider") - } - - eksAuditModule, err := createEksAudit(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate aws eks audit module & resources") - } - - // Render - hclBlocks := lwgenerate.CreateHclStringOutput( - lwgenerate.CombineHclBlocks( - requiredProviders, - awsProvider, - laceworkProvider, - eksAuditModule), - ) - return hclBlocks, nil -} - -func createRequiredProviders(args *GenerateAwsEksAuditTfConfigurationArgs) (*hclwrite.Block, error) { - if args.UseExistingRequiredProviders { - return nil, nil - } - return lwgenerate.CreateRequiredProviders( - lwgenerate.NewRequiredProvider("lacework", - lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), - lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion))) -} - -func populateParsedRegionsList(args *GenerateAwsEksAuditTfConfigurationArgs) { - for region := range args.ParsedRegionClusterMap { - // append each region to args.ParsedRegionsList this will be used to sort the keys - // of the map and ensure ordering - args.ParsedRegionsList = append(args.ParsedRegionsList, region) - - // This sorted list will be used to ensure order when retrieving from the ParsedRegionClusterMap - sort.Strings(args.ParsedRegionsList) - } -} -func createProviderAlias(args *GenerateAwsEksAuditTfConfigurationArgs, baseName string) string { - aliasName := baseName - if args.ProviderAliasPrefix != "" { - aliasName = fmt.Sprintf("%s-%s", args.ProviderAliasPrefix, baseName) - } - - return aliasName -} - -func createAwsProvider(args *GenerateAwsEksAuditTfConfigurationArgs) ([]*hclwrite.Block, error) { - var blocks []*hclwrite.Block - for i := range args.ParsedRegionsList { - region := args.ParsedRegionsList[i] - - attrs := map[string]interface{}{ - "alias": createProviderAlias(args, region), - "region": region, - } - - if args.AwsProfile != "" { - attrs["profile"] = args.AwsProfile - } - - providerBlock, err := lwgenerate.NewProvider( - "aws", - lwgenerate.HclProviderWithAttributes(attrs), - ).ToBlock() - - if err != nil { - return nil, err - } - - blocks = append(blocks, providerBlock) - } - - // set kms key multi region to false if only 1 region is supplied - if len(args.ParsedRegionsList) == 1 { - args.KmsKeyMultiRegion = false - } - return blocks, nil -} - -func createLaceworkProvider(args *GenerateAwsEksAuditTfConfigurationArgs) (*hclwrite.Block, error) { - if args.LaceworkProfile != "" { - return lwgenerate.NewProvider( - "lacework", - lwgenerate.HclProviderWithAttributes(map[string]interface{}{"profile": args.LaceworkProfile}), - ).ToBlock() - } - return nil, nil -} - -func createEksAudit(args *GenerateAwsEksAuditTfConfigurationArgs) ([]*hclwrite.Block, error) { - var blocks []*hclwrite.Block - moduleAttrs := map[string]interface{}{} - resourceAttrs := map[string]interface{}{} - moduleDetails := []lwgenerate.HclModuleModifier{lwgenerate.HclModuleWithVersion(lwgenerate.AwsEksAuditVersion)} - - if args.LaceworkAccountID != "" { - moduleAttrs["lacework_aws_account_id"] = args.LaceworkAccountID - } - - if args.UseExistinglBucket { - moduleAttrs["use_existing_bucket"] = true - moduleAttrs["bucket_arn"] = args.ExistinglBucketArn - } - - if args.BucketEnableMfaDelete && args.BucketVersioning { - moduleAttrs["bucket_enable_mfa_delete"] = true - } - - if !args.BucketEnableEncryption { - moduleAttrs["bucket_encryption_enabled"] = args.BucketEnableEncryption - } - - if args.BucketLifecycleExpirationDays > 0 { - moduleAttrs["bucket_lifecycle_expiration_days"] = args.BucketLifecycleExpirationDays - } - - if args.BucketSseAlgorithm != "" && args.BucketEnableEncryption { - moduleAttrs["bucket_sse_algorithm"] = args.BucketSseAlgorithm - } - - if !args.BucketVersioning { - moduleAttrs["bucket_versioning_enabled"] = args.BucketVersioning - } - - if args.BucketSseKeyArn != "" && args.BucketEnableEncryption { - moduleAttrs["bucket_key_arn"] = args.BucketSseKeyArn - } - - if args.ExistingCloudWatchIamRoleArn != "" { - moduleAttrs["use_existing_cloudwatch_iam_role"] = true - moduleAttrs["cloudwatch_iam_role_arn"] = args.ExistingCloudWatchIamRoleArn - } - - if args.ExistingCrossAccountIamRole != nil { - moduleAttrs["use_existing_cross_account_iam_role"] = true - moduleAttrs["iam_role_arn"] = args.ExistingCrossAccountIamRole.Arn - moduleAttrs["iam_role_external_id"] = args.ExistingCrossAccountIamRole.ExternalId - } - - if args.ExistingFirehoseIamRoleArn != "" { - moduleAttrs["use_existing_firehose_iam_role"] = true - moduleAttrs["firehose_iam_role_arn"] = args.ExistingFirehoseIamRoleArn - } - - if args.FilterPattern != "" { - moduleAttrs["filter_pattern"] = args.FilterPattern - } - - if !args.FirehoseEncryptionEnabled { - moduleAttrs["kinesis_firehose_encryption_enabled"] = args.FirehoseEncryptionEnabled - } - - if args.FirehoseEncryptionKeyArn != "" && args.FirehoseEncryptionEnabled { - moduleAttrs["kinesis_firehose_key_arn"] = args.FirehoseEncryptionKeyArn - } - - if args.KmsKeyDeletionDays >= 7 && args.KmsKeyDeletionDays <= 30 { - moduleAttrs["kms_key_deletion_days"] = args.KmsKeyDeletionDays - } - - if !args.KmsKeyMultiRegion { - moduleAttrs["kms_key_multi_region"] = args.KmsKeyMultiRegion - } - - if !args.KmsKeyRotation { - moduleAttrs["kms_key_rotation"] = args.KmsKeyRotation - } - - if !args.SnsTopicEncryptionEnabled { - moduleAttrs["sns_topic_encryption_enabled"] = args.SnsTopicEncryptionEnabled - } - - if args.SnsTopicEncryptionKeyArn != "" && args.SnsTopicEncryptionEnabled { - moduleAttrs["sns_topic_key_arn"] = args.SnsTopicEncryptionKeyArn - } - - if len(args.ParsedRegionsList) > 1 { - // set no_cw_subscription_filter if we have more than 1 region in the ParsedRegionClusterMap - moduleAttrs["no_cw_subscription_filter"] = true - - // Add aws_cloudwatch_log_subscription_filter(s) resource per region - for i := range args.ParsedRegionsList { - region := args.ParsedRegionsList[i] - clusters := args.ParsedRegionClusterMap[region] - - // create hcl tokens for each cluster and create a token array to be added to our hcl - //tuple. (we are unable to add the for loop inside the call to TokensForTuple) - var tokens []hclwrite.Tokens - for _, cluster := range clusters { - tokens = append(tokens, hclwrite.TokensForValue(cty.StringVal(cluster))) - } - - // the for_each input must be in the following format toset(["", ""]) - // In order to achieve this we can use TokensForTuple to build the tuple `[]` - // then TokensForFunctionCall to wrap this with our call to the `toset()` function - resourceAttrs["for_each"] = hclwrite.TokensForFunctionCall( - "toset", - hclwrite.TokensForTuple(tokens), - ) - // Using hclwrite.Tokens as $ is not supported as part of string expression. - // Adding a single "$" would result in "$$" - resourceAttrs["name"] = hclwrite.Tokens{ - {Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`)}, - {Type: hclsyntax.TokenIdent, Bytes: []byte(`${module.aws_eks_audit_log.filter_prefix}-${each.value}`)}, - {Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)}, - } - resourceAttrs["log_group_name"] = hclwrite.Tokens{ - {Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`)}, - {Type: hclsyntax.TokenIdent, Bytes: []byte(`/aws/eks/${each.value}/cluster`)}, - {Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)}, - } - - resourceAttrs["role_arn"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "aws_eks_audit_log", "cloudwatch_iam_role_arn"}) - resourceAttrs["filter_pattern"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "aws_eks_audit_log", "filter_pattern"}) - resourceAttrs["destination_arn"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "aws_eks_audit_log", "firehose_arn"}) - - // the depends_on input must be in the following format [""] - // In order to achieve this we can use TokensForTuple to build the tuple `[]` - resourceAttrs["depends_on"] = hclwrite.TokensForTuple([]hclwrite.Tokens{ - hclwrite.TokensForTraversal( - lwgenerate.CreateSimpleTraversal([]string{"module", "aws_eks_audit_log"}), - ), - }) - - lwCwSubscriptionFilter, err := lwgenerate.NewResource( - "aws_cloudwatch_log_subscription_filter", - fmt.Sprintf( - "lw_cw_subscription_filter_%s", - region), - lwgenerate.HclResourceWithAttributesAndProviderDetails( - resourceAttrs, - []string{fmt.Sprintf("aws.%s", createProviderAlias(args, region))}, - ), - ).ToResourceBlock() - - if err != nil { - return nil, err - } - - blocks = append(blocks, lwCwSubscriptionFilter) - } - } else if len(args.ParsedRegionsList) > 0 { - // set no_cw_subscription_filter to false if we have only 1 region in the ParsedRegionClusterMap - moduleAttrs["no_cw_subscription_filter"] = false - for i := range args.ParsedRegionsList { - region := args.ParsedRegionsList[i] - clusters := args.ParsedRegionClusterMap[region] - moduleAttrs["cluster_names"] = clusters - } - } - - moduleAttrs["cloudwatch_regions"] = args.ParsedRegionsList - - moduleDetails = append(moduleDetails, - lwgenerate.HclModuleWithProviderDetails( - map[string]string{"aws": fmt.Sprintf("aws.%s", createProviderAlias(args, args.ParsedRegionsList[0]))}), - lwgenerate.HclModuleWithAttributes(moduleAttrs), - ) - - modDetails, err := lwgenerate.NewModule( - "aws_eks_audit_log", - lwgenerate.AwsEksAuditSource, - moduleDetails..., - ).ToBlock() - - if err != nil { - return nil, err - } - blocks = append(blocks, modDetails) - - return blocks, nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/azure/azure.go b/vendor/github.com/lacework/go-sdk/lwgenerate/azure/azure.go deleted file mode 100644 index e1fb2f987..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/azure/azure.go +++ /dev/null @@ -1,671 +0,0 @@ -// A package that generates Lacework deployment code for Azure cloud. -package azure - -import ( - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/lacework/go-sdk/lwgenerate" - "github.com/pkg/errors" -) - -type GenerateAzureTfConfigurationArgs struct { - // Should we configure Activity Log integration in LW? - ActivityLog bool - - // Should we add Config integration in LW? - Config bool - - // Should we create an Entra ID integration in LW? - EntraIdActivityLog bool - - // Should we create an Active Directory integration - CreateAdIntegration bool - - // If Config is true, give the user the opportunity to name their integration. Defaults to "TF Config" - ConfigIntegrationName string - - // If ActivityLog is true, give the user the opportunity to name their integration. Defaults to "TF activity log" - ActivityLogIntegrationName string - - // If EntraIdIntegration is true, give the user the opportunity to name their integration. - // Defaults to "TF Entra ID activity log" - EntraIdIntegrationName string - - // Active Directory application Id - AdApplicationId string - - // Active Directory password - AdApplicationPassword string - - // Active Directory Enterprise app object id - AdServicePrincipalId string - - // Should we use the management group, rather than subscription - ManagementGroup bool - - // Management Group ID to set - ManagementGroupId string - - // List of subscription Ids - SubscriptionIds []string - - // Subscription ID configured in azurerm provider block - SubscriptionID string - - // Grant read access to ALL subscriptions - AllSubscriptions bool - - // Storage Account name - StorageAccountName string - - // Storage Account Resource Group - StorageAccountResourceGroup string - - // Should we use existing storage account - ExistingStorageAccount bool - - // Azure region where the storage account for logging resides - StorageLocation string - - LaceworkProfile string - - // Azure region where the event hub for logging will reside - EventHubLocation string - - // Number of partitions in the Event Hub for logging - EventHubPartitionCount int - - // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc - ExtraBlocksRootTerraform []*hclwrite.Block - - // ExtraAZRMArguments allows adding more arguments to the provider block as needed (custom use cases) - ExtraAZRMArguments map[string]interface{} - - // ExtraAZReadArguments allows adding more arguments to the provider block as needed (custom use cases) - ExtraAZReadArguments map[string]interface{} - - // ExtraBlocks allows adding more hclwrite.Block to the root terraform document (advanced use cases) - ExtraBlocks []*hclwrite.Block - - // Custom outputs - CustomOutputs []lwgenerate.HclOutput -} - -// Ensure all combinations of inputs are valid for supported spec -func (args *GenerateAzureTfConfigurationArgs) validate() error { - // Validate one of config or activity log was enabled; otherwise error out - if !args.ActivityLog && !args.Config && !args.EntraIdActivityLog { - return errors.New("audit log or config integration must be enabled") - } - - // Validate that active directory settings are correct - if !args.CreateAdIntegration && (args.AdApplicationId == "" || - args.AdServicePrincipalId == "" || args.AdApplicationPassword == "") { - return errors.New("Active directory details must be set") - } - - // Validate the Mangement Group - if args.ManagementGroup && args.ManagementGroupId == "" { - return errors.New("When Group Management is enabled, then Group Id must be configured") - } - - // Validate Storage Account - if args.ExistingStorageAccount && (args.StorageAccountName == "" || args.StorageAccountResourceGroup == "") { - return errors.New("When using existing storage account, storage account details must be configured") - } - - return nil -} - -type AzureTerraformModifier func(c *GenerateAzureTfConfigurationArgs) - -// NewTerraform returns an instance of the GenerateAzureTfConfigurationArgs struct with the provided enabled -// settings (config/activity log). -// -// Note: Additional configuration details may be set using modifiers of the AzureTerraformModifier type -func NewTerraform( - enableConfig bool, enableActivityLog bool, enableEntraIdActivityLog, createAdIntegration bool, - mods ...AzureTerraformModifier, -) *GenerateAzureTfConfigurationArgs { - config := &GenerateAzureTfConfigurationArgs{ - ActivityLog: enableActivityLog, - Config: enableConfig, - EntraIdActivityLog: enableEntraIdActivityLog, - CreateAdIntegration: createAdIntegration, - } - for _, m := range mods { - m(config) - } - return config -} - -// WithConfigIntegrationName Set the Config Integration name to be displayed on the Lacework UI -func WithConfigIntegrationName(name string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.ConfigIntegrationName = name - } -} - -// WithConfigOutputs Set Custom Terraform Outputs -func WithCustomOutputs(outputs []lwgenerate.HclOutput) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.CustomOutputs = outputs - } -} - -// WithExtraRootBlocks allows adding generic hcl blocks to the root `terraform{}` block -// this enables custom use cases -func WithExtraRootBlocks(blocks []*hclwrite.Block) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.ExtraBlocksRootTerraform = blocks - } -} - -// WithExtraAZRMArguments enables adding additional arguments into the `azurerm` provider block -// this enables custom use cases -func WithExtraAZRMArguments(arguments map[string]interface{}) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.ExtraAZRMArguments = arguments - } -} - -// WithExtraAZReadArguments enables adding additional arguments into the `azuread` provider block -// this enables custom use cases -func WithExtraAZReadArguments(arguments map[string]interface{}) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.ExtraAZReadArguments = arguments - } -} - -// WithExtraBlocks enables adding additional arbitrary blocks to the root hcl document -func WithExtraBlocks(blocks []*hclwrite.Block) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.ExtraBlocks = blocks - } -} - -// WithActivityLogIntegrationName Set the Activity Log Integration name to be displayed on the Lacework UI -func WithActivityLogIntegrationName(name string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.ActivityLogIntegrationName = name - } -} - -// WithEntraIdActivityLogIntegrationName Set the Entra ID Activity Log Integration name -// to be displayed on the Lacework UI -func WithEntraIdActivityLogIntegrationName(name string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.EntraIdIntegrationName = name - } -} - -// WithAdApplicationId Set Active Directory application id -func WithAdApplicationId(AdApplicationId string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.AdApplicationId = AdApplicationId - } -} - -// WithAdApplicationPassword Set the Active Directory password -func WithAdApplicationPassword(AdApplicationPassword string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.AdApplicationPassword = AdApplicationPassword - } -} - -// WithAdServicePrincipalId Set Active Directory principal id -func WithAdServicePrincipalId(AdServicePrincipalId string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.AdServicePrincipalId = AdServicePrincipalId - } -} - -// WithManagementGroup Enable the Management Group to allow AD to be reader on management group -// rather then subscription -func WithManagementGroup(enableManagentGroup bool) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.ManagementGroup = enableManagentGroup - } -} - -// WithManagementGroupId The Group Id to add reader permissions -func WithManagementGroupId(managementGroupId string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.ManagementGroupId = managementGroupId - } -} - -// WithSubscriptionIds List of subscriptions to to enable logging -func WithSubscriptionIds(subscriptionIds []string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.SubscriptionIds = subscriptionIds - } -} - -// WithAllSubscriptions Grant read access to ALL subscriptions within -// the selected Tenant (overrides 'subscription_ids') -func WithAllSubscriptions(allSubscriptions bool) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.AllSubscriptions = allSubscriptions - } -} - -// WithExistingStorageAccount Use an existing Storage Account -func WithExistingStorageAccount(existingStorageAccount bool) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.ExistingStorageAccount = existingStorageAccount - } -} - -// WithStorageAccountName The name of the Storage Account -func WithStorageAccountName(storageAccountName string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.StorageAccountName = storageAccountName - } -} - -// WithStorageAccountResourceGroup The Resource Group for the existing Storage Account -func WithStorageAccountResourceGroup(storageAccountResourceGroup string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.StorageAccountResourceGroup = storageAccountResourceGroup - } -} - -// WithStorageLocation The Azure region where storage account for logging is -func WithStorageLocation(location string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.StorageLocation = location - } -} - -// WithEventHubLocation The Azure region where the event hub for logging resides -func WithEventHubLocation(location string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.EventHubLocation = location - } -} - -// WitthEventHubPartitionCount The number of partitions in the Event Hub for logging -func WithEventHubPartitionCount(partitionCount int) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.EventHubPartitionCount = partitionCount - } -} - -func WithLaceworkProfile(name string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.LaceworkProfile = name - } -} - -func WithSubscriptionID(subcriptionID string) AzureTerraformModifier { - return func(c *GenerateAzureTfConfigurationArgs) { - c.SubscriptionID = subcriptionID - } -} - -// Generate new Terraform code based on the supplied args. -func (args *GenerateAzureTfConfigurationArgs) Generate() (string, error) { - // Validate inputs - if err := args.validate(); err != nil { - return "", errors.Wrap(err, "invalid inputs") - } - - // Create blocks - requiredProviders, err := createRequiredProviders(args.ExtraBlocksRootTerraform) - if err != nil { - return "", errors.Wrap(err, "failed to generate required providers") - } - - laceworkProvider, err := createLaceworkProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate lacework provider") - } - - azureADProvider, err := createAzureADProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate AD provider") - } - - azureRMProvider, err := createAzureRMProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate AM provider") - } - - laceworkADProvider, err := createLaceworkAzureADModule(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate lacework Azure AD provider") - } - - configModule, err := createConfig(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate azure config module") - } - - activityLogModule, err := createActivityLog(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate azure activity log module") - } - - entraIdActivityLogModule, err := createEntraIdActivityLog(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate azure Entra ID activity log module") - } - - outputBlocks := []*hclwrite.Block{} - for _, output := range args.CustomOutputs { - outputBlock, err := output.ToBlock() - if err != nil { - return "", errors.Wrap(err, "failed to add custom output") - } - outputBlocks = append(outputBlocks, outputBlock) - } - - // Render - hclBlocks := lwgenerate.CreateHclStringOutput( - lwgenerate.CombineHclBlocks( - requiredProviders, - laceworkProvider, - azureADProvider, - azureRMProvider, - laceworkADProvider, - configModule, - activityLogModule, - entraIdActivityLogModule, - outputBlocks, - args.ExtraBlocks), - ) - return hclBlocks, nil -} - -func createRequiredProviders(extraBlocks []*hclwrite.Block) (*hclwrite.Block, error) { - return lwgenerate.CreateRequiredProvidersWithCustomBlocks( - extraBlocks, - lwgenerate.NewRequiredProvider( - "lacework", - lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), - lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion), - ), - ) -} - -func createLaceworkProvider(args *GenerateAzureTfConfigurationArgs) (*hclwrite.Block, error) { - if args.LaceworkProfile != "" { - return lwgenerate.NewProvider( - "lacework", - lwgenerate.HclProviderWithAttributes(map[string]interface{}{"profile": args.LaceworkProfile}), - ).ToBlock() - } - return nil, nil -} - -func createAzureADProvider(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { - blocks := []*hclwrite.Block{} - attrs := map[string]interface{}{} - - // set custom args before the required ones below to ensure expected behavior (i.e., no overrides) - for k, v := range args.ExtraAZReadArguments { - attrs[k] = v - } - - provider, err := lwgenerate.NewProvider( - "azuread", - lwgenerate.HclProviderWithAttributes(attrs), - ).ToBlock() - - if err != nil { - return nil, err - } - - blocks = append(blocks, provider) - return blocks, nil -} - -// In this we need to create a provider block with a features -// configuration but with nothing set, this is as per the -// Azure examples and is of the format -// -// provider "azurerm" { -// features = {} -// } -func createAzureRMProvider(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { - blocks := []*hclwrite.Block{} - attrs := map[string]interface{}{} - featureAttrs := map[string]interface{}{} - - // set custom args before the required ones below to ensure expected behavior (i.e., no overrides) - for k, v := range args.ExtraAZRMArguments { - attrs[k] = v - } - - if args.SubscriptionID != "" { - attrs["subscription_id"] = args.SubscriptionID - } - - provider, err := lwgenerate.NewProvider( - "azurerm", - lwgenerate.HclProviderWithAttributes(attrs), - ).ToBlock() - - if err != nil { - return nil, err - } - // Create the features block - featuresBlock, err := lwgenerate.HclCreateGenericBlock("features", []string{}, featureAttrs) - provider.Body().AppendBlock(featuresBlock) - - if err != nil { - return nil, err - } - - blocks = append(blocks, provider) - return blocks, nil -} - -func createLaceworkAzureADModule(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { - blocks := []*hclwrite.Block{} - - if args.CreateAdIntegration { - provider, err := lwgenerate.NewModule( - "az_ad_application", - lwgenerate.LWAzureADSource, - lwgenerate.HclModuleWithVersion(lwgenerate.LWAzureADVersion), - ).ToBlock() - - if err != nil { - return nil, err - } - - blocks = append(blocks, provider) - } - return blocks, nil -} - -func createConfig(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { - blocks := []*hclwrite.Block{} - if args.Config { - attributes := map[string]interface{}{} - moduleDetails := []lwgenerate.HclModuleModifier{} - - if args.ConfigIntegrationName != "" { - attributes["lacework_integration_name"] = args.ConfigIntegrationName - } - - // Check if we have created an Active Directory app - if args.CreateAdIntegration { - attributes["use_existing_ad_application"] = true - attributes["application_id"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "az_ad_application", "application_id"}) - attributes["application_password"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "az_ad_application", "application_password"}) - attributes["service_principal_id"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "az_ad_application", "service_principal_id"}) - } else { - attributes["use_existing_ad_application"] = true - attributes["application_id"] = args.AdApplicationId - attributes["application_password"] = args.AdApplicationPassword - attributes["service_principal_id"] = args.AdServicePrincipalId - } - - // Only set subscription ids if all subscriptions flag is not set - if !args.AllSubscriptions { - if len(args.SubscriptionIds) > 0 { - attributes["subscription_ids"] = args.SubscriptionIds - } - } else { - // Set Subscription information - attributes["all_subscriptions"] = args.AllSubscriptions - } - - // Set Management Group details - if args.ManagementGroup { - attributes["use_management_group"] = args.ManagementGroup - attributes["management_group_id"] = args.ManagementGroupId - } - - moduleDetails = append(moduleDetails, - lwgenerate.HclModuleWithAttributes(attributes), - ) - - moduleBlock, err := lwgenerate.NewModule( - "az_config", - lwgenerate.LWAzureConfigSource, - append(moduleDetails, lwgenerate.HclModuleWithVersion(lwgenerate.LWAzureConfigVersion))..., - ).ToBlock() - - if err != nil { - return nil, err - } - blocks = append(blocks, moduleBlock) - } - - return blocks, nil -} - -func createActivityLog(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { - blocks := []*hclwrite.Block{} - if args.ActivityLog { - attributes := map[string]interface{}{} - moduleDetails := []lwgenerate.HclModuleModifier{} - - if args.ActivityLogIntegrationName != "" { - attributes["lacework_integration_name"] = args.ActivityLogIntegrationName - } - - // Check if we have created an Active Directory integration - if args.CreateAdIntegration { - attributes["use_existing_ad_application"] = true - attributes["application_id"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "az_ad_application", "application_id"}) - attributes["application_password"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "az_ad_application", "application_password"}) - attributes["service_principal_id"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "az_ad_application", "service_principal_id"}) - } else { - attributes["use_existing_ad_application"] = true - attributes["application_id"] = args.AdApplicationId - attributes["application_password"] = args.AdApplicationPassword - attributes["service_principal_id"] = args.AdServicePrincipalId - } - - // Only set subscription ids if all subscriptions flag is not set - if !args.AllSubscriptions { - if len(args.SubscriptionIds) > 0 { - attributes["subscription_ids"] = args.SubscriptionIds - } - } else { - // Set Subscription information - attributes["all_subscriptions"] = args.AllSubscriptions - } - - // Set storage account name, if set - if args.StorageAccountName != "" { - attributes["storage_account_name"] = args.StorageAccountName - } - - // Set storage info if existing storage flag is set - if args.ExistingStorageAccount { - attributes["use_existing_storage_account"] = args.ExistingStorageAccount - attributes["storage_account_resource_group"] = args.StorageAccountResourceGroup - } - - // if a new storage account is being created (i.e., ExistingStorageAccount is false), enable infrastructure - // encryption - if !args.ExistingStorageAccount { - attributes["infrastructure_encryption_enabled"] = true - } - - // Set the location if needed - if args.StorageLocation != "" { - attributes["location"] = args.StorageLocation - } - - moduleDetails = append(moduleDetails, - lwgenerate.HclModuleWithAttributes(attributes), - ) - - moduleBlock, err := lwgenerate.NewModule( - "az_activity_log", - lwgenerate.LWAzureActivityLogSource, - append(moduleDetails, lwgenerate.HclModuleWithVersion(lwgenerate.LWAzureActivityLogVersion))..., - ).ToBlock() - - if err != nil { - return nil, err - } - blocks = append(blocks, moduleBlock) - - } - return blocks, nil -} - -func createEntraIdActivityLog(args *GenerateAzureTfConfigurationArgs) ([]*hclwrite.Block, error) { - blocks := []*hclwrite.Block{} - if args.EntraIdActivityLog { - attributes := map[string]interface{}{} - moduleDetails := []lwgenerate.HclModuleModifier{} - - if args.EntraIdIntegrationName != "" { - attributes["lacework_integration_name"] = args.EntraIdIntegrationName - } - - // Check if we have created an Active Directory integration - if args.CreateAdIntegration { - attributes["use_existing_ad_application"] = false - attributes["application_id"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "az_ad_application", "application_id"}) - attributes["application_password"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "az_ad_application", "application_password"}) - attributes["service_principal_id"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "az_ad_application", "service_principal_id"}) - } else { - attributes["use_existing_ad_application"] = true - attributes["application_id"] = args.AdApplicationId - attributes["application_password"] = args.AdApplicationPassword - attributes["service_principal_id"] = args.AdServicePrincipalId - } - - if args.EventHubLocation != "" { - attributes["location"] = args.EventHubLocation - } - - if args.EventHubPartitionCount > 0 { - attributes["num_partitions"] = args.EventHubPartitionCount - } - - moduleDetails = append(moduleDetails, - lwgenerate.HclModuleWithAttributes(attributes), - ) - - moduleBlock, err := lwgenerate.NewModule( - "microsoft-entra-id-activity-log", - lwgenerate.LWAzureEntraIdActivityLogSource, - append(moduleDetails, lwgenerate.HclModuleWithVersion(lwgenerate.LWAzureEntraIdActivityLogVersion))..., - ).ToBlock() - - if err != nil { - return nil, err - } - blocks = append(blocks, moduleBlock) - } - return blocks, nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/constants.go b/vendor/github.com/lacework/go-sdk/lwgenerate/constants.go deleted file mode 100644 index 633dd5d5c..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/constants.go +++ /dev/null @@ -1,43 +0,0 @@ -package lwgenerate - -// TODO update -const ( - LaceworkProviderSource = "lacework/lacework" - LaceworkProviderVersion = "~> 1.0" - - AwsAgentlessSource = "lacework/agentless-scanning/aws" - AwsAgentlessVersion = "~> 0.6" - AwsConfigSource = "lacework/config/aws" - AwsConfigVersion = "~> 0.5" - AwsConfigOrgSource = "lacework/org-configuration/aws" - AwsConfigOrgVersion = "~> 1.0" - AwsCloudTrailSource = "lacework/cloudtrail/aws" - AwsCloudTrailVersion = "~> 2.7" - AwsCloudTrailControlTowerSource = "lacework/cloudtrail-controltower/aws" - AwsCloudTrailControlTowerVersion = "~> 0.3" - AwsEksAuditSource = "lacework/eks-audit-log/aws" - AwsEksAuditVersion = "~> 1.0" - - LWAzureConfigSource = "lacework/config/azure" - LWAzureConfigVersion = "~> 2.0" - LWAzureActivityLogSource = "lacework/activity-log/azure" - LWAzureActivityLogVersion = "~> 2.0" - LWAzureEntraIdActivityLogSource = "lacework/microsoft-entra-id-activity-log/azure" - LWAzureEntraIdActivityLogVersion = "~> 0.2" - LWAzureADSource = "lacework/ad-application/azure" - LWAzureADVersion = "~> 1.0" - - GcpAgentlessSource = "lacework/agentless-scanning/gcp" - GcpAgentlessVersion = "~> 2.0" - GcpConfigSource = "lacework/config/gcp" - GcpConfigVersion = "~> 3.0" - GcpAuditLogSource = "lacework/audit-log/gcp" - GcpAuditLogVersion = "~> 3.4" - GcpGKEAuditLogSource = "lacework/gke-audit-log/gcp" - GcpGKEAuditLogVersion = "~> 0.3" - GcpPubSubAuditLog = "lacework/pub-sub-audit-log/gcp" - GcpPubSubAuditLogVersion = "~> 0.2" - - OciConfigSource = "lacework/config/oci" - OciConfigVersion = "~> 0.2" -) diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gcp.go b/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gcp.go deleted file mode 100644 index 791b83ec0..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gcp.go +++ /dev/null @@ -1,932 +0,0 @@ -// A package that generates Lacework deployment code for Google cloud. -package gcp - -import ( - "fmt" - "sort" - - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/lacework/go-sdk/internal/unique" - "github.com/lacework/go-sdk/lwgenerate" - "github.com/pkg/errors" -) - -type ExistingServiceAccountDetails struct { - // Existing Service Account Name - Name string - - // Existing Service Account private key in JSON format, base64 encoded - PrivateKey string -} - -// NewExistingServiceAccountDetails Create new existing Service Account details -func NewExistingServiceAccountDetails(name string, privateKey string) *ExistingServiceAccountDetails { - return &ExistingServiceAccountDetails{ - Name: name, - PrivateKey: privateKey, - } -} - -func (e *ExistingServiceAccountDetails) IsPartial() bool { - // If nil, return false - if e == nil { - return false - } - - // If all values are empty, return false - if e.Name == "" && e.PrivateKey == "" { - return false - } - - // If all values are populated, return false - if e.Name != "" && e.PrivateKey != "" { - return false - } - - return true -} - -type GenerateGcpTfConfigurationArgs struct { - // Should we configure Agentless integration in LW? - Agentless bool - - // Should we configure AuditLog integration in LW? - AuditLog bool - - // Should we use the Pub Sub Audit Log or use the Bucket based one - UsePubSubAudit bool - - // Should we configure CSPM integration in LW? - Configuration bool - - // A list of GCP project IDs to monitor for Agentless integration - ProjectFilterList []string - - // A list of regions to deploy for Agentless integration - Regions []string - - // Path to service account credentials to be used by Terraform - ServiceAccountCredentials string - - // Should we configure an Organization wide integration? - OrganizationIntegration bool - - // Supply a GCP Organization ID, only asked if OrganizationIntegration is True - GcpOrganizationId string - - // Supply a GCP Project ID, to host the new resources - GcpProjectId string - - // Optionally supply existing Service Account Details - ExistingServiceAccount *ExistingServiceAccountDetails - - // If Configuration is true, give the user the opportunity to name their integration. Defaults to "TF Config" - ConfigurationIntegrationName string - - // Set of labels which will be added to the resources managed by the module - AuditLogLabels map[string]string - - // Set of labels which will be added to the audit log bucket - BucketLabels map[string]string - - // Set of labels which will be added to the subscription - PubSubSubscriptionLabels map[string]string - - // Set of labels which will be added to the topic - PubSubTopicLabels map[string]string - - CustomBucketName string - - // Supply a GCP region for the new bucket. EU/US/ASIA - BucketRegion string - - // Existing Bucket Name - ExistingLogBucketName string - - // Existing Sink Name - ExistingLogSinkName string - - // Should we force destroy the bucket if it has stuff in it? (only relevant on new Audit Log creation) - // DEPRECATED - EnableForceDestroyBucket bool - - // Boolean for enabling Uniform Bucket Level Access on the audit log bucket. Defaults to False - EnableUBLA bool - - // Number of days to keep audit logs in Lacework GCS bucket before deleting. - // If left empty the TF will default to -1 - LogBucketLifecycleRuleAge int - - // If AuditLog is true, give the user the opportunity to name their integration. Defaults to "TF audit_log" - AuditLogIntegrationName string - - // Lacework Profile to use - LaceworkProfile string - - FoldersToInclude []string - - FoldersToExclude []string - - IncludeRootProjects bool - - CustomFilter string - - GoogleWorkspaceFilter bool - - K8sFilter bool - - Prefix string - - WaitTime string - - Projects []string - - // Default GCP Provider labels - ProviderDefaultLabels map[string]interface{} - - // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc - ExtraBlocksRootTerraform []*hclwrite.Block - - // ExtraProviderArguments allows adding more arguments to the provider block as needed (custom use cases) - ExtraProviderArguments map[string]interface{} - - // ExtraBlocks allows adding more hclwrite.Block to the root terraform document (advanced use cases) - ExtraBlocks []*hclwrite.Block - - // Custom outputs - CustomOutputs []lwgenerate.HclOutput -} - -// Ensure all combinations of inputs are valid for supported spec -func (args *GenerateGcpTfConfigurationArgs) validate() error { - // Validate one of agentless, config or audit log was enabled; otherwise error out - if !args.Agentless && !args.AuditLog && !args.Configuration { - return errors.New("agentless, audit log or configuration integration must be enabled") - } - - if args.Agentless && len(args.Regions) == 0 { - return errors.New("regions must be provided for Agentless Integration") - } - - // Validate if this is an organization integration, verify that the organization id has been provided - if args.OrganizationIntegration && args.GcpOrganizationId == "" { - return errors.New("an Organization ID must be provided for an Organization Integration") - } - - // Validate existing Service Account values, if set - if args.ExistingServiceAccount != nil { - if args.ExistingServiceAccount.Name == "" || - args.ExistingServiceAccount.PrivateKey == "" { - return errors.New("when using an existing Service Account, existing name, and base64 " + - "encoded JSON Private Key fields all must be set") - } - } - - return nil -} - -type GcpTerraformModifier func(c *GenerateGcpTfConfigurationArgs) - -// NewTerraform returns an instance of the GenerateGcpTfConfigurationArgs struct with the provided enabled -// settings (configuration/audit log). -// -// Note: Additional configuration details may be set using modifiers of the GcpTerraformModifier type -// -// Basic usage: Initialize a new GcpTerraformModifier struct, with GCP service account credentials. Then use generate to -// -// create a string output of the required HCL. -// -// hcl, err := gcp.NewTerraform(true, true, true, true, -// gcp.WithGcpServiceAccountCredentials("/path/to/sa/credentials.json")).Generate() -func NewTerraform( - enableAgentless, enableConfig bool, enableAuditLog bool, enablePubSubAudit bool, mods ...GcpTerraformModifier, -) *GenerateGcpTfConfigurationArgs { - config := &GenerateGcpTfConfigurationArgs{ - Agentless: enableAgentless, - AuditLog: enableAuditLog, - UsePubSubAudit: enablePubSubAudit, - Configuration: enableConfig, - IncludeRootProjects: true, - EnableUBLA: true, - GoogleWorkspaceFilter: true, - K8sFilter: true, - } - // default LogBucketLifecycleRuleAge to -1. This helps us determine if the var has been set by the end user - config.LogBucketLifecycleRuleAge = -1 - for _, m := range mods { - m(config) - } - return config -} - -// WithUsePubSubAudit Set wether we use pub sub with the audit log rather than bucket based -func WithUsePubSubAudit(usePubSub bool) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.UsePubSubAudit = usePubSub - } -} - -// WithGcpServiceAccountCredentials Set the path for the GCP Service Account to be utilized by the GCP provider -func WithGcpServiceAccountCredentials(path string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ServiceAccountCredentials = path - } -} - -// WithProviderDefaultLabels adds default_labels to the provider configuration for GCP (if labels are present) -func WithProviderDefaultLabels(labels map[string]interface{}) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ProviderDefaultLabels = labels - } -} - -// WithConfigOutputs Set Custom Terraform Outputs -func WithCustomOutputs(outputs []lwgenerate.HclOutput) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.CustomOutputs = outputs - } -} - -// WithExtraRootBlocks allows adding generic hcl blocks to the root `terraform{}` block -// this enables custom use cases -func WithExtraRootBlocks(blocks []*hclwrite.Block) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ExtraBlocksRootTerraform = blocks - } -} - -// WithExtraProviderArguments enables adding additional arguments into the `gcp` provider block -// this enables custom use cases -func WithExtraProviderArguments(arguments map[string]interface{}) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ExtraProviderArguments = arguments - } -} - -// WithExtraBlocks enables adding additional arbitrary blocks to the root hcl document -func WithExtraBlocks(blocks []*hclwrite.Block) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ExtraBlocks = blocks - } -} - -// WithLaceworkProfile Set the Lacework Profile to utilize when integrating -func WithLaceworkProfile(name string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.LaceworkProfile = name - } -} - -// WithOrganizationIntegration Set whether we configure as an Organization wide integration -func WithOrganizationIntegration(enabled bool) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.OrganizationIntegration = enabled - } -} - -// WithOrganizationId Set the Lacework organization ID to integrate with for an organization integration -func WithOrganizationId(id string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.GcpOrganizationId = id - } -} - -// WithProjectId Set the Lacework project ID that new resources should be created in -// (required for both project & org integration) -func WithProjectId(id string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.GcpProjectId = id - } -} - -// WithExistingServiceAccount Set an existing Service Account to be used by the Lacework Integration -func WithExistingServiceAccount(serviceAccountDetails *ExistingServiceAccountDetails) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ExistingServiceAccount = serviceAccountDetails - } -} - -// WithConfigurationIntegrationName Set the Config Integration name to be displayed on the Lacework UI -func WithConfigurationIntegrationName(name string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ConfigurationIntegrationName = name - } -} - -// WithAuditLogLabels set labels to be applied to ALL newly created Audit Log resources -func WithAuditLogLabels(labels map[string]string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.AuditLogLabels = labels - } -} - -// WithBucketLabels set labels to be applied to the newly created Audit Log Bucket -func WithBucketLabels(labels map[string]string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.BucketLabels = labels - } -} - -// WithPubSubSubscriptionLabels set labels to be applied to the newly created Audit Log PubSub -func WithPubSubSubscriptionLabels(labels map[string]string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.PubSubSubscriptionLabels = labels - } -} - -// WithPubSubTopicLabels set labels to be applied to the newly created Audit Log PubSub Topic -func WithPubSubTopicLabels(labels map[string]string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.PubSubTopicLabels = labels - } -} - -func WithCustomBucketName(name string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.CustomBucketName = name - } -} - -// WithBucketRegion Set the Region in which the Bucket should be created -func WithBucketRegion(region string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.BucketRegion = region - } -} - -// WithExistingLogBucketName Set the bucket Name of an existing Audit Log Bucket setup -func WithExistingLogBucketName(name string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ExistingLogBucketName = name - } -} - -// WithExistingLogSinkName Set the Topic ARN of an existing Audit Log setup -func WithExistingLogSinkName(name string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ExistingLogSinkName = name - } -} - -// WithEnableUBLA Enable force destroy of the bucket if it has stuff in it -func WithEnableUBLA(enable bool) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.EnableUBLA = enable - } -} - -// WithLogBucketLifecycleRuleAge Set the number of days to keep audit logs in Lacework GCS bucket before deleting -// Defaults to -1. Leave default to keep indefinitely. -func WithLogBucketLifecycleRuleAge(ruleAge int) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.LogBucketLifecycleRuleAge = ruleAge - } -} - -// WithAuditLogIntegrationName Set the Config Integration name to be displayed on the Lacework UI -func WithAuditLogIntegrationName(name string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.AuditLogIntegrationName = name - } -} - -func WithFoldersToInclude(folders []string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.FoldersToInclude = folders - } -} - -func WithFoldersToExclude(folders []string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.FoldersToExclude = folders - } -} - -func WithIncludeRootProjects(include bool) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.IncludeRootProjects = include - } -} - -func WithCustomFilter(filter string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.CustomFilter = filter - } -} - -func WithGoogleWorkspaceFilter(filter bool) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.GoogleWorkspaceFilter = filter - } -} - -func WithK8sFilter(filter bool) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.K8sFilter = filter - } -} - -func WithPrefix(prefix string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.Prefix = prefix - } -} - -func WithWaitTime(waitTime string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.WaitTime = waitTime - } -} - -func WithMultipleProject(projects []string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.Projects = projects - } -} - -func WithProjectFilterList(projectFilterList []string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.ProjectFilterList = projectFilterList - } -} - -func WithRegions(regions []string) GcpTerraformModifier { - return func(c *GenerateGcpTfConfigurationArgs) { - c.Regions = regions - } -} - -// Generate new Terraform code based on the supplied args. -func (args *GenerateGcpTfConfigurationArgs) Generate() (string, error) { - // Validate inputs - if err := args.validate(); err != nil { - return "", errors.Wrap(err, "invalid inputs") - } - - // Create blocks - requiredProviders, err := createRequiredProviders(false, args.ExtraBlocksRootTerraform) - if err != nil { - return "", errors.Wrap(err, "failed to generate required providers") - } - - gcpProvider, err := createGcpProvider(args.ExtraProviderArguments, - args.ServiceAccountCredentials, args.GcpProjectId, args.Regions, "", args.ProviderDefaultLabels) - if err != nil { - return "", errors.Wrap(err, "failed to generate gcp provider") - } - - laceworkProvider, err := createLaceworkProvider(args.LaceworkProfile) - if err != nil { - return "", errors.Wrap(err, "failed to generate lacework provider") - } - - agentlessModule, err := createAgentless(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate gcp agentless module") - } - - configurationModule, err := createConfiguration(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate gcp configuration module") - } - - auditLogModule, err := createAuditLog(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate gcp audit log module") - } - - outputBlocks := []*hclwrite.Block{} - for _, output := range args.CustomOutputs { - outputBlock, err := output.ToBlock() - if err != nil { - return "", errors.Wrap(err, "failed to add custom output") - } - outputBlocks = append(outputBlocks, outputBlock) - } - - // Render - hclBlocks := lwgenerate.CreateHclStringOutput( - lwgenerate.CombineHclBlocks( - requiredProviders, - gcpProvider, - laceworkProvider, - agentlessModule, - configurationModule, - auditLogModule, - outputBlocks, - args.ExtraBlocks, - ), - ) - return hclBlocks, nil -} - -func createRequiredProviders(useExistingRequiredProviders bool, - extraBlocks []*hclwrite.Block) (*hclwrite.Block, error) { - if useExistingRequiredProviders { - return nil, nil - } - return lwgenerate.CreateRequiredProvidersWithCustomBlocks( - extraBlocks, - lwgenerate.NewRequiredProvider( - "lacework", - lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), - lwgenerate.HclRequiredProviderWithVersion(lwgenerate.LaceworkProviderVersion), - ), - ) -} - -func createLaceworkProvider(laceworkProfile string) (*hclwrite.Block, error) { - if laceworkProfile != "" { - return lwgenerate.NewProvider( - "lacework", - lwgenerate.HclProviderWithAttributes(map[string]interface{}{"profile": laceworkProfile}), - ).ToBlock() - } - return nil, nil -} - -func createGcpProvider( - extraProviderArguments map[string]interface{}, - serviceAccountCredentials string, - projectId string, - regionsArg []string, - alias string, - providerDefaultLabels map[string]interface{}, -) ([]*hclwrite.Block, error) { - blocks := []*hclwrite.Block{} - - regions := append([]string{}, regionsArg...) - if len(regions) == 0 { - regions = append(regions, "") - } - - for _, region := range regions { - attrs := map[string]interface{}{} - - // set custom args before the required ones below to ensure expected behavior (i.e., no overrides) - for k, v := range extraProviderArguments { - attrs[k] = v - } - if serviceAccountCredentials != "" { - attrs["credentials"] = serviceAccountCredentials - } - - if projectId != "" { - attrs["project"] = projectId - } - - if alias != "" { - attrs["alias"] = alias - } - - if region != "" { - attrs["alias"] = region - attrs["region"] = region - } - - if len(providerDefaultLabels) != 0 { - attrs["default_labels"] = providerDefaultLabels - } - - modifiers := []lwgenerate.HclProviderModifier{ - lwgenerate.HclProviderWithAttributes(attrs), - } - - provider, err := lwgenerate.NewProvider( - "google", - modifiers...).ToBlock() - if err != nil { - return nil, err - } - - blocks = append(blocks, provider) - } - - return blocks, nil -} - -func createAgentless(args *GenerateGcpTfConfigurationArgs) ([]*hclwrite.Block, error) { - if !args.Agentless { - return nil, nil - } - - blocks := []*hclwrite.Block{} - - for i, region := range args.Regions { - moduleName := "lacework_gcp_agentless_scanning_global" - moduleDetails := []lwgenerate.HclModuleModifier{ - lwgenerate.HclModuleWithVersion(lwgenerate.GcpAgentlessVersion), - } - - attributes := map[string]interface{}{"regional": true} - if i == 0 { - attributes["global"] = true - if len(args.ProjectFilterList) > 0 { - attributes["project_filter_list"] = args.ProjectFilterList - } - if args.OrganizationIntegration { - attributes["integration_type"] = "ORGANIZATION" - } - if len(args.GcpOrganizationId) > 0 { - attributes["organization_id"] = args.GcpOrganizationId - } - } - if i > 0 { - moduleName = "lacework_gcp_agentless_scanning_region_" + region - attributes["global_module_reference"] = lwgenerate.CreateSimpleTraversal( - []string{"module", "lacework_gcp_agentless_scanning_global"}, - ) - } - - moduleDetails = append( - moduleDetails, - lwgenerate.HclModuleWithProviderDetails( - map[string]string{"google": fmt.Sprintf("google.%s", region)}, - ), - ) - - moduleDetails = append( - moduleDetails, - lwgenerate.HclModuleWithAttributes(attributes), - ) - - module, err := lwgenerate.NewModule( - moduleName, - lwgenerate.GcpAgentlessSource, - moduleDetails..., - ).ToBlock() - if err != nil { - return nil, err - } - - blocks = append(blocks, module) - } - - return blocks, nil -} - -func createConfiguration(args *GenerateGcpTfConfigurationArgs) ([]*hclwrite.Block, error) { - blocks := []*hclwrite.Block{} - if args.Configuration { - attributes := map[string]interface{}{} - moduleDetails := []lwgenerate.HclModuleModifier{} - - // default to using the project level module - configurationModuleName := "gcp_project_level_config" - if args.OrganizationIntegration { - // if organization integration is true, override configModuleName to use the organization level module - configurationModuleName = "gcp_organization_level_config" - attributes["org_integration"] = args.OrganizationIntegration - attributes["organization_id"] = args.GcpOrganizationId - - if len(args.FoldersToInclude) > 0 { - set := unique.StringSlice(args.FoldersToInclude) - sort.Strings(set) - attributes["folders_to_include"] = set - } - - if len(args.FoldersToExclude) > 0 { - set := unique.StringSlice(args.FoldersToExclude) - sort.Strings(set) - attributes["folders_to_exclude"] = set - - // Default true in gcp-audit-log TF module - if !args.IncludeRootProjects { - attributes["include_root_projects"] = args.IncludeRootProjects - } - } - } - - if args.ExistingServiceAccount != nil { - attributes["use_existing_service_account"] = true - attributes["service_account_name"] = args.ExistingServiceAccount.Name - attributes["service_account_private_key"] = args.ExistingServiceAccount.PrivateKey - } - - if args.ConfigurationIntegrationName != "" { - attributes["lacework_integration_name"] = args.ConfigurationIntegrationName - } - - if args.Prefix != "" { - attributes["prefix"] = args.Prefix - } - - if args.WaitTime != "" { - attributes["wait_time"] = args.WaitTime - } - - if len(args.Projects) > 0 { - value := make(map[string]string, len(args.Projects)) - for _, p := range args.Projects { - value[p] = p - } - moduleDetails = append(moduleDetails, lwgenerate.HclModuleWithForEach("project_id", value)) - } - - moduleDetails = append(moduleDetails, - lwgenerate.HclModuleWithAttributes(attributes), - ) - - // Regions is required when Agentless integration is enabled - // Use the first region name as the alias for Google provider if multiple regions are provided - if args.Agentless && len(args.Regions) > 0 { - moduleDetails = append( - moduleDetails, - lwgenerate.HclModuleWithProviderDetails( - map[string]string{"google": fmt.Sprintf("google.%s", args.Regions[0])}, - ), - ) - } - - moduleBlock, err := lwgenerate.NewModule( - configurationModuleName, - lwgenerate.GcpConfigSource, - append(moduleDetails, lwgenerate.HclModuleWithVersion(lwgenerate.GcpConfigVersion))..., - ).ToBlock() - - if err != nil { - return nil, err - } - blocks = append(blocks, moduleBlock) - - } - - return blocks, nil -} - -func createAuditLog(args *GenerateGcpTfConfigurationArgs) (*hclwrite.Block, error) { - if args.AuditLog { - attributes := map[string]interface{}{} - - moduleDetails := []lwgenerate.HclModuleModifier{} - - if args.PubSubSubscriptionLabels != nil { - attributes["pubsub_subscription_labels"] = args.PubSubSubscriptionLabels - } - - if args.PubSubTopicLabels != nil { - attributes["pubsub_topic_labels"] = args.PubSubTopicLabels - } - - if args.ExistingLogBucketName != "" { - attributes["existing_bucket_name"] = args.ExistingLogBucketName - } else { - if args.LogBucketLifecycleRuleAge != -1 { - attributes["lifecycle_rule_age"] = args.LogBucketLifecycleRuleAge - } - - if args.AuditLogLabels != nil { - attributes["labels"] = args.AuditLogLabels - } - - if args.BucketLabels != nil { - attributes["bucket_labels"] = args.BucketLabels - } - - // Default true in gcp-audit-log TF module - if !args.EnableUBLA { - attributes["enable_ubla"] = args.EnableUBLA - } - - if args.CustomBucketName != "" { - attributes["custom_bucket_name"] = args.CustomBucketName - } - - if args.BucketRegion != "" { - attributes["bucket_region"] = args.BucketRegion - } - } - - if args.ExistingLogSinkName != "" { - attributes["existing_sink_name"] = args.ExistingLogSinkName - } - - // default to using the project level module - auditLogModuleName := "gcp_project_audit_log" - - configurationModuleName := "gcp_project_level_config" - if args.OrganizationIntegration { - // if organization integration is true, override configModuleName to use the organization level module - configurationModuleName = "gcp_organization_level_config" - auditLogModuleName = "gcp_organization_level_audit_log" - // Determine if this is the a pub-sub audit log - if args.UsePubSubAudit { - attributes["integration_type"] = "ORGANIZATION" - } else { - attributes["org_integration"] = args.OrganizationIntegration - } - attributes["organization_id"] = args.GcpOrganizationId - - if len(args.FoldersToInclude) > 0 { - set := unique.StringSlice(args.FoldersToInclude) - sort.Strings(set) - attributes["folders_to_include"] = set - } - - if len(args.FoldersToExclude) > 0 { - set := unique.StringSlice(args.FoldersToExclude) - sort.Strings(set) - attributes["folders_to_exclude"] = set - - // Default true in gcp-audit-log TF module - if !args.IncludeRootProjects { - attributes["include_root_projects"] = args.IncludeRootProjects - } - } - } - - if args.ExistingServiceAccount == nil && args.Configuration { - attributes["use_existing_service_account"] = true - - cfgModuleName := configurationModuleName - - if len(args.Projects) > 0 { - cfgModuleName = fmt.Sprintf("%s[each.key]", cfgModuleName) - } - - attributes["service_account_name"] = lwgenerate.CreateSimpleTraversal( - []string{"module", cfgModuleName, "service_account_name"}, - ) - attributes["service_account_private_key"] = lwgenerate.CreateSimpleTraversal( - []string{"module", cfgModuleName, "service_account_private_key"}, - ) - } - - if args.ExistingServiceAccount != nil { - attributes["use_existing_service_account"] = true - attributes["service_account_name"] = args.ExistingServiceAccount.Name - attributes["service_account_private_key"] = args.ExistingServiceAccount.PrivateKey - } - - if args.AuditLogIntegrationName != "" { - attributes["lacework_integration_name"] = args.AuditLogIntegrationName - } - - if args.CustomFilter != "" { - attributes["custom_filter"] = args.CustomFilter - } - - // Default true in gcp-audit-log TF module - if !args.GoogleWorkspaceFilter { - attributes["google_workspace_filter"] = args.GoogleWorkspaceFilter - } - - // Default true in gcp-audit-log TF module - if !args.K8sFilter { - attributes["k8s_filter"] = args.K8sFilter - } - - if args.Prefix != "" { - attributes["prefix"] = args.Prefix - } - - if args.WaitTime != "" { - attributes["wait_time"] = args.WaitTime - } - - if len(args.Projects) > 0 { - value := make(map[string]string) - for _, p := range args.Projects { - value[p] = p - } - moduleDetails = append(moduleDetails, lwgenerate.HclModuleWithForEach("project_id", value)) - } - - moduleDetails = append(moduleDetails, - lwgenerate.HclModuleWithAttributes(attributes), - ) - - // Regions is required when Agentless integration is enabled - // Use the first region name as the alias for Google provider if multiple regions are provided - if args.Agentless && len(args.Regions) > 0 { - moduleDetails = append( - moduleDetails, - lwgenerate.HclModuleWithProviderDetails( - map[string]string{"google": fmt.Sprintf("google.%s", args.Regions[0])}, - ), - ) - } - - return lwgenerate.NewModule( - auditLogModuleName, - getAuditLogModule(args.UsePubSubAudit), - append(moduleDetails, lwgenerate.HclModuleWithVersion(getAuditLogVersion(args.UsePubSubAudit)))..., - ).ToBlock() - } - - return nil, nil -} - -func getAuditLogModule(isPubSub bool) string { - if isPubSub { - return lwgenerate.GcpPubSubAuditLog - } - return lwgenerate.GcpAuditLogSource -} - -func getAuditLogVersion(isPubSub bool) string { - if isPubSub { - return lwgenerate.GcpPubSubAuditLogVersion - } - return lwgenerate.GcpAuditLogVersion -} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gke.go b/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gke.go deleted file mode 100644 index 0413b3ca9..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/gke.go +++ /dev/null @@ -1,256 +0,0 @@ -package gcp - -import ( - "fmt" - - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/lacework/go-sdk/lwgenerate" - "github.com/pkg/errors" -) - -type GenerateGkeTfConfigurationArgs struct { - UseExistingRequiredProviders bool - GcpProviderAlias string - ExistingServiceAccount *ServiceAccount - ExistingSinkName string - IntegrationName string - Labels map[string]string - LaceworkProfile string - OrganizationId string - OrganizationIntegration bool - Prefix string - ProjectId string - PubSubSubscriptionLabels map[string]string - PubSubTopicLabels map[string]string - ServiceAccountCredentials string - WaitTime string - // Default GCP Provider labels - ProviderDefaultLabels map[string]interface{} - // Add custom blocks to the root `terraform{}` block. Can be used for advanced configuration. Things like backend, etc - ExtraBlocksRootTerraform []*hclwrite.Block - // ExtraProviderArguments allows adding more arguments to the provider block as needed (custom use cases) - ExtraProviderArguments map[string]interface{} -} - -type Modifier func(c *GenerateGkeTfConfigurationArgs) - -func (args *GenerateGkeTfConfigurationArgs) Generate() (string, error) { - if err := args.validate(); err != nil { - return "", errors.Wrap(err, "invalid inputs") - } - - requiredProviders, err := createRequiredProviders(args.UseExistingRequiredProviders, args.ExtraBlocksRootTerraform) - if err != nil { - return "", errors.Wrap(err, "failed to generate required providers") - } - - gcpProvider, err := createGcpProvider( - args.ExtraProviderArguments, - args.ServiceAccountCredentials, - args.ProjectId, - []string{}, - args.GcpProviderAlias, - args.ProviderDefaultLabels, - ) - if err != nil { - return "", errors.Wrap(err, "failed to generate gcp provider") - } - - laceworkProvider, err := createLaceworkProvider(args.LaceworkProfile) - if err != nil { - return "", errors.Wrap(err, "failed to generate lacework provider") - } - - gkeAuditLogModule, err := createGKEAuditLog(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate GKE Audit Log module") - } - - hclBlocks := lwgenerate.CreateHclStringOutput( - lwgenerate.CombineHclBlocks( - requiredProviders, - gcpProvider, - laceworkProvider, - gkeAuditLogModule), - ) - return hclBlocks, nil -} - -func (args *GenerateGkeTfConfigurationArgs) validate() error { - if args.OrganizationIntegration && args.OrganizationId == "" { - return errors.New("an Organization ID must be provided for an Organization Integration") - } - - if !args.OrganizationIntegration && args.OrganizationId != "" { - return errors.New("to provide an Organization ID, Organization Integration must be true") - } - - if args.ExistingServiceAccount != nil { - if args.ExistingServiceAccount.Name == "" || - args.ExistingServiceAccount.PrivateKey == "" { - return errors.New( - "when using an existing Service Account, existing name, and base64 encoded " + - "JSON Private Key fields all must be set", - ) - } - } - - return nil -} - -func NewGkeTerraform(mods ...Modifier) *GenerateGkeTfConfigurationArgs { - config := &GenerateGkeTfConfigurationArgs{} - - for _, m := range mods { - m(config) - } - - return config -} - -func WithGkeExistingRequiredProviders() Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.UseExistingRequiredProviders = true - } -} - -func WithGkeGcpProviderAlias(alias string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.GcpProviderAlias = alias - } -} - -func WithGkeExistingServiceAccount(serviceAccount *ServiceAccount) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.ExistingServiceAccount = serviceAccount - } -} - -func WithGkeExistingSinkName(name string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.ExistingSinkName = name - } -} - -func WithGkeIntegrationName(name string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.IntegrationName = name - } -} - -func WithGkeLabels(labels map[string]string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.Labels = labels - } -} - -func WithGkeLaceworkProfile(name string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.LaceworkProfile = name - } -} - -func WithGkeOrganizationId(id string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.OrganizationId = id - } -} - -func WithGkeOrganizationIntegration(enabled bool) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.OrganizationIntegration = enabled - } -} - -func WithGkePrefix(prefix string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.Prefix = prefix - } -} - -func WithGkeProjectId(id string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.ProjectId = id - } -} - -func WithGkePubSubSubscriptionLabels(labels map[string]string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.PubSubSubscriptionLabels = labels - } -} - -func WithGkePubSubTopicLabels(labels map[string]string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.PubSubTopicLabels = labels - } -} - -func WithGkeServiceAccountCredentials(path string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.ServiceAccountCredentials = path - } -} - -func WithGkeWaitTime(waitTime string) Modifier { - return func(c *GenerateGkeTfConfigurationArgs) { - c.WaitTime = waitTime - } -} - -func createGKEAuditLog(args *GenerateGkeTfConfigurationArgs) (*hclwrite.Block, error) { - var level string - attributes := map[string]interface{}{} - - if args.OrganizationIntegration { - level = "organization" - attributes["integration_type"] = "ORGANIZATION" - attributes["organization_id"] = args.OrganizationId - - } else { - level = "project" - attributes["integration_type"] = "PROJECT" - } - - if args.ExistingSinkName != "" { - attributes["existing_sink_name"] = args.ExistingSinkName - } - - if args.ExistingServiceAccount != nil { - attributes["use_existing_service_account"] = true - attributes["service_account_name"] = args.ExistingServiceAccount.Name - attributes["service_account_private_key"] = args.ExistingServiceAccount.PrivateKey - } - - if args.IntegrationName != "" { - attributes["lacework_integration_name"] = args.IntegrationName - } - - if args.Prefix != "" { - attributes["prefix"] = args.Prefix - } - - if args.WaitTime != "" { - attributes["wait_time"] = args.WaitTime - } - - moduleDetails := []lwgenerate.HclModuleModifier{ - lwgenerate.HclModuleWithAttributes(attributes), - lwgenerate.HclModuleWithVersion(lwgenerate.GcpGKEAuditLogVersion), - } - - if args.GcpProviderAlias != "" { - moduleDetails = append( - moduleDetails, - lwgenerate.HclModuleWithProviderDetails( - map[string]string{"google": fmt.Sprintf("google.%s", args.GcpProviderAlias)}, - ), - ) - } - - return lwgenerate.NewModule( - fmt.Sprintf("gcp_%s_level_gke_audit_log", level), - lwgenerate.GcpGKEAuditLogSource, - moduleDetails..., - ).ToBlock() -} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/service_account.go b/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/service_account.go deleted file mode 100644 index aba498180..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/gcp/service_account.go +++ /dev/null @@ -1,110 +0,0 @@ -package gcp - -import ( - "encoding/base64" - "encoding/json" - "io" - "os" - - "github.com/lacework/go-sdk/internal/file" - "github.com/pkg/errors" -) - -type ServiceAccount struct { - Name string - PrivateKey string -} - -func NewServiceAccount(name string, privateKey string) *ServiceAccount { - return &ServiceAccount{ - Name: name, - PrivateKey: privateKey, - } -} - -func (s *ServiceAccount) IsPartial() bool { - if s == nil { - return false - } - - if s.Name == "" && s.PrivateKey == "" { - return false - } - - if s.Name != "" && s.PrivateKey != "" { - return false - } - - return true -} - -func ValidateServiceAccountCredentialsFile(credFile string) error { - if file.FileExists(credFile) { - jsonFile, err := os.Open(credFile) // guardrails-disable-line - if err != nil { - return errors.Wrap(err, "issue opening credentials file") - } - defer jsonFile.Close() - - byteValue, err := io.ReadAll(jsonFile) - if err != nil { - return errors.Wrap(err, "unable to parse credentials file") - } - - var credFileContent map[string]interface{} - err = json.Unmarshal(byteValue, &credFileContent) - if err != nil { - return errors.Wrap(err, "unable to parse credentials file") - } - credFileContent, valid := ValidateSaCredFileContent(credFileContent) - if !valid { - return errors.New("invalid Service Account credentials file. " + - "The private_key and client_email fields MUST be present.") - } - } else { - return errors.New("provided credentials file does not exist") - } - return nil -} - -func ValidateSaCredFileContent(credFileContent map[string]interface{}) (map[string]interface{}, bool) { - if credFileContent["private_key"] != nil && credFileContent["client_email"] != nil { - privateKey, ok := credFileContent["private_key"].(string) - if !ok { - return credFileContent, false - } - err := ValidateStringIsBase64(privateKey) - if err != nil { - privateKey := base64.StdEncoding.EncodeToString([]byte(privateKey)) - credFileContent["private_key"] = privateKey - return credFileContent, true - } - } - return credFileContent, false -} - -func ValidateStringIsBase64(val interface{}) error { - switch value := val.(type) { - case string: - _, err := base64.StdEncoding.DecodeString(value) - if err != nil { - return errors.New("provided private key is not base64 encoded") - } - default: - return errors.New("value must be a string") - } - - return nil -} - -func ValidateServiceAccountCredentials(val interface{}) error { - if value, ok := val.(string); ok { - if value == "" { - return nil - } else { - return ValidateServiceAccountCredentialsFile(value) - } - } - - return errors.New("value must be a string") -} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/hcl.go b/vendor/github.com/lacework/go-sdk/lwgenerate/hcl.go deleted file mode 100644 index 5c796ae70..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/hcl.go +++ /dev/null @@ -1,662 +0,0 @@ -// A package that generates Lacework deployment code for multiple cloud providers. -package lwgenerate - -import ( - "errors" - "fmt" - "sort" - - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/zclconf/go-cty/cty" -) - -type HclRequiredProvider struct { - name string - source string - version string -} - -func (p *HclRequiredProvider) Source() string { - return p.source -} - -func (p *HclRequiredProvider) Version() string { - return p.version -} - -func (p *HclRequiredProvider) Name() string { - return p.name -} - -type HclRequiredProviderModifier func(p *HclRequiredProvider) - -func HclRequiredProviderWithSource(source string) HclRequiredProviderModifier { - return func(p *HclRequiredProvider) { - p.source = source - } -} - -func HclRequiredProviderWithVersion(version string) HclRequiredProviderModifier { - return func(p *HclRequiredProvider) { - p.version = version - } -} - -func NewRequiredProvider(name string, mods ...HclRequiredProviderModifier) *HclRequiredProvider { - provider := &HclRequiredProvider{name: name} - for _, m := range mods { - m(provider) - } - return provider -} - -type HclProvider struct { - // Required, provider name - name string - - // Optional. Extra properties for this module. Can supply string, bool, int, or map[string]interface{} as values - attributes map[string]interface{} - - // optional. Generic blocks - blocks []*hclwrite.Block -} - -func (p *HclProvider) ToBlock() (*hclwrite.Block, error) { - block, err := HclCreateGenericBlock("provider", []string{p.name}, p.attributes) - if err != nil { - return nil, err - } - - if p.blocks != nil { - for _, b := range p.blocks { - block.Body().AppendNewline() - block.Body().AppendBlock(b) - } - } - - return block, nil -} - -type HclProviderModifier func(p *HclProvider) - -// NewProvider Create a new HCL Provider -func NewProvider(name string, mods ...HclProviderModifier) *HclProvider { - provider := &HclProvider{name: name} - for _, m := range mods { - m(provider) - } - return provider -} - -func HclProviderWithAttributes(attrs map[string]interface{}) HclProviderModifier { - return func(p *HclProvider) { - p.attributes = attrs - } -} - -// HclProviderWithGenericBlocks sets the generic blocks within the provider -func HclProviderWithGenericBlocks(blocks ...*hclwrite.Block) HclProviderModifier { - return func(p *HclProvider) { - p.blocks = blocks - } -} - -type ForEach struct { - key string - value map[string]string -} - -type HclOutput struct { - // required, name of the resultant output - name string - - // required, converted into a traversal - // e.g. []string{"a", "b", "c"} as input results in traversal having value a.b.c - value []string - - // optional - description string -} - -func (m *HclOutput) ToBlock() (*hclwrite.Block, error) { - if m.value == nil { - return nil, errors.New("value must be supplied") - } - - attributes := map[string]interface{}{ - "value": CreateSimpleTraversal(m.value), - } - - if m.description != "" { - attributes["description"] = m.description - } - - block, err := HclCreateGenericBlock( - "output", - []string{m.name}, - attributes, - ) - if err != nil { - return nil, err - } - - return block, nil -} - -// NewOutput Create a provider statement in the HCL output -func NewOutput(name string, value []string, description string) *HclOutput { - return &HclOutput{name: name, description: description, value: value} -} - -type HclModule struct { - // Required, module name - name string - - // Required, source for this module - source string - - // Required, version - version string - - // Optional. Extra properties for this module. Can supply string, bool, int, or map[string]interface{} as values - attributes map[string]interface{} - - // Optional. Provide a map of strings. Creates an instance of the module block for each item in the map, with the - // map keys assigned to the key field. - forEach *ForEach - - // Optional. Provider details to override defaults. These values must be supplied as strings, and raw values will be - // accepted. Unfortunately map[string]hcl.Traversal is not a format that is supported by hclwrite.SetAttributeValue - // today so we must work around it (https://github.com/hashicorp/hcl/issues/347). - providerDetails map[string]string -} - -type HclModuleModifier func(p *HclModule) - -// NewModule Create a provider statement in the HCL output -func NewModule(name string, source string, mods ...HclModuleModifier) *HclModule { - module := &HclModule{name: name, source: source} - for _, m := range mods { - m(module) - } - return module -} - -// HclModuleWithAttributes Used to set parameters within the module usage -func HclModuleWithAttributes(attrs map[string]interface{}) HclModuleModifier { - return func(p *HclModule) { - p.attributes = attrs - } -} - -// HclModuleWithVersion Used to set the version of a module source to use -func HclModuleWithVersion(version string) HclModuleModifier { - return func(p *HclModule) { - p.version = version - } -} - -// HclModuleWithProviderDetails Used to provide additional provider details to a given module. -// -// Note: The values supplied become traversals -// -// e.g. https://www.terraform.io/docs/language/modules/develop/providers.html#passing-providers-explicitly -func HclModuleWithProviderDetails(providerDetails map[string]string) HclModuleModifier { - return func(p *HclModule) { - p.providerDetails = providerDetails - } -} - -func HclModuleWithForEach(key string, value map[string]string) HclModuleModifier { - return func(p *HclModule) { - p.forEach = &ForEach{key, value} - } -} - -// ToBlock Create hclwrite.Block for module -func (m *HclModule) ToBlock() (*hclwrite.Block, error) { - if m.attributes == nil { - m.attributes = make(map[string]interface{}) - } - if m.source != "" { - m.attributes["source"] = m.source - - } - if m.version != "" { - m.attributes["version"] = m.version - } - block, err := HclCreateGenericBlock( - "module", - []string{m.name}, - m.attributes, - ) - if err != nil { - return nil, err - } - - if m.forEach != nil { - block.Body().AppendNewline() - - value, err := convertTypeToCty(m.forEach.value) - if err != nil { - return nil, err - } - block.Body().SetAttributeValue("for_each", value) - - block.Body().SetAttributeRaw(m.forEach.key, createForEachKey()) - } - - if m.providerDetails != nil { - block.Body().AppendNewline() - block.Body().SetAttributeRaw("providers", CreateMapTraversalTokens(m.providerDetails)) - } - - return block, nil -} - -// ToResourceBlock Create hclwrite.Block for resource -func (m *HclResource) ToResourceBlock() (*hclwrite.Block, error) { - if m.attributes == nil { - m.attributes = make(map[string]interface{}) - } - - block, err := HclCreateGenericBlock( - "resource", - []string{m.rType, m.name}, - m.attributes, - ) - if err != nil { - return nil, err - } - - if m.providerDetails != nil { - block.Body().AppendNewline() - block.Body().SetAttributeTraversal("provider", CreateSimpleTraversal(m.providerDetails)) - } - - if m.blocks != nil { - for _, b := range m.blocks { - block.Body().AppendNewline() - block.Body().AppendBlock(b) - } - } - - return block, nil -} - -type HclResource struct { - // Required, resourceType - rType string - - // Required, resource name - name string - - // Optional. Extra properties for this resource. Can supply string, bool, int, or map[string]interface{} as values - attributes map[string]interface{} - - // Optional. Provider details to override defaults. These values must be supplied as strings, and raw values will be - // accepted. Unfortunately map[string]hcl.Traversal is not a format that is supported by hclwrite.SetAttributeValue - // today so we must work around it (https://github.com/hashicorp/hcl/issues/347). - providerDetails []string - - // optional. Generic blocks - blocks []*hclwrite.Block -} - -type HclResourceModifier func(p *HclResource) - -// NewResource Create a provider statement in the HCL output -func NewResource(rType string, name string, mods ...HclResourceModifier) *HclResource { - resource := &HclResource{rType: rType, name: name} - for _, m := range mods { - m(resource) - } - return resource -} - -// HclResourceWithAttributesAndProviderDetails Used to set parameters within the resource usage -func HclResourceWithAttributesAndProviderDetails(attrs map[string]interface{}, - providerDetails []string) HclResourceModifier { - return func(p *HclResource) { - p.attributes = attrs - p.providerDetails = providerDetails - } -} - -// HclResourceWithGenericBlocks sets the generic blocks within the resource -func HclResourceWithGenericBlocks(blocks ...*hclwrite.Block) HclResourceModifier { - return func(p *HclResource) { - p.blocks = blocks - } -} - -// Convert standard value types to cty.Value -// -// All values used in hclwrite.Block(s) must be cty.Value or a cty.Traversal. This function performs that conversion -// for standard types (non-traversal) -func convertTypeToCty(value interface{}) (cty.Value, error) { - switch v := value.(type) { - case string: - return cty.StringVal(v), nil - case int: - return cty.NumberIntVal(int64(v)), nil - case bool: - return cty.BoolVal(v), nil - case map[string]string: - valueMap := map[string]cty.Value{} - for key, val := range v { - valueMap[key] = cty.StringVal(val) - } - return cty.MapVal(valueMap), nil - case map[string]interface{}: - valueMap := map[string]cty.Value{} - for key, val := range v { - convertedValue, err := convertTypeToCty(val) - if err != nil { - return cty.NilVal, err - } - valueMap[key] = convertedValue - } - return cty.MapVal(valueMap), nil - case []map[string]interface{}: - values := []cty.Value{} - for _, item := range v { - valueMap := map[string]cty.Value{} - for key, val := range item { - convertedValue, err := convertTypeToCty(val) - if err != nil { - return cty.NilVal, err - } - valueMap[key] = convertedValue - } - values = append(values, cty.ObjectVal(valueMap)) - } - return cty.ListVal(values), nil - case []string: - valueSlice := []cty.Value{} - for _, s := range v { - valueSlice = append(valueSlice, cty.StringVal(s)) - } - return cty.ListVal(valueSlice), nil - case []interface{}: - valueSlice := []cty.Value{} - for _, i := range v { - newVal, err := convertTypeToCty(i) - if err != nil { - return cty.Value{}, err - } - valueSlice = append(valueSlice, newVal) - } - return cty.TupleVal(valueSlice), nil - default: - return cty.NilVal, errors.New("unknown attribute value type") - } -} - -// Used to set block attribute values based on attribute value interface type -// -// hclwrite.Block attributes use cty.Value, hclwrite.Tokens or can be traversals, this function -// determines what type of value is being used and builds the block accordingly -func setBlockAttributeValue(block *hclwrite.Block, key string, val interface{}) error { - switch v := val.(type) { - case hcl.Traversal: - block.Body().SetAttributeTraversal(key, v) - case string, int, bool, []string, []interface{}: - value, err := convertTypeToCty(v) - if err != nil { - return err - } - block.Body().SetAttributeValue(key, value) - case []map[string]interface{}: - values := []cty.Value{} - for _, item := range v { - valueMap := map[string]cty.Value{} - for key, val := range item { - convertedValue, err := convertTypeToCty(val) - if err != nil { - return err - } - valueMap[key] = convertedValue - } - values = append(values, cty.ObjectVal(valueMap)) - } - - if !cty.CanListVal(values) { - return errors.New( - "setBlockAttributeValue: Values can not be coalesced into a single List due to inconsistent element types", - ) - } - block.Body().SetAttributeValue(key, cty.ListVal(values)) - case map[string]interface{}: - data := map[string]cty.Value{} - for attrKey, attrVal := range v { - value, err := convertTypeToCty(attrVal) - if err != nil { - return err - } - data[attrKey] = value - } - block.Body().SetAttributeValue(key, cty.ObjectVal(data)) - case map[string]string: - value, err := convertTypeToCty(v) - if err != nil { - return err - } - block.Body().SetAttributeValue(key, value) - case hclwrite.Tokens: - block.Body().SetAttributeRaw(key, v) - default: - return fmt.Errorf("setBlockAttributeValue: unknown type for key: %s", key) - } - - return nil -} - -// HclCreateGenericBlock Helper to create various types of new hclwrite.Block using generic inputs -func HclCreateGenericBlock(hcltype string, labels []string, attr map[string]interface{}) (*hclwrite.Block, error) { - block := hclwrite.NewBlock(hcltype, labels) - - // Source and version require some special handling, should go at the top of a block declaration - sourceFound := false - versionFound := false - - // We need/want to guarantee the ordering of the attributes, do that here - var keys []string - for k := range attr { - switch k { - case "source": - sourceFound = true - case "version": - versionFound = true - default: - keys = append(keys, k) - } - } - sort.Strings(keys) - - if sourceFound || versionFound { - var newKeys []string - if sourceFound { - newKeys = append(newKeys, "source") - } - if versionFound { - newKeys = append(newKeys, "version") - } - keys = append(newKeys, keys...) - } - - // Write block data - for _, key := range keys { - val := attr[key] - if err := setBlockAttributeValue(block, key, val); err != nil { - return nil, err - } - } - - return block, nil -} - -// Create tokens for map of traversals. Used as a workaround for writing complex types where the built-in -// SetAttributeValue won't work -func CreateMapTraversalTokens(input map[string]string) hclwrite.Tokens { - // Sort input - var keys []string - for k := range input { - keys = append(keys, k) - } - sort.Strings(keys) - - tokens := hclwrite.Tokens{ - {Type: hclsyntax.TokenOBrace, Bytes: []byte("{"), SpacesBefore: 1}, - {Type: hclsyntax.TokenNewline, Bytes: []byte("\n")}, - } - - for _, k := range keys { - tokens = append(tokens, []*hclwrite.Token{ - {Type: hclsyntax.TokenStringLit, Bytes: []byte(k)}, - {Type: hclsyntax.TokenEqual, Bytes: []byte("=")}, - {Type: hclsyntax.TokenStringLit, Bytes: []byte(" " + input[k]), SpacesBefore: 1}, - {Type: hclsyntax.TokenNewline, Bytes: []byte("\n")}, - }...) - } - - tokens = append(tokens, []*hclwrite.Token{ - {Type: hclsyntax.TokenNewline}, - {Type: hclsyntax.TokenCBrace, Bytes: []byte("}")}, - }...) - - return tokens -} - -// Create tokens for the for_each meta-argument -func createForEachKey() hclwrite.Tokens { - return hclwrite.Tokens{ - {Type: hclsyntax.TokenStringLit, Bytes: []byte(" each.key"), SpacesBefore: 1}, - } -} - -// CreateHclStringOutput Convert blocks to a string -func CreateHclStringOutput(blocks []*hclwrite.Block) string { - file := hclwrite.NewEmptyFile() - body := file.Body() - blockCount := len(blocks) - 1 - - for i, b := range blocks { - if b != nil { - body.AppendBlock(b) - - // If this is not the last block, add a new line to provide spacing - if i < blockCount { - body.AppendNewline() - } - } - } - return string(file.Bytes()) -} - -// rootTerraformBlock is a helper that creates the literal `terraform{}` hcl block -func rootTerraformBlock() (*hclwrite.Block, error) { - return HclCreateGenericBlock("terraform", nil, nil) -} - -// createRequiredProviders is a helper that creates the `required_providers` hcl block -func createRequiredProviders(providers ...*HclRequiredProvider) (*hclwrite.Block, error) { - providerDetails := map[string]interface{}{} - for _, provider := range providers { - details := map[string]interface{}{} - if provider.Source() != "" { - details["source"] = provider.Source() - } - if provider.Version() != "" { - details["version"] = provider.Version() - } - providerDetails[provider.Name()] = details - } - - requiredProviders, err := HclCreateGenericBlock("required_providers", nil, providerDetails) - if err != nil { - return nil, err - } - - return requiredProviders, nil -} - -// CreateRequiredProviders Create required providers block -func CreateRequiredProviders(providers ...*HclRequiredProvider) (*hclwrite.Block, error) { - block, err := rootTerraformBlock() - if err != nil { - return nil, err - } - - requiredProviders, err := createRequiredProviders(providers...) - if err != nil { - return nil, err - } - - block.Body().AppendBlock(requiredProviders) - return block, nil -} - -// CreateRequiredProviders Create required providers block -func CreateRequiredProvidersWithCustomBlocks( - blocks []*hclwrite.Block, - providers ...*HclRequiredProvider, -) (*hclwrite.Block, error) { - block, err := rootTerraformBlock() - if err != nil { - return nil, err - } - - requiredProviders, err := createRequiredProviders(providers...) - if err != nil { - return nil, err - } - - block.Body().AppendBlock(requiredProviders) - for _, customBlock := range blocks { - block.Body().AppendBlock(customBlock) - } - - return block, nil -} - -// CreateSimpleTraversal helper to create a hcl.Traversal in the order of supplied []string -// -// e.g. []string{"a", "b", "c"} as input results in traversal having value a.b.c -func CreateSimpleTraversal(input []string) hcl.Traversal { - var traverser []hcl.Traverser - - for i, val := range input { - if i == 0 { - traverser = append(traverser, hcl.TraverseRoot{Name: val}) - } else { - traverser = append(traverser, hcl.TraverseAttr{Name: val}) - } - } - return traverser -} - -// CombineHclBlocks Simple helper to combine multiple blocks (or slices of blocks) into a -// single slice to be rendered to string -func CombineHclBlocks(results ...interface{}) []*hclwrite.Block { - blocks := []*hclwrite.Block{} - // Combine all blocks into single flat slice - for _, result := range results { - switch v := result.(type) { - case *hclwrite.Block: - if v != nil { - blocks = append(blocks, v) - } - case []*hclwrite.Block: - if len(v) > 0 { - blocks = append(blocks, v...) - } - default: - continue - } - } - - return blocks -} diff --git a/vendor/github.com/lacework/go-sdk/lwgenerate/oci/oci.go b/vendor/github.com/lacework/go-sdk/lwgenerate/oci/oci.go deleted file mode 100644 index 433e6e519..000000000 --- a/vendor/github.com/lacework/go-sdk/lwgenerate/oci/oci.go +++ /dev/null @@ -1,177 +0,0 @@ -package oci - -import ( - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/lacework/go-sdk/lwgenerate" - "github.com/pkg/errors" -) - -type GenerateOciTfConfigurationArgs struct { - // Should we configure CSPM integration in LW? - Config bool - - // Optional name for config - ConfigName string - - // Lacework profile to use - LaceworkProfile string - - // Tenant OCID - TenantOcid string - - // OCI user email - OciUserEmail string -} - -type OciTerraformModifier func(c *GenerateOciTfConfigurationArgs) - -const ( - LaceworkProviderVersion = ">= 1.9.0" -) - -// Set the Lacework profile to use for integration -func WithLaceworkProfile(name string) OciTerraformModifier { - return func(c *GenerateOciTfConfigurationArgs) { - c.LaceworkProfile = name - } -} - -// Set the name Lacework will use for the name -func WithConfigName(name string) OciTerraformModifier { - return func(c *GenerateOciTfConfigurationArgs) { - c.ConfigName = name - } -} - -// Set the OCID of the tenant to be integrated -func WithTenantOcid(ocid string) OciTerraformModifier { - return func(c *GenerateOciTfConfigurationArgs) { - c.TenantOcid = ocid - } -} - -// Set the email for the OCI user created for the integration -func WithUserEmail(email string) OciTerraformModifier { - return func(c *GenerateOciTfConfigurationArgs) { - c.OciUserEmail = email - } -} - -// NewTerraform returns an instance of the GenerateOciTfConfigurationArgs struct -// -// Note: Additional configuration details may be set using modifiers of the OciTerraformModifier type -// -// Basic usage: -// Initialize a new OciTerraformModifier struct then use generate to -// create a string output of the required HCL. -// -// hcl, err := aws.NewTerraform( -// true, -// oci.WithTenancyOcid("ocid1.tenancy...abc"), -// oci.WithUserEmail("a@b.c"), -// ).Generate() -func NewTerraform(enableConfig bool, mods ...OciTerraformModifier, -) *GenerateOciTfConfigurationArgs { - config := &GenerateOciTfConfigurationArgs{Config: enableConfig} - for _, m := range mods { - m(config) - } - return config -} - -// Generate new Terraform code based on the supplied args. -func (args *GenerateOciTfConfigurationArgs) Generate() (string, error) { - // Validate inputs - if err := args.validate(); err != nil { - return "", errors.Wrap(err, "invalid inputs") - } - - // Required providers block - requiredProviders, err := createRequiredProviders() - if err != nil { - return "", errors.Wrap(err, "failed to generate required providers") - } - - // provider lacework block - laceworkProvider, err := createLaceworkProvider(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate lacework provider") - } - - configModule, err := createConfig(args) - if err != nil { - return "", errors.Wrap(err, "failed to generate oci config module") - } - - // render HCL - hclBlocks := lwgenerate.CreateHclStringOutput( - lwgenerate.CombineHclBlocks( - requiredProviders, - laceworkProvider, - configModule, - ), - ) - return hclBlocks, nil -} - -// Ensure all combinations of inputs our valid for supported spec -func (args *GenerateOciTfConfigurationArgs) validate() error { - if !args.Config { - return errors.New("config integration must be enabled to continue") - } - - if args.TenantOcid == "" { - return errors.New("tenant OCID must be set") - } - - if args.OciUserEmail == "" { - return errors.New("OCI user email must be set") - } - - return nil -} - -func createRequiredProviders() (*hclwrite.Block, error) { - return lwgenerate.CreateRequiredProviders( - lwgenerate.NewRequiredProvider("lacework", - lwgenerate.HclRequiredProviderWithSource(lwgenerate.LaceworkProviderSource), - lwgenerate.HclRequiredProviderWithVersion(LaceworkProviderVersion), - ), - ) -} - -func createLaceworkProvider(args *GenerateOciTfConfigurationArgs) (*hclwrite.Block, error) { - if args.LaceworkProfile != "" { - return lwgenerate.NewProvider( - "lacework", - lwgenerate.HclProviderWithAttributes(map[string]interface{}{"profile": args.LaceworkProfile}), - ).ToBlock() - } - return nil, nil -} - -func createConfig(args *GenerateOciTfConfigurationArgs) (*hclwrite.Block, error) { - if !args.Config { - return nil, nil - } - - attributes := map[string]interface{}{} - - // Set the attributes - attributes["tenancy_id"] = args.TenantOcid - attributes["user_email"] = args.OciUserEmail - if args.ConfigName != "" { - attributes["integration_name"] = args.ConfigName - } - - // Create and return the module - modDetails := []lwgenerate.HclModuleModifier{ - lwgenerate.HclModuleWithVersion(lwgenerate.OciConfigVersion), - lwgenerate.HclModuleWithAttributes(attributes), - } - return lwgenerate.NewModule( - "oci_config", - lwgenerate.OciConfigSource, - modDetails..., - ).ToBlock() -} diff --git a/vendor/github.com/lacework/go-sdk/lwlogger/README.md b/vendor/github.com/lacework/go-sdk/lwlogger/README.md deleted file mode 100644 index efb9e2b8c..000000000 --- a/vendor/github.com/lacework/go-sdk/lwlogger/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Lacework Logger - -A wrapper Logger Go package for Lacework projects based of [zap](https://github.com/uber-go/zap). - -## Usage - -Download the library into your `$GOPATH`: - - $ go get github.com/lacework/go-sdk/lwlogger - -Import the library into your tool: - -```go -import "github.com/lacework/go-sdk/lwlogger" -``` - -## Environment Variables - -This package can be controlled via environment variables: - -| Environment Variable | Description | Default | Supported Options | -|----------------------|-------------|---------|-------------------| -|`LW_LOG`|Change the verbosity of the logs |`""`| `INFO` or `DEBUG` | -|`LW_LOG_FORMAT`|Controls the format of the logs|`JSON`| `JSON` or `CONSOLE` | -|`LW_LOG_DEV`|Switch the logger instance to development mode (extra verbose)|`false`| `true` or `false` | - -## Examples - -To create a new logger instance with the log level `INFO`, write an interesting -info message and another debug message. Note that only the info message will be -displayed: -```go -package main - -import "github.com/lacework/go-sdk/lwlogger" - -func main() { - lwL := lwlogger.New("INFO") - - lwL.Debug("this is a debug message, too long and only needed when debugging this app") - // This message wont be displayed - - lwL.Info("interesting info") - // Output: {"level":"info","ts":"[timestamp]","caller":"main.go:9","msg":"interesting info"} -} -``` - -Look at the [examples/](examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/lwlogger/logger.go b/vendor/github.com/lacework/go-sdk/lwlogger/logger.go deleted file mode 100644 index 58a143a84..000000000 --- a/vendor/github.com/lacework/go-sdk/lwlogger/logger.go +++ /dev/null @@ -1,198 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// A wrapper Logger package for Lacework projects based of zap logger. -package lwlogger - -import ( - "fmt" - "io" - "os" - "strings" - "time" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -var ( - // LogLevelEnv represents the level that the logger is configured - LogLevelEnv = "LW_LOG" - SupportedLogLevels = [4]string{"", "ERROR", "INFO", "DEBUG"} - - // LogFormatEnv controls the format of the logger - LogFormatEnv = "LW_LOG_FORMAT" - DefaultLogFormat = "JSON" - SupportedLogFormats = [2]string{"JSON", "CONSOLE"} - - // LogDevelopmentModeEnv switches the logger to development mode - LogDevelopmentModeEnv = "LW_LOG_DEV" - - // LogToNativeLoggerEnv is used for those consumers like terraform that control - // the logs that are presented to the user, when this environment is turned - // on, the logger implementation will use the native Go logger 'log.Writer()' - LogToNativeLoggerEnv = "LW_LOG_NATIVE" -) - -// New initialize a new logger with the provided level and options -func New(level string, options ...zap.Option) *zap.Logger { - return NewWithFormat(level, "", options...) -} - -func NewWithFormat(level string, format string, options ...zap.Option) *zap.Logger { - if level == "" { - level = LogLevelFromEnvironment() - } - if format == "" { - format = logFormatFromEnv() - } - - zapConfig := zap.Config{ - Level: zapLogLevel(level), - Sampling: &zap.SamplingConfig{ - Initial: 100, - Thereafter: 100, - }, - Development: inDevelopmentMode(), - Encoding: format, - EncoderConfig: laceworkEncoderConfig(), - OutputPaths: []string{"stderr"}, - ErrorOutputPaths: []string{"stderr"}, - } - - l, err := zapConfig.Build(options...) - if err != nil { - fmt.Printf("Error: unable to initialize logger: %v\n", err) - return zap.NewExample(options...) - } - - return l -} - -// NewWithWriter initialize a new logger with the provided level and options -// but redirecting the logs to the provider io.Writer -func NewWithWriter(level string, out io.Writer, options ...zap.Option) *zap.Logger { - if level == "" { - level = LogLevelFromEnvironment() - } - - var ( - writeSyncer = zapcore.AddSync(out) - core = zapcore.NewCore( - zapEncoderFromFormat(logFormatFromEnv()), - writeSyncer, - zapLogLevel(level), - ) - localOpts = []zap.Option{ - zap.ErrorOutput(writeSyncer), - zap.AddCaller(), - zap.WrapCore(func(core zapcore.Core) zapcore.Core { - return zapcore.NewSamplerWithOptions(core, time.Second, 100, 100) - }), - } - ) - - return zap.New(core, options...).WithOptions(localOpts...) -} - -// Merges multiple loggers into one. A call to the merged logger will be -// forwarded to all the loggers -func Merge(loggers ...*zap.Logger) *zap.Logger { - cores := make([]zapcore.Core, len(loggers)) - for i, log := range loggers { - cores[i] = log.Core() - } - return zap.New(zapcore.NewTee(cores...)) -} - -func ValidLevel(level string) bool { - for _, l := range SupportedLogLevels { - if l == level { - return true - } - } - return false -} - -// LogLevelFromEnvironment checks the environment variable 'LW_LOG' -func LogLevelFromEnvironment() string { - switch os.Getenv(LogLevelEnv) { - case "info", "INFO": - return "INFO" - case "debug", "DEBUG": - return "DEBUG" - case "error", "ERROR": - return "ERROR" - default: - return "" - } -} - -func zapLogLevel(level string) zap.AtomicLevel { - switch level { - case "INFO": - return zap.NewAtomicLevelAt(zap.InfoLevel) - case "DEBUG": - return zap.NewAtomicLevelAt(zap.DebugLevel) - default: - return zap.NewAtomicLevelAt(zap.ErrorLevel) - } -} - -func inDevelopmentMode() bool { - return os.Getenv(LogDevelopmentModeEnv) == "true" -} - -func logFormatFromEnv() string { - switch os.Getenv(LogFormatEnv) { - case "console", "CONSOLE": - return "console" - case "json", "JSON": - return "json" - } - // @afiune the library require the format to be lowercase - return strings.ToLower(DefaultLogFormat) -} - -func zapEncoderFromFormat(format string) zapcore.Encoder { - switch format { - case "console": - return zapcore.NewConsoleEncoder(laceworkEncoderConfig()) - case "json": - return zapcore.NewJSONEncoder(laceworkEncoderConfig()) - default: - // @afiune we should never land here but just in case ;) - return zapcore.NewJSONEncoder(laceworkEncoderConfig()) - } -} - -func laceworkEncoderConfig() zapcore.EncoderConfig { - return zapcore.EncoderConfig{ - TimeKey: "ts", - LevelKey: "level", - NameKey: "logger", - CallerKey: "caller", - MessageKey: "msg", - StacktraceKey: "stacktrace", - LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.LowercaseLevelEncoder, - EncodeTime: zapcore.RFC3339TimeEncoder, - EncodeDuration: zapcore.SecondsDurationEncoder, - EncodeCaller: zapcore.ShortCallerEncoder, - } -} diff --git a/vendor/github.com/lacework/go-sdk/lwrunner/awsrunner.go b/vendor/github.com/lacework/go-sdk/lwrunner/awsrunner.go deleted file mode 100644 index ef514970c..000000000 --- a/vendor/github.com/lacework/go-sdk/lwrunner/awsrunner.go +++ /dev/null @@ -1,422 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lwrunner - -import ( - "context" - "fmt" - "os" - "strings" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/ec2" - ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" - "github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect" - "github.com/aws/aws-sdk-go-v2/service/iam" - "github.com/aws/aws-sdk-go-v2/service/iam/types" - "github.com/aws/aws-sdk-go-v2/service/ssm" - ssmtypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" - "golang.org/x/crypto/ssh" -) - -type AWSRunner struct { - Runner Runner - Region string - AvailabilityZone string - InstanceID string - ImageName string -} - -func NewAWSRunner( - amiImageId, - userFromCLIArg, - host, - region, - availabilityZone, - instanceID string, - filterSSH bool, - callback ssh.HostKeyCallback, - cfg aws.Config) (*AWSRunner, error) { - // Look up the AMI name of the runner - imageName, err := getAMIName(amiImageId, region, cfg) - if err != nil { - return nil, err - } - - // Heuristically assign SSH username based on AMI name - var detectedUsername string - if filterSSH { - detectedUsername, err = getSSHUsername(userFromCLIArg, imageName) - if err != nil { - return nil, err - } - } else { - detectedUsername = "no_ssh_username_provided" - } - - defaultCallback, err := DefaultKnownHosts() - if err == nil && callback == nil { - callback = defaultCallback - } - - runner := New(detectedUsername, host, callback) - - return &AWSRunner{ - *runner, - region, - availabilityZone, - instanceID, - imageName, - }, nil -} - -func (run AWSRunner) SendAndUseIdentityFile(cfg aws.Config) error { - pubBytes, privBytes, err := GetKeyBytes() - if err != nil { - return err - } - - err = run.SendPublicKey(pubBytes, cfg) - if err != nil { - return err - } - - signer, err := ssh.ParsePrivateKey(privBytes) - if err != nil { - return err - } - run.Runner.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} - - return nil -} - -// Helper function to send a public key to a test instance. Uses -// EC2InstanceConnect. The AWS account used to run the tests must -// have EC2InstanceConnect permissions attached to its IAM role. -// First checks to make sure the instance is still running. -func (run AWSRunner) SendPublicKey(pubBytes []byte, cfg aws.Config) error { - // Send public key - cfg.Region = run.Region - svc := ec2instanceconnect.NewFromConfig(cfg) - - input := &ec2instanceconnect.SendSSHPublicKeyInput{ - AvailabilityZone: &run.AvailabilityZone, - InstanceId: &run.InstanceID, - InstanceOSUser: aws.String(run.Runner.User), - SSHPublicKey: aws.String(string(pubBytes)), - } - - _, err := svc.SendSSHPublicKey(context.Background(), input) - if err != nil { - return err - } - - return nil -} - -// AssociateInstanceProfileWithRunner associates a given instance profile with the -// receiving runner. First checks if there are any instance profiles already associated -// with the runner, and returns an error if so (since a runner can only have one instance -// profile associated with it). Then associates the instance profile with the runner. -// Returns the association ID or an error. -func (run AWSRunner) AssociateInstanceProfileWithRunner( - cfg aws.Config, instanceProfile types.InstanceProfile, -) (string, error) { - c := ec2.New(ec2.Options{ - Credentials: cfg.Credentials, - Region: run.Region, - }) - - // Check to see if there are any instance profiles already associated with the runner - describeOutput, err := c.DescribeIamInstanceProfileAssociations( - context.Background(), - &ec2.DescribeIamInstanceProfileAssociationsInput{ - Filters: []ec2types.Filter{ - { - Name: aws.String("instance-id"), - Values: []string{ - run.InstanceID, - }, - }, - }, - }, - ) - if err != nil { - return "", err - } - - associationID, err := run.isCorrectInstanceProfileAlreadyAssociated( - cfg, describeOutput.IamInstanceProfileAssociations, - ) - if err != nil { - return "", err - } - - if associationID != "" { // use the existing, correctly configured instance profile - return associationID, nil - } else { // associate our own instance profile - associateOutput, err := c.AssociateIamInstanceProfile( - context.Background(), - &ec2.AssociateIamInstanceProfileInput{ - IamInstanceProfile: &ec2types.IamInstanceProfileSpecification{ - Arn: instanceProfile.Arn, - }, - InstanceId: aws.String(run.InstanceID), - }, - ) - if err != nil { - return "", err - } - - return *associateOutput.IamInstanceProfileAssociation.AssociationId, nil - } -} - -// isCorrectInstanceProfileAlreadyAssociated takes a list of instance profile associations -// and checks if there is an instance profile associated and if this instance -// profile has the correct policy for SSM access. Returns `, nil` if so. Returns -// `"", nil` if there is no instance profile associated. Returns `"", ` if -// there is an incorrect instance profile associated, or if there was an error in -// executing this function. -func (run AWSRunner) isCorrectInstanceProfileAlreadyAssociated( - cfg aws.Config, associations []ec2types.IamInstanceProfileAssociation, -) (string, error) { - if len(associations) <= 0 { // no instance profile associated - return "", nil - } - instanceProfileName := strings.Split(*associations[0].IamInstanceProfile.Arn, "instance-profile/")[1] - - c := iam.New(iam.Options{ - Credentials: cfg.Credentials, - Region: cfg.Region, - }) - - getInstanceProfileOutput, err := c.GetInstanceProfile( - context.Background(), - &iam.GetInstanceProfileInput{ - InstanceProfileName: aws.String(instanceProfileName), - }, - ) - if err != nil { - return "", err - } - - // Check to see if the instance profile associated with the runner has the correct policy - - if len(getInstanceProfileOutput.InstanceProfile.Roles) <= 0 { // can only have max one role - return "", fmt.Errorf( - "runner %v already has an instance profile (%v) attached, does not have a role", - run, - getInstanceProfileOutput.InstanceProfile, - ) - } - - // Check which policies are associated with this instance profile's role - listAttachedRolePoliciesOutput, err := c.ListAttachedRolePolicies( - context.Background(), - &iam.ListAttachedRolePoliciesInput{ - RoleName: getInstanceProfileOutput.InstanceProfile.Roles[0].RoleName, - }, - ) - if err != nil { - return "", err - } - - for _, policy := range listAttachedRolePoliciesOutput.AttachedPolicies { - if *policy.PolicyArn == SSMInstancePolicy { - return *associations[0].AssociationId, nil // everything is configured correctly, we can return now - } - } - - // The runner has an instance profile attached, the instance profile has a role, - // and the role does not have the policy we need for SSM. We can't install on - // this instance, return an error - return "", fmt.Errorf( - "runner %v already has an instance profile (%v) attached, does not have policy %s", - run, - getInstanceProfileOutput.InstanceProfile, - SSMInstancePolicy, - ) -} - -func (run AWSRunner) DisassociateInstanceProfileFromRunner(cfg aws.Config, associationID string) error { - c := ec2.New(ec2.Options{ - Credentials: cfg.Credentials, - Region: run.Region, - }) - - _, err := c.DisassociateIamInstanceProfile( - context.Background(), - &ec2.DisassociateIamInstanceProfileInput{ - AssociationId: aws.String(associationID), - }, - ) - - return err -} - -const SSMInstancePolicy string = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" - -// RunSSMCommandOnRemoteHost takes a shell command to install the agent on the runner -// the runner and executes it using SSM. `operation` must be one of the commands allowed -// by the SSM document. This function will not return until the command is in a terminal -// state, or until 2min have passed. -func (run AWSRunner) RunSSMCommandOnRemoteHost(cfg aws.Config, operation string) ( - ssm.GetCommandInvocationOutput, error, -) { - c := ssm.New(ssm.Options{ - Credentials: cfg.Credentials, - Region: run.Region, - }) - - sendCommandOutput, err := c.SendCommand( - context.Background(), - &ssm.SendCommandInput{ - DocumentName: aws.String("AWS-RunShellScript"), - Comment: aws.String("this command is for installing the Lacework Agent"), - InstanceIds: []string{ - run.InstanceID, - }, - Parameters: map[string][]string{ - "commands": { - operation, - }, - }, - }, - ) - if err != nil { - return ssm.GetCommandInvocationOutput{}, err - } - - var getCommandInvocationOutput *ssm.GetCommandInvocationOutput - - // Sleep while waiting for the command to execute - const durationTensOfSeconds = 12 - for i := 0; i < durationTensOfSeconds; i++ { - time.Sleep(10 * time.Second) - - getCommandInvocationOutput, err = c.GetCommandInvocation( - context.Background(), - &ssm.GetCommandInvocationInput{ - CommandId: sendCommandOutput.Command.CommandId, - InstanceId: aws.String(run.InstanceID), - }, - ) - if err != nil { - return ssm.GetCommandInvocationOutput{}, err - } - - // Check if the command has reached a "terminal state" - if getCommandInvocationOutput.Status == ssmtypes.CommandInvocationStatusSuccess || - getCommandInvocationOutput.Status == ssmtypes.CommandInvocationStatusCancelled || - getCommandInvocationOutput.Status == ssmtypes.CommandInvocationStatusTimedOut || - getCommandInvocationOutput.Status == ssmtypes.CommandInvocationStatusFailed { - return *getCommandInvocationOutput, nil - } - } - - return *getCommandInvocationOutput, fmt.Errorf( - "command %s did not finish in %dmin, final state %v, stdout %s, stderr %s", - *sendCommandOutput.Command.CommandId, - durationTensOfSeconds/6, - *getCommandInvocationOutput, - GetSSMCommandInvocationStdOut(*getCommandInvocationOutput), - GetSSMCommandInvocationStdErr(*getCommandInvocationOutput), - ) -} - -// getAMIName takes an AMI image ID, an AWS region name, and an AWS -// credential config as input and calls the AWS API to get the name -// of the AMI. Returns the AMI name or an error if unsuccessful. -func getAMIName(amiImageId, region string, cfg aws.Config) (string, error) { - cfg.Region = region - svc := ec2.NewFromConfig(cfg) - input := ec2.DescribeImagesInput{ - ImageIds: []string{ - amiImageId, - }, - } - result, err := svc.DescribeImages(context.Background(), &input) - if err != nil { - return "", err - } - if len(result.Images) != 1 { - return "", fmt.Errorf("expected to find only one AMI, instead found %v", result.Images) - } - - return *result.Images[0].Name, nil -} - -// getSSHUsername takes any username passed as a CLI arg, -// an AMI image name, a shell environment, and returns -// the username for SSHing into the AWS runner or the empty -// string and an error if the AMI is not supported. -// It first checks if `LW_SSH_USER` is set and returns it if so. -// Then it checks the AMI image name to heuristically determine the -// SSH username. -func getSSHUsername(userFromCLIArg, imageName string) (string, error) { - if userFromCLIArg != "" { // from CLI arg - return userFromCLIArg, nil - } - usernameLUT := getSSHUsernameLookupTable() - for _, matchFn := range usernameLUT { - if match, foundName := matchFn(imageName); match { - return foundName, nil - } - } - // No matching AMI found, return an error - return "", fmt.Errorf("no SSH username found for AMI %s, set as arg or shell env", imageName) -} - -// getSSHUsernameLookupTable returns a lookup table for heuristically -// determining SSH username based on AMI. -// The first row of the table it returns is a function that checks -// `LW_SSH_USER` in the shell environment. -func getSSHUsernameLookupTable() []func(string) (bool, string) { - return []func(string) (bool, string){ - // THIS ROW MUST BE FIRST IN THE TABLE - func(_ string) (bool, string) { return os.Getenv("LW_SSH_USER") != "", os.Getenv("LW_SSH_USER") }, - func(imageName string) (bool, string) { return strings.Contains(imageName, "ubuntu"), "ubuntu" }, - func(imageName string) (bool, string) { - return strings.Contains(imageName, "amazon_linux"), "ec2-user" - }, - func(imageName string) (bool, string) { return strings.Contains(imageName, "amzn2-ami"), "ec2-user" }, - } -} - -// GetSSMCommandInvocationStdOut is a helper function to safely deference the -// SSM struct we receive from the AWS API. -func GetSSMCommandInvocationStdOut(out ssm.GetCommandInvocationOutput) string { - if out.StandardOutputContent != nil { - return *out.StandardOutputContent - } else { - return "" - } -} - -// GetSSMCommandInvocationStdErr is a helper function to safely deference the -// SSM struct we receive from the AWS API. -func GetSSMCommandInvocationStdErr(out ssm.GetCommandInvocationOutput) string { - if out.StandardErrorContent != nil { - return *out.StandardErrorContent - } else { - return "" - } -} diff --git a/vendor/github.com/lacework/go-sdk/lwrunner/gcprunner.go b/vendor/github.com/lacework/go-sdk/lwrunner/gcprunner.go deleted file mode 100644 index 69b209e60..000000000 --- a/vendor/github.com/lacework/go-sdk/lwrunner/gcprunner.go +++ /dev/null @@ -1,122 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lwrunner - -import ( - "context" - "fmt" - "time" - - oslogin "cloud.google.com/go/oslogin/apiv1" - osloginpb "cloud.google.com/go/oslogin/apiv1/osloginpb" - "golang.org/x/crypto/ssh" - osloginpb_common "google.golang.org/genproto/googleapis/cloud/oslogin/common" -) - -type GCPRunner struct { - Runner Runner - ParentUsername string - ProjectID string - AvailabilityZone string - InstanceID string -} - -func NewGCPRunner( - host, parentUsername, projectID, availabilityZone, instanceID string, callback ssh.HostKeyCallback, -) (*GCPRunner, error) { - defaultCallback, err := DefaultKnownHosts() - if err == nil && callback == nil { - callback = defaultCallback - } - - runner := New("", host, callback) // populate username during `SendAndUseIdentityFile()` - - return &GCPRunner{ - *runner, - parentUsername, - projectID, - availabilityZone, - instanceID, - }, nil -} - -func (run GCPRunner) SendAndUseIdentityFile() error { - pubBytes, privBytes, err := GetKeyBytes() - if err != nil { - return err - } - - err = run.SendPublicKey(pubBytes) - if err != nil { - return err - } - - signer, err := ssh.ParsePrivateKey(privBytes) - if err != nil { - return err - } - run.Runner.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} - - retries := 15 - for i := 0; i < retries; i += 1 { - if _, err = ssh.Dial("tcp", run.Runner.Address(), run.Runner.ClientConfig); err == nil { - return nil - } - time.Sleep(time.Second) - } - return fmt.Errorf("could not connect to host successfully after %d tries with err %v", retries, err) -} - -// SendPublicKey is a helper function to send a public key to a GCP account -// for OSLogin authentication. The account must have the "Compute OS Login" IAM role -// and "Service Account User" authorization for the GCE default service account. -// When the SSH key is sent, it will persist in the GCP account for 10min. -func (run GCPRunner) SendPublicKey(pubBytes []byte) error { - ctx := context.Background() - c, err := oslogin.NewClient(ctx) - if err != nil { - return err - } - defer c.Close() - - key := &osloginpb_common.SshPublicKey{ - Key: string(pubBytes), - ExpirationTimeUsec: time.Now().UnixMicro() + (10 * time.Minute.Microseconds()), // expiration time is 10min from now - } - - req := &osloginpb.ImportSshPublicKeyRequest{ - Parent: run.ParentUsername, - SshPublicKey: key, - ProjectId: run.ProjectID, - } - resp, err := c.ImportSshPublicKey(ctx, req) - if err != nil { - return err - } - - // Get login info from the OSLogin profile for our SSH login - posixAccounts := resp.LoginProfile.GetPosixAccounts() - for _, account := range posixAccounts { - if account.Primary { // there will only be one Primary account per profile - run.Runner.User = account.Username - } - } - - return nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwrunner/runner.go b/vendor/github.com/lacework/go-sdk/lwrunner/runner.go deleted file mode 100644 index 9e7fd82d6..000000000 --- a/vendor/github.com/lacework/go-sdk/lwrunner/runner.go +++ /dev/null @@ -1,227 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// A runner package that executes commands on remote hosts. -package lwrunner - -import ( - "bytes" - "errors" - "fmt" - "io" - "net" - "os" - "path" - - "github.com/lacework/go-sdk/internal/file" - homedir "github.com/mitchellh/go-homedir" - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/knownhosts" -) - -type Runner struct { - Hostname string - Port int - *ssh.ClientConfig -} - -func New(user, host string, callback ssh.HostKeyCallback) *Runner { - if os.Getenv("LW_SSH_USER") != "" { - user = os.Getenv("LW_SSH_USER") - } - - defaultCallback, err := DefaultKnownHosts() - if err == nil && callback == nil { - callback = defaultCallback - } - - return &Runner{ - host, - 22, - &ssh.ClientConfig{ - User: user, - Auth: []ssh.AuthMethod{}, - HostKeyCallback: callback, - }, - } -} - -func (run Runner) UseIdentityFile(file string) error { - signer, err := newSignerFromFile(file) - if err != nil { - return err - } - run.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} - return nil -} - -func (run Runner) UsePassword(secret string) { - run.Auth = []ssh.AuthMethod{ssh.Password(secret)} -} - -func (run *Runner) Address() string { - return fmt.Sprintf("%s:%d", run.Hostname, run.Port) -} - -// Exec executes a command on the configured remote host -func (run *Runner) Exec(cmd string) (stdout bytes.Buffer, stderr bytes.Buffer, err error) { - conn, err := ssh.Dial("tcp", run.Address(), run.ClientConfig) - if err != nil { - return - } - - session, err := conn.NewSession() - if err != nil { - return - } - defer session.Close() - - session.Stdout = &stdout - session.Stderr = &stderr - err = session.Run(cmd) - return -} - -// DefaultKnownHosts returns a host key callback from default known hosts path -func DefaultKnownHosts() (ssh.HostKeyCallback, error) { - path, err := DefaultKnownHostsPath() - if err != nil { - return nil, err - } - - return knownhosts.New(path) -} - -// DefaultKnownHostsPath returns default user ~/.ssh/known_hosts file -func DefaultKnownHostsPath() (string, error) { - home, err := homedir.Dir() - if err != nil { - return "", err - } - - return path.Join(home, ".ssh", "known_hosts"), nil -} - -// AddKnownHost adds a host to the provided known hosts file, if no known hosts -// file is provided, it will fallback to default known_hosts file -func AddKnownHost(host string, remote net.Addr, key ssh.PublicKey, knownFile string) (err error) { - if knownFile == "" { - path, err := DefaultKnownHostsPath() - if err != nil { - return err - } - - knownFile = path - } - - if !file.FileExists(knownFile) { - if err := os.MkdirAll(path.Dir(knownFile), 0700); err != nil { - return err - } - } - - f, err := os.OpenFile(knownFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) - if err != nil { - return err - } - - defer f.Close() - - var ( - remoteNormalized = knownhosts.Normalize(remote.String()) - hostNormalized = knownhosts.Normalize(host) - addresses = []string{remoteNormalized} - ) - - if hostNormalized != remoteNormalized { - addresses = append(addresses, hostNormalized) - } - - _, err = f.WriteString(knownhosts.Line(addresses, key) + "\n") - return err -} - -// CheckKnownHost checks if a host is in known hosts file, if no known hosts -// file is provided, it will fallback to default known_hosts file -func CheckKnownHost(host string, remote net.Addr, key ssh.PublicKey, knownFile string) (found bool, err error) { - var keyErr *knownhosts.KeyError - - // Fallback to default known_hosts file - if knownFile == "" { - path, err := DefaultKnownHostsPath() - if err != nil { - return false, err - } - - knownFile = path - } - - // get host key callback - callback, err := knownhosts.New(knownFile) - if err != nil { - return false, err - } - - // check if host already exists - err = callback(host, remote, key) - if err == nil { - // host is known (already exists) - return true, nil - } - - // if keyErr.Want is greater than 0 length, that means host is in file with different key - if errors.As(err, &keyErr) && len(keyErr.Want) > 0 { - return true, keyErr - } - - // if not, pass it back to the user - if err != nil { - return false, err - } - - // key is not trusted because it is not in the known hosts file - return false, nil -} - -func DefaultIdentityFilePath() (string, error) { - if os.Getenv("LW_SSH_IDENTITY_FILE") != "" { - return os.Getenv("LW_SSH_IDENTITY_FILE"), nil - } - - home, err := homedir.Dir() - if err != nil { - return "", err - } - - return path.Join(home, ".ssh", "id_rsa"), nil -} - -func newSignerFromFile(keyname string) (ssh.Signer, error) { - fp, err := os.Open(keyname) - if err != nil { - return nil, err - } - defer fp.Close() - - buf, err := io.ReadAll(fp) - if err != nil { - return nil, err - } - - return ssh.ParsePrivateKey(buf) -} diff --git a/vendor/github.com/lacework/go-sdk/lwrunner/ssh.go b/vendor/github.com/lacework/go-sdk/lwrunner/ssh.go deleted file mode 100644 index e580aaf3d..000000000 --- a/vendor/github.com/lacework/go-sdk/lwrunner/ssh.go +++ /dev/null @@ -1,103 +0,0 @@ -// -// Author:: Nicholas Schmeller () -// Copyright:: Copyright 2022, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lwrunner - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "errors" - - "golang.org/x/crypto/ssh" -) - -// Helper function to generate a random private key. This key will -// stay in memory and does not persist across test runs. -func GeneratePrivateKey(bitSize int) (*rsa.PrivateKey, error) { - // generate key - priv, err := rsa.GenerateKey(rand.Reader, bitSize) - if err != nil { - return nil, err - } - - // validate key - err = priv.Validate() - if err != nil { - return nil, err - } - - return priv, err -} - -// Generates SSH keys in EC2-readable format -// Returns public key bytes, private key bytes, error -func GetKeyBytes() ([]byte, []byte, error) { - // generate keys with bit size 4096 - priv, err := GeneratePrivateKey(4096) - if err != nil { - return nil, nil, err - } - - pubBytes, err := GetPublicKeyBytes(&priv.PublicKey) - if err != nil { - return nil, nil, err - } - - privBytes, err := EncodePrivateKeyToPEM(priv) - if err != nil { - return nil, nil, err - } - - return pubBytes, privBytes, err -} - -// Takes an rsa.PublicKey and returns bytes suitable for writing to .pub file -// Returns in the format "ssh-rsa ..." -func GetPublicKeyBytes(key *rsa.PublicKey) ([]byte, error) { - publicRsaKey, err := ssh.NewPublicKey(key) - if err != nil { - return nil, err - } - - pubKeyBytes := ssh.MarshalAuthorizedKey(publicRsaKey) - - return pubKeyBytes, err -} - -// Encodes private key from RSA struct to PEM formatted bytes -func EncodePrivateKeyToPEM(privateKey *rsa.PrivateKey) ([]byte, error) { - // Get ASN.1 DER format - privDER := x509.MarshalPKCS1PrivateKey(privateKey) - - // pem.Block - privBlock := pem.Block{ - Type: "RSA PRIVATE KEY", - Headers: nil, - Bytes: privDER, - } - - // Private key in PEM format - privatePEM := pem.EncodeToMemory(&privBlock) - if privatePEM == nil { - return nil, errors.New("failed to encode private key to PEM") - } - - return privatePEM, nil -} diff --git a/vendor/github.com/lacework/go-sdk/lwseverity/severity.go b/vendor/github.com/lacework/go-sdk/lwseverity/severity.go deleted file mode 100644 index 6e6b77299..000000000 --- a/vendor/github.com/lacework/go-sdk/lwseverity/severity.go +++ /dev/null @@ -1,172 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// A helper package for Lacework severities -package lwseverity - -import ( - "fmt" - "sort" - "strings" -) - -type severity int - -func (s severity) GetSeverity() string { - return s.String() -} - -const ( - // Unknown severity - Unknown severity = iota - // Critical severity - Critical - // High severity - High - // Medium severity - Medium - // Low severity - Low - // Informational severity - Info -) - -var severities = map[severity]string{ - Unknown: "Unknown", - Critical: "Critical", - High: "High", - Medium: "Medium", - Low: "Low", - Info: "Info", -} - -// Get severity as a string type -func (s severity) String() string { - return severities[s] -} - -type validSeverities []severity - -// A list of valid Lacework severities (critical, high, medium, low, info) -var ValidSeverities = validSeverities{Critical, High, Medium, Low, Info} - -// Return a string representation of valid severities -// "critical, high, medium, low, info" -func (v validSeverities) String() string { - s := "" - - for _, severity := range v { - s += fmt.Sprintf("%s, ", strings.ToLower(severities[severity])) - } - - return strings.TrimRight(s, ", ") -} - -// Initialize a severity from string -func NewSeverity(s string) severity { - switch strings.ToLower(s) { - case "1", "critical": - return Critical - case "2", "high": - return High - case "3", "medium": - return Medium - case "4", "low": - return Low - case "5", "info": - return Info - default: - return Unknown - } -} - -type Severity interface { - GetSeverity() string -} - -// Normalize takes a string representation of Lacework severity and returns it's normalized -// integer and string values. -// -// Relation: -// - Critical Severity => 1, "Critical" -// - High Severity => 2, "High" -// - Medium Severity => 3, "Medium" -// - Low Severity => 4, "Low" -// - Informational Severity => 5, "Info" -// - Unknown Severity => 0, "Unknown" -func Normalize(s string) (int, string) { - severity := NewSeverity(s) - return int(severity), severity.String() -} - -// Take a string representation of Lacework severity and -// return whether it properly maps to a valid severity (not unknown) -func IsValid(s string) bool { - return NewSeverity(s) != Unknown -} - -// Returns true if the first severity not as critical as the second severity -// -// For instance: -// -// - "info" is not as crtical as "low" (true) -// - "medium" is as critical as "medium" (false) -// - "high" is more critical than "medium" (false) -// - "unknown" is more critical than "medium" (false) -// - "medium" is not as critical as "unknown" (true) -func NotAsCritical(first, second string) bool { - sevFirst, _ := Normalize(first) - sevSecond, _ := Normalize(second) - return sevFirst > sevSecond -} - -// Returns true if the threshold is proper and the severity is -// greater than or equal to the threshold -// -// For instance: -// -// - "medium" severity should be filtered for "high" threshold -// - "critical" severity should NOT be filtered for "high" threshold -// - "info" severity should NOT be filtered for "info" threshold -// - invalid (unknown) severity should NOT be filtered for * threshold -// - all severities should NOT be filtered for an invalid (unknown) threshold -func ShouldFilter(severity, threshold string) bool { - sevThreshold, _ := Normalize(threshold) - if sevThreshold == 0 { - return false - } - return NotAsCritical(severity, threshold) -} - -// Sort a slice of Severity interfaces from critical -> info -func SortSlice[S Severity](s []S) { - sort.SliceStable(s, func(i, j int) bool { - sevI, _ := Normalize(s[i].GetSeverity()) - sevJ, _ := Normalize(s[j].GetSeverity()) - return sevI < sevJ - }) -} - -// Sort a slice of Severity interfaces from info -> critical -func SortSliceA[S Severity](s []S) { - sort.SliceStable(s, func(i, j int) bool { - sevI, _ := Normalize(s[i].GetSeverity()) - sevJ, _ := Normalize(s[j].GetSeverity()) - return sevI > sevJ - }) -} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/README.md b/vendor/github.com/lacework/go-sdk/lwtime/README.md deleted file mode 100644 index 64a380119..000000000 --- a/vendor/github.com/lacework/go-sdk/lwtime/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# Lacework Time Library - -A simple relative and natural time package. - -## Usage - -Download the library into your `$GOPATH`: - - $ go get github.com/lacework/go-sdk/lwtime - -Import the library: - -```go -import "github.com/lacework/go-sdk/lwtime" -``` - -## Relative Time Specifiers - -Relative times allow you to represent time values dynamically, using specifiers that represent an offset from the current time. For instance, a relative time of `-24h` produces a date/time that is 24 hours less the current time. Relative times can also snap to a particular time. For instance, a relative time of `@d` would represent the start of the current day. - -For example, to generate a time range (using a start and end time) that represents the previous day: -```go -package main - -import ( - "fmt" - "os" - - "github.com/lacework/go-sdk/lwtime" -) - -func main() { - start, err := lwtime.ParseRelative("-1d@d") - if err != nil { - fmt.Println("Unable to parse start time range: %s", err) - os.Exit(1) - } - end, err := lwtime.ParseRelative("@d") - if err != nil { - fmt.Println("Unable to parse end time range: %s", err) - os.Exit(1) - } - // Output: The time range is 2023-07-11 07:00:00 +0000 UTC to 2023-07-12 07:00:00 +0000 UTC - fmt.Printf("The time range is %s to %s\n", start.String(), end.String()) -} -``` - -A relative time has three components: -* A signed (+/-) integer -* A relative time unit -* A relative time snap - -Lacework supports the following relative time units: -* y - year -* mon - month -* w - week -* d - day -* h - hour -* m - minute -* s - second - -Additional considerations include: -* To represent the current time, you can specify either `now` or `+0s`. -* When specifying an integer and relative time unit, snaps are optional. -* When specifying a snap, the integer and relative time unit are optional. For instance, `@d` is actually interpreted as `+0s@d`. - - -## Natural Time Ranges - -Natural time ranges allow you to represent time range values using natural language. For instance, a natural time range of `yesterday` represents a relative start time of `-1d@d` and a relative end time of `@d`. - -For example, to generate a time range of this month: -```go -package main - -import ( - "fmt" - "os" - - "github.com/lacework/go-sdk/lwtime" -) - -func main() { - start, end, err := lwtime.ParseNatural("this month") - if err != nil { - fmt.Println("Unable to parse natural time: %s", err) - os.Exit(1) - } - // The time range is 2023-07-01 07:00:00 +0000 UTC to 2023-07-13 01:23:59.921851 +0000 UTC - fmt.Printf("The time range is %s to %s\n", start.String(), end.String()) -} -``` - -A natural time has three components: -* An adjective -* A positive number (only when using the last adjective) -* The full text representation of a relative time unit (i.e., year/years) - -Lacework supports the following adjectives (disambiguating previous and last by design): -* this/current -* previous -* last - -Additional considerations include: -* `last` implies "in the last". So last week reads as "in the last week" and represents a start time of `-1w` and an end time of `now`. -* `previous` always snaps. So "previous week" represents a start time of `-1w@w` and an end time of `@w`. -* `yesterday` is a valid natural time and is equivalent to previous day. -* `today` is a valid natural time and is equivalent to this day or current day. - -## Examples - -Look at the [_examples/](_examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/lwtime/epoch.go b/vendor/github.com/lacework/go-sdk/lwtime/epoch.go deleted file mode 100644 index c9516f7d1..000000000 --- a/vendor/github.com/lacework/go-sdk/lwtime/epoch.go +++ /dev/null @@ -1,39 +0,0 @@ -package lwtime - -import ( - "fmt" - "strconv" - "time" -) - -// Epoch time type to parse the returned 13 digit time in milliseconds -type Epoch time.Time - -func (epoch *Epoch) UnmarshalJSON(b []byte) error { - ms, _ := strconv.Atoi(string(b)) - t := time.Unix(0, int64(ms)*int64(time.Millisecond)) - *epoch = Epoch(t) - return nil -} - -func (epoch Epoch) MarshalJSON() ([]byte, error) { - epochJson := fmt.Sprintf("%v", epoch.ToTime().UnixNano()/int64(time.Millisecond)) - return []byte(epochJson), nil -} - -func (epoch Epoch) ToTime() time.Time { - return time.Time(epoch) -} -func (epoch Epoch) Format(s string) string { - return epoch.ToTime().Format(s) -} -func (epoch Epoch) UTC() time.Time { - return epoch.ToTime().UTC() -} - -func (epoch *Epoch) String() string { - if epoch != nil { - return epoch.UTC().Format(time.RFC3339) - } - return "" -} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/epochstring.go b/vendor/github.com/lacework/go-sdk/lwtime/epochstring.go deleted file mode 100644 index 8abd65b06..000000000 --- a/vendor/github.com/lacework/go-sdk/lwtime/epochstring.go +++ /dev/null @@ -1,33 +0,0 @@ -package lwtime - -import ( - "strconv" - "strings" - "time" -) - -// EpochString time type to parse the returned 13 digit time in milliseconds. -// Used instead of Epoch type when unmarshalling a json response where epoch time is a string. -type EpochString time.Time - -func (epoch *EpochString) UnmarshalJSON(b []byte) error { - t := strings.Trim(string(b), `"`) - millis, _ := strconv.ParseInt(t, 10, 64) - seconds := time.Unix(millis/1000, 0) - *epoch = EpochString(seconds) - return nil -} - -func (epoch *EpochString) MarshalJSON() ([]byte, error) { - return epoch.ToTime().UTC().MarshalJSON() -} - -func (epoch EpochString) ToTime() time.Time { - return time.Time(epoch) -} -func (epoch EpochString) Format(s string) string { - return epoch.ToTime().Format(s) -} -func (epoch EpochString) UTC() time.Time { - return epoch.ToTime().UTC() -} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/nanotime.go b/vendor/github.com/lacework/go-sdk/lwtime/nanotime.go deleted file mode 100644 index b6c97b027..000000000 --- a/vendor/github.com/lacework/go-sdk/lwtime/nanotime.go +++ /dev/null @@ -1,33 +0,0 @@ -package lwtime - -import "time" - -// NanoTime time type to parse the returned time with nano format -// -// Example: "2020-08-20T01:00:00+0000" -type NanoTime time.Time - -func (nano *NanoTime) UnmarshalJSON(b []byte) (err error) { - s := string(b) - t, err := time.Parse(time.RFC3339Nano, s[1:len(s)-1]) - if err != nil { - t, err = time.Parse("2006-01-02T15:04:05.999999999Z0700", s[1:len(s)-1]) - } - *nano = NanoTime(t) - return -} - -func (nano NanoTime) MarshalJSON() ([]byte, error) { - // @afiune we might have problems changing the location :( - return nano.ToTime().UTC().MarshalJSON() -} - -func (nano NanoTime) ToTime() time.Time { - return time.Time(nano) -} -func (nano NanoTime) Format(s string) string { - return nano.ToTime().Format(s) -} -func (nano NanoTime) UTC() time.Time { - return nano.ToTime().UTC() -} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/nattime.go b/vendor/github.com/lacework/go-sdk/lwtime/nattime.go deleted file mode 100644 index 4df5b6efd..000000000 --- a/vendor/github.com/lacework/go-sdk/lwtime/nattime.go +++ /dev/null @@ -1,201 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// A simple relative and natural time package. -package lwtime - -import ( - "fmt" - "regexp" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" -) - -type naturalAdjective string - -const ( - // both plural and singular regular expressions should contain 3 capture groups - naturalPluralRE = `^(last)\s(\d+)\s(years|months|weeks|days|hours|minutes|seconds)$` - naturalSingularRE = `^(this|current|previous|last)(\s)(year|month|week|day|hour|minute|second)$` - - Today naturalAdjective = "today" - Yesterday naturalAdjective = "yesterday" - This naturalAdjective = "this" - Current naturalAdjective = "current" - Previous naturalAdjective = "previous" - Last naturalAdjective = "last" -) - -func (nta naturalAdjective) isValid() bool { - switch naturalAdjective(strings.ToLower(string(nta))) { // inline lowercase conversion - case Today, Yesterday, This, Current, Previous, Last: - return true - } - return false -} - -type natural struct { - adjective naturalAdjective - num string - iNum int - unit relativeUnit -} - -func (nt *natural) loadRelativeUnit(u string) bool { - switch strings.ToLower(u) { - case "year", "years": - nt.unit = Year - case "month", "months": - nt.unit = Month - case "week", "weeks": - nt.unit = Week - case "day", "days": - nt.unit = Day - case "hour", "hours": - nt.unit = Hour - case "minute", "minutes": - nt.unit = Minute - case "second", "seconds": - nt.unit = Second - default: - return false - } - return true -} - -func newNatural(s string) (natural, error) { - s = strings.ToLower(s) - - // Today - if naturalAdjective(s) == Today { - return natural{ - adjective: This, - num: "1", iNum: 1, - unit: relativeUnit("d"), - }, nil - } - // Yesterday - if naturalAdjective(s) == Yesterday { - return natural{ - adjective: Previous, - num: "1", iNum: 1, - unit: relativeUnit("d"), - }, nil - } - - nt := natural{} - var nt_parts []string - // Singular - singularRE := regexp.MustCompile(naturalSingularRE) - if nt_parts = singularRE.FindStringSubmatch(s); s == "" || nt_parts == nil { - // Plural - pluralRE := regexp.MustCompile(naturalPluralRE) - if nt_parts = pluralRE.FindStringSubmatch(s); s == "" || nt_parts == nil { - return nt, errors.New(fmt.Sprintf("natural time (%s) is invalid", s)) - } - } - // Adjective - nt.adjective = naturalAdjective(nt_parts[1]) - if !nt.adjective.isValid() { - // this would indicate a code mismatch between enumerated adjectives and regex - return nt, errors.New(fmt.Sprintf("invalid adjective for natural time (%s)", s)) - } - // Num - nt.num = nt_parts[2] - var err error - nt.iNum, err = strconv.Atoi(nt.num) - if err != nil { - nt.num = "1" - nt.iNum = 1 - } - // Unit - if ok := nt.loadRelativeUnit(nt_parts[3]); !ok { - // this would indicate a code mismatch between relative units and regex - return nt, errors.New(fmt.Sprintf("invalid unit for natural time (%s)", s)) - } - return nt, nil -} - -func (nt natural) getRange(t time.Time) (start time.Time, end time.Time, err error) { - var relStart, relEnd string - baseErr := "unable to compute natural time range" - - // relatives - if relStart, relEnd, err = nt.getRelativeRange(); err != nil { - err = errors.Wrap(err, baseErr) - return - } - - // start time - if start, err = parseRelativeFromTime(relStart, t); err != nil { - err = errors.Wrap(err, baseErr) - return - } - - // end time - if end, err = parseRelativeFromTime(relEnd, t); err != nil { - err = errors.Wrap(err, baseErr) - return - } - - return -} - -func (nt natural) getRelativeRange() (relStart string, relEnd string, err error) { - // use natural adjective to determine relative start/end specifiers - switch nt.adjective { - case This, Current: - relStart = fmt.Sprintf("@%s", nt.unit) - relEnd = "now" - case Previous: - relStart = fmt.Sprintf("-1%s@%s", nt.unit, nt.unit) - relEnd = fmt.Sprintf("@%s", nt.unit) - case Last: - relStart = fmt.Sprintf("-%s%s", nt.num, nt.unit) - relEnd = "now" - default: - err = errors.New("invalid adjective for natural time") - } - return -} - -// ParseNatural parses the string representation of a Lacework natural time -// Start and End time objects are returned in UTC -// -// start, end, err := lwtime.ParseNatural("this year") -// if err != nil { -// ... -// } -func ParseNatural(n string) (time.Time, time.Time, error) { - // time.Now() is intentional here such that snaps work properly - // For instance snapping to @d should snap to the start of the local day - startLocal, endLocal, err := parseNaturalFromTime(n, time.Now()) - return startLocal.UTC(), endLocal.UTC(), err -} - -func parseNaturalFromTime(n string, fromTime time.Time) (time.Time, time.Time, error) { - natural, err := newNatural(n) - if err != nil { - return time.Time{}, time.Time{}, err - } - - return natural.getRange(fromTime) -} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/reltime.go b/vendor/github.com/lacework/go-sdk/lwtime/reltime.go deleted file mode 100644 index 3c6143b61..000000000 --- a/vendor/github.com/lacework/go-sdk/lwtime/reltime.go +++ /dev/null @@ -1,257 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package lwtime - -import ( - "fmt" - "os" - "regexp" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" -) - -type relativeDate struct { - year int - month time.Month - day int -} - -// The potential difference between the clocks on the client -// and the Lacework API server -const clockOffset = "+0s" - -// Returns 'now' with the default or the provided clock offset -func nowClockOffset() string { - if os.Getenv("LW_CLOCK_OFFSET") != "" { - return os.Getenv("LW_CLOCK_OFFSET") - } - return clockOffset -} - -func mondays(year int) (mondays []relativeDate) { - // get the start of the year datetime - start := time.Date(year, 1, 1, 0, 0, 0, 0, time.Now().Location()) - // get 24 hours of duration - d, _ := time.ParseDuration("24h") - // set startYear for comparison as we iterate - startYear := start.Year() - // iterate until startYear deviates from start.Year() - for startYear == start.Year() { - // if we kave a monday, add it.... - if start.Weekday() == time.Monday { - year, month, day := start.Date() - mondays = append(mondays, relativeDate{year, month, day}) - } - // add our 24 hour duration - start = start.Add(d) - } - return mondays -} - -type relativeUnit string - -const ( - relativeRE = `^([+-])?(?:(\d+)(\w+))?(?:@(\w+))?$` - Year relativeUnit = "y" - Month relativeUnit = "mon" - Week relativeUnit = "w" - Day relativeUnit = "d" - Hour relativeUnit = "h" - Minute relativeUnit = "m" - Second relativeUnit = "s" - HoursInADay = 24 -) - -func (ru relativeUnit) isValid() bool { - switch relativeUnit(strings.ToLower(string(ru))) { // inline lowercase conversion - case Year, Month, Week, Day, Hour, Minute, Second, relativeUnit(""): - return true - } - return false -} - -func (ru relativeUnit) snapTime(inTime time.Time) (outTime time.Time, err error) { - // immediately short circuit if snap is invalid - if !ru.isValid() { - err = errors.New(fmt.Sprintf( - "snap (%s) is not a valid relative time unit", ru)) - return - } - - year, month, day := inTime.Date() - hour := inTime.Hour() - minute := inTime.Minute() - second := inTime.Second() - nano := inTime.Nanosecond() - - switch relativeUnit(strings.ToLower(string(ru))) { - case Week: - year, week := inTime.ISOWeek() - relDate := mondays(year)[week-1] - outTime = time.Date( - relDate.year, - relDate.month, - relDate.day, - 0, 0, 0, 0, inTime.Location(), - ) - return - case Year: - month = 1 - fallthrough - case Month: - day = 1 - fallthrough - case Day: - hour = 0 - fallthrough - case Hour: - minute = 0 - fallthrough - case Minute: - second = 0 - fallthrough - case Second: - nano = 0 - } - outTime = time.Date(year, month, day, hour, minute, second, nano, inTime.Location()) - return -} - -type relative struct { - num string - iNum int - unit relativeUnit - snap relativeUnit -} - -func newRelative(s string) (relative, error) { - var rel relative - var rel_parts []string - - // now is equivelant to LW_CLOCK_OFFSET (defaults to const clockOffset(+0s)) - // prevent corner conditions with Lacework's API server - if s == "now" { - s = nowClockOffset() - } - // regex - re := regexp.MustCompile(relativeRE) - if rel_parts = re.FindStringSubmatch(s); s == "" || rel_parts == nil { - return rel, errors.New(fmt.Sprintf("relative time specifier (%s) is invalid", s)) - } - // Num - if rel_parts[1] == "-" { - rel.num = rel_parts[1] + rel_parts[2] - } else { - rel.num = rel_parts[2] - } - var err error - rel.iNum, err = strconv.Atoi(rel.num) - if err != nil { - rel.num = "0" - rel.iNum = 0 - } - // Unit - rel.unit = relativeUnit(strings.ToLower(string(rel_parts[3]))) - if !rel.unit.isValid() { - return rel, errors.New(fmt.Sprintf("invalid unit for relative time specifier (%s)", s)) - } - // normalize years, weeks, and days in to hours - switch rel.unit { - case relativeUnit(""): - rel.unit = Second - case Week: - rel.iNum = rel.iNum * 7 * HoursInADay - rel.num = strconv.Itoa(rel.iNum) - rel.unit = Hour - case Day: - rel.iNum = rel.iNum * HoursInADay - rel.num = strconv.Itoa(rel.iNum) - rel.unit = Hour - } - // Snap - rel.snap = relativeUnit(strings.ToLower(string(rel_parts[4]))) - if !rel.snap.isValid() { - return rel, errors.New(fmt.Sprintf("invalid snap for relative time specifier (%s)", s)) - } - return rel, nil -} - -func (rel relative) time(inTime time.Time) (outTime time.Time, err error) { - baseErr := "unable to construct time object" - - switch rel.unit { - case Year: - outTime = inTime.AddDate(rel.iNum, 0, 0) - case Month: - outTime = inTime.AddDate(0, rel.iNum, 0) - case Day: - outTime = inTime.AddDate(0, 0, rel.iNum) - case Hour, Minute, Second: - var d time.Duration - d, err = time.ParseDuration(fmt.Sprintf("%s%s", rel.num, rel.unit)) - if err != nil { - return - } - outTime = inTime.Add(d) - default: - err = errors.Wrap( - errors.New(fmt.Sprintf("relative time unit (%s) is invalid", rel.unit)), - baseErr, - ) - return - } - if rel.snap != "" { - outTime, err = rel.snap.snapTime(outTime) - } - if err != nil { - err = errors.Wrap(err, baseErr) - return - } - if outTime.Unix() < 0 { - err = errors.Wrap(errors.New("time predates epoch"), baseErr) - return - } - return -} - -// ParseRelative parses the string representation of a Lacework relative time. -// Time object is returned in UTC. -// -// t, err := lwtime.ParseRelative("-1y@y") -// if err != nil { -// ... -// } -func ParseRelative(s string) (time.Time, error) { - // time.Now() is intentional here such that snaps work properly - // For instance snapping to @d should snap to the start of the local day - localTime, err := parseRelativeFromTime(s, time.Now()) - return localTime.UTC(), err -} - -func parseRelativeFromTime(s string, fromTime time.Time) (time.Time, error) { - relative, err := newRelative(s) - if err != nil { - return time.Time{}, err - } - - return relative.time(fromTime) -} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/rfc1123z.go b/vendor/github.com/lacework/go-sdk/lwtime/rfc1123z.go deleted file mode 100644 index 412ca62ae..000000000 --- a/vendor/github.com/lacework/go-sdk/lwtime/rfc1123z.go +++ /dev/null @@ -1,29 +0,0 @@ -package lwtime - -import ( - "strings" - "time" -) - -type RFC1123Z time.Time - -func (rfc *RFC1123Z) UnmarshalJSON(b []byte) (err error) { - t := strings.Trim(string(b), `"`) - res, _ := time.Parse(time.RFC1123Z, t) - *rfc = RFC1123Z(res) - return -} - -func (rfc *RFC1123Z) MarshalJSON() ([]byte, error) { - return rfc.ToTime().UTC().MarshalJSON() -} - -func (rfc RFC1123Z) ToTime() time.Time { - return time.Time(rfc) -} -func (rfc RFC1123Z) Format(s string) string { - return rfc.ToTime().Format(s) -} -func (rfc RFC1123Z) UTC() time.Time { - return rfc.ToTime().UTC() -} diff --git a/vendor/github.com/lacework/go-sdk/lwtime/rfc3339.go b/vendor/github.com/lacework/go-sdk/lwtime/rfc3339.go deleted file mode 100644 index a8744aa50..000000000 --- a/vendor/github.com/lacework/go-sdk/lwtime/rfc3339.go +++ /dev/null @@ -1,5 +0,0 @@ -package lwtime - -const ( - RFC3339Milli = "2006-01-02T15:04:05.000Z" -) diff --git a/vendor/github.com/lacework/go-sdk/lwupdater/README.md b/vendor/github.com/lacework/go-sdk/lwupdater/README.md deleted file mode 100644 index 8a70c5e5a..000000000 --- a/vendor/github.com/lacework/go-sdk/lwupdater/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Lacework Updater - -A Go library to check for available updates of Lacework projects. - -## Usage - -Download the library into your `$GOPATH`: - - $ go get github.com/lacework/go-sdk/lwupdater - -Import the library into your tool: - -```go -import "github.com/lacework/go-sdk/lwupdater" -``` - -## Examples - -This example checks for the latest release of this repository (https://github.com/lacework/go-sdk): -```go -package main - -import ( - "fmt" - - "github.com/lacework/go-sdk/lwupdater" -) - -func main() { - var ( - project = "go-sdk" - sdk, err = lwupdater.Check(project, "v0.1.0") - ) - - if err != nil { - fmt.Println("Unable to check for updates: %s", err) - } else { - // Output: The latest release of the go-sdk project is v0.1.7 - fmt.Printf("The latest release of the %s project is %s\n", - project, sdk.LatestVersion, - ) - } -} -``` - -Look at the [_examples/](_examples/) folder for more examples. diff --git a/vendor/github.com/lacework/go-sdk/lwupdater/updater.go b/vendor/github.com/lacework/go-sdk/lwupdater/updater.go deleted file mode 100644 index c0510da3d..000000000 --- a/vendor/github.com/lacework/go-sdk/lwupdater/updater.go +++ /dev/null @@ -1,223 +0,0 @@ -// -// Author:: Salim Afiune Maya () -// Copyright:: Copyright 2020, Lacework Inc. -// License:: Apache License, Version 2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// A library to check for available updates of Lacework projects. -package lwupdater - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/url" - "os" - "strings" - "time" - - "github.com/cenkalti/backoff/v4" - "github.com/lacework/go-sdk/lwlogger" - "github.com/pkg/errors" -) - -const ( - // GithubOrganization is the default Github organization where - // Lacework stores their open source projects - GithubOrganization = "lacework" - - // DisableEnv controls the overall check for updates behavior, when - // this environment variable is set, we do not check for updates - DisableEnv = "LW_UPDATES_DISABLE" -) - -var log = lwlogger.New("") - -// Version is used to check project versions and store it into a cache file -// normally at the directory `~/.config/lacework`, to execute regular version checks -type Version struct { - Project string `json:"project"` - CurrentVersion string `json:"current_version"` - LatestVersion string `json:"latest_version"` - LastCheckTime time.Time `json:"last_check_time"` - Outdated bool `json:"outdated"` - ComponentsLastCheck map[string]time.Time `json:"components_last_check,omitempty"` -} - -// StoreCache stores version information into the provided path -func (cache *Version) StoreCache(path string) error { - buf := new(bytes.Buffer) - if err := json.NewEncoder(buf).Encode(cache); err != nil { - return err - } - - err := os.WriteFile(path, buf.Bytes(), 0644) - if err != nil { - return err - } - - return nil -} - -// StoreCache stores version information into the provided path -func (cache *Version) CheckComponentBefore(component string, checkTime time.Time) bool { - if cache.ComponentsLastCheck == nil { - cache.ComponentsLastCheck = make(map[string]time.Time) - } - - if lastCheck, ok := cache.ComponentsLastCheck[component]; !ok || lastCheck.Before(checkTime) { - return true - } - return false -} - -// Check verifies if the a project is outdated based of the current version -func Check(project, current string) (*Version, error) { - if disabled := os.Getenv(DisableEnv); disabled != "" { - return new(Version), nil - } - - release, err := getGitRelease(project, "latest") - if err != nil { - return new(Version), err - } - - outdated := false - if !strings.Contains(current, "dev") && current != release.TagName { - outdated = true - } - - return &Version{ - Project: project, - CurrentVersion: current, - LatestVersion: release.TagName, - LastCheckTime: time.Now(), - Outdated: outdated, - }, nil -} - -// LoadCache loads a version cache file from the provided path -func LoadCache(path string) (*Version, error) { - cacheJSON, err := os.ReadFile(path) - if err != nil { - return nil, err - } - - var versionCache = new(Version) - err = json.Unmarshal(cacheJSON, versionCache) - return versionCache, err -} - -// getGitRelease uses the git API to fetch the release information of a project. -// This function could hit request rate limits wich are roughly 60 every 30m, to -// check your current rate limits run: curl https://api.github.com/rate_limit -// If the GITHUB_TOKEN environment variable is set, then this function will use -// it to authenticate the API request, which grants higher rate limits. -func getGitRelease(project, version string) (*gitReleaseResponse, error) { - if project == "" { - return nil, errors.New("specify a valid project") - } - if version == "" { - version = "latest" - } - - var ( - c = http.Client{} - u = url.URL{ - Scheme: "https", - Host: "api.github.com", - Path: fmt.Sprintf( - "/repos/%s/%s/releases/latest", - GithubOrganization, project, - ), - } - ) - if version != "latest" { - u.Path = fmt.Sprintf("/repos/%s/%s/releases/tags/%s", - GithubOrganization, project, version) - } - - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - return nil, err - } - - // Set the user agent since it is required - // https://developer.github.com/v3/#user-agent-required - req.Header.Set("User-Agent", "lacework-updater") - - token := os.Getenv("GITHUB_TOKEN") - if len(token) > 0 { - req.Header.Set("Authorization", "Bearer "+token) - } - - var resp *http.Response - err = backoff.Retry(func() error { - resp, err = c.Do(req) - if err != nil { - return err - } - if resp.StatusCode < 200 || resp.StatusCode > 299 { - logHeaders(resp) - return errors.New(resp.Status) - } - return nil - }, backoffStrategy()) - if err != nil { - return nil, err - } - - var gitRelRes gitReleaseResponse - if err := json.NewDecoder(resp.Body).Decode(&gitRelRes); err != nil { - return nil, err - } - - return &gitRelRes, nil -} - -func backoffStrategy() *backoff.ExponentialBackOff { - strategy := backoff.NewExponentialBackOff() - strategy.InitialInterval = 2 * time.Second - strategy.MaxElapsedTime = 1 * time.Minute - return strategy -} - -func logHeaders(resp *http.Response) { - var headers strings.Builder - for key, values := range resp.Header { - headers.WriteString(fmt.Sprintf("%s: %s\n", key, strings.Join(values, ","))) - } - log.Debug(headers.String()) -} - -type gitReleaseResponse struct { - ID int32 `json:"id"` - Url string `json:"url"` - HtmlUrl string `json:"html_url"` - AssetsUrl string `json:"assets_url"` - UploadUrl string `json:"upload_url"` - TarballUrl string `json:"tarball_url"` - ZipballUrl string `json:"zipball_url"` - NodeID string `json:"node_id"` - TagName string `json:"tag_name"` - TargetCommitish string `json:"target_commitish"` - Name string `json:"name"` - Body string `json:"body"` - Draft bool `json:"draft"` - Prerelease bool `json:"prerelease"` - CreatedAt time.Time `json:"created_at"` - PublishedAt time.Time `json:"published_at"` -} diff --git a/vendor/modules.txt b/vendor/modules.txt index bea486478..ecef71dfb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -446,45 +446,6 @@ github.com/kr/pty # github.com/kyokomi/emoji/v2 v2.2.12 ## explicit; go 1.14 github.com/kyokomi/emoji/v2 -# github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65 -## explicit; go 1.21 -github.com/lacework/go-sdk/api -github.com/lacework/go-sdk/cli/cdk/client/go -github.com/lacework/go-sdk/cli/cdk/go/proto/v1 -github.com/lacework/go-sdk/cli/cmd -github.com/lacework/go-sdk/internal/archive -github.com/lacework/go-sdk/internal/array -github.com/lacework/go-sdk/internal/cache -github.com/lacework/go-sdk/internal/capturer -github.com/lacework/go-sdk/internal/databox -github.com/lacework/go-sdk/internal/failon -github.com/lacework/go-sdk/internal/file -github.com/lacework/go-sdk/internal/format -github.com/lacework/go-sdk/internal/intgguid -github.com/lacework/go-sdk/internal/lacework -github.com/lacework/go-sdk/internal/pointer -github.com/lacework/go-sdk/internal/unique -github.com/lacework/go-sdk/internal/validate -github.com/lacework/go-sdk/lwcloud/gcp/helpers -github.com/lacework/go-sdk/lwcloud/gcp/resources/folders -github.com/lacework/go-sdk/lwcloud/gcp/resources/instances -github.com/lacework/go-sdk/lwcloud/gcp/resources/models -github.com/lacework/go-sdk/lwcloud/gcp/resources/projects -github.com/lacework/go-sdk/lwcomponent -github.com/lacework/go-sdk/lwconfig -github.com/lacework/go-sdk/lwdomain -github.com/lacework/go-sdk/lwgenerate -github.com/lacework/go-sdk/lwgenerate/aws -github.com/lacework/go-sdk/lwgenerate/aws_controltower -github.com/lacework/go-sdk/lwgenerate/aws_eks_audit -github.com/lacework/go-sdk/lwgenerate/azure -github.com/lacework/go-sdk/lwgenerate/gcp -github.com/lacework/go-sdk/lwgenerate/oci -github.com/lacework/go-sdk/lwlogger -github.com/lacework/go-sdk/lwrunner -github.com/lacework/go-sdk/lwseverity -github.com/lacework/go-sdk/lwtime -github.com/lacework/go-sdk/lwupdater # github.com/magiconair/properties v1.8.6 ## explicit; go 1.13 github.com/magiconair/properties From a845ad2cd0da525054de32cbab6bec208b5e52f9 Mon Sep 17 00:00:00 2001 From: Lei Jin Date: Wed, 30 Oct 2024 19:52:11 +0000 Subject: [PATCH 3/3] chore: Add go-sdk-v2 Signed-off-by: Lei Jin --- api/version.go | 2 +- go.mod | 3 ++- go.sum | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/api/version.go b/api/version.go index 7b8f06b8c..350516f0a 100644 --- a/api/version.go +++ b/api/version.go @@ -1,5 +1,5 @@ // Code generated by: scripts/version_updater.sh -// File generated at: 20241030175518 +// File generated at: 20241030195149 // // <<< DO NOT EDIT >>> // diff --git a/go.mod b/go.mod index a778edf3f..37ea0bde0 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/lacework/go-sdk +module github.com/lacework/go-sdk/v2 go 1.21 @@ -62,6 +62,7 @@ require ( github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.0 github.com/hashicorp/consul/sdk v0.13.1 + github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65 github.com/mattn/go-isatty v0.0.18 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/otiai10/copy v1.14.0 diff --git a/go.sum b/go.sum index 2fcb558a6..2cec564f6 100644 --- a/go.sum +++ b/go.sum @@ -355,6 +355,8 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kyokomi/emoji/v2 v2.2.12 h1:sSVA5nH9ebR3Zji1o31wu3yOwD1zKXQA2z0zUyeit60= github.com/kyokomi/emoji/v2 v2.2.12/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= +github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65 h1:A4LDKoyuC0fKknf7Nd6BM3MkFqzlbmjs0gXDPsH5szQ= +github.com/lacework/go-sdk v1.54.1-0.20241030155210-2b0ffd411c65/go.mod h1:l0kCskNExDs1E8fBfpaZeafC42pmKucdXn3nZO1iyLI= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=