diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 013b08951fd..49123f81295 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -4,97 +4,138 @@ env: VAULT_PATH: "kv/ci-shared/observability-ingest/cloud/gcp" DOCKER_REGISTRY: "docker.elastic.co" steps: - - label: "Unit tests - Ubuntu 22.04" - key: "unit-tests-2204" - command: ".buildkite/scripts/steps/unit-tests.sh" - artifact_paths: - - "build/TEST-**" - - "build/diagnostics/*" - - "coverage.out" - agents: - provider: "gcp" - image: "family/core-ubuntu-2204" - retry: - manual: - allowed: true + - group: "Unit tests" + key: "unit-tests" + steps: + - label: "Unit tests - Ubuntu 22.04" + key: "unit-tests-2204" + command: ".buildkite/scripts/steps/unit-tests.sh" + artifact_paths: + - "build/TEST-**" + - "build/diagnostics/*" + - "coverage.out" + agents: + provider: "gcp" + image: "family/core-ubuntu-2204" + retry: + manual: + allowed: true - - label: "Unit tests - Ubuntu 22.04 ARM64" - key: "unit-tests-2204-arm64" - command: ".buildkite/scripts/steps/unit-tests.sh" - artifact_paths: - - "build/TEST-**" - - "build/diagnostics/*" - - "coverage.out" - agents: - provider: "aws" - imagePrefix: "core-ubuntu-2204-aarch64" - diskSizeGb: 200 - instanceType: "m6g.4xlarge" - retry: - manual: - allowed: true + - label: "Unit tests - Ubuntu 22.04 ARM64" + key: "unit-tests-2204-arm64" + command: ".buildkite/scripts/steps/unit-tests.sh" + artifact_paths: + - "build/TEST-**" + - "build/diagnostics/*" + - "coverage.out" + agents: + provider: "aws" + imagePrefix: "core-ubuntu-2204-aarch64" + diskSizeGb: 200 + instanceType: "m6g.xlarge" + retry: + manual: + allowed: true - - label: "Unit tests - Windows 2022" - key: "unit-tests-win2022" - command: ".\\.buildkite\\scripts\\steps\\unit-tests.ps1" - artifact_paths: - - "build/TEST-**" - - "build/diagnostics/*" - - "coverage.out" - agents: - provider: "gcp" - image: "family/core-windows-2022" - machine_type: "n2-standard-8" - disk_size: 200 - disk_type: "pd-ssd" - retry: - manual: - allowed: true + - label: "Unit tests - Windows 2022" + key: "unit-tests-win2022" + command: ".\\.buildkite\\scripts\\steps\\unit-tests.ps1" + artifact_paths: + - "build/TEST-**" + - "build/diagnostics/*" + - "coverage.out" + agents: + provider: "gcp" + image: "family/core-windows-2022" + machine_type: "n2-standard-8" + disk_size: 200 + disk_type: "pd-ssd" + retry: + manual: + allowed: true - - label: "Unit tests - Windows 2016" - key: "unit-tests-win2016" - command: ".\\.buildkite\\scripts\\steps\\unit-tests.ps1" - artifact_paths: - - "build/TEST-**" - - "build/diagnostics/*" - - "coverage.out" - agents: - provider: "gcp" - image: "family/core-windows-2016" - machine_type: "n2-standard-8" - disk_size: 200 - disk_type: "pd-ssd" - retry: - manual: - allowed: true + - label: "Unit tests - Windows 2016" + key: "unit-tests-win2016" + command: ".\\.buildkite\\scripts\\steps\\unit-tests.ps1" + artifact_paths: + - "build/TEST-**" + - "build/diagnostics/*" + - "coverage.out" + agents: + provider: "gcp" + image: "family/core-windows-2016" + machine_type: "n2-standard-8" + disk_size: 200 + disk_type: "pd-ssd" + retry: + manual: + allowed: true - - label: "Unit tests - MacOS 13 ARM" - key: "unit-tests-macos-13-arm" - command: ".buildkite/scripts/steps/unit-tests.sh" - artifact_paths: - - "build/TEST-**" - - "build/diagnostics/*" - - "coverage.out" - agents: - provider: orka - imagePrefix: generic-13-ventura-arm - retry: - manual: - allowed: true + # Runs inly on the main branch + - label: "Unit tests - MacOS 13 ARM" + key: "unit-tests-macos-13-arm" + command: ".buildkite/scripts/steps/unit-tests.sh" + branches: main + artifact_paths: + - "build/TEST-**" + - "build/diagnostics/*" + - "coverage.out" + agents: + provider: orka + imagePrefix: generic-13-ventura-arm + retry: + manual: + allowed: true - - label: "Unit tests - MacOS 13" - key: "unit-tests-macos-13" - command: ".buildkite/scripts/steps/unit-tests.sh" - artifact_paths: - - "build/TEST-**" - - "build/diagnostics/*" - - "coverage.out" - agents: - provider: orka - imagePrefix: generic-13-ventura-x64 - retry: - manual: - allowed: true + - label: "Unit tests - MacOS 13" + key: "unit-tests-macos-13" + command: ".buildkite/scripts/steps/unit-tests.sh" + artifact_paths: + - "build/TEST-**" + - "build/diagnostics/*" + - "coverage.out" + agents: + provider: orka + imagePrefix: generic-13-ventura-x64 + retry: + manual: + allowed: true + + - group: "Desktop Windows tests" + key: "extended-windows" + steps: + - label: "Unit tests - Windows 10" + key: "unit-tests-win10" + command: ".\\.buildkite\\scripts\\steps\\unit-tests.ps1" + artifact_paths: + - "build/TEST-**" + - "build/diagnostics/*" + - "coverage.out" + agents: + provider: "gcp" + # TODO create own image + image: "family/endpoint-windows-10-tester-rel" + machine_type: "n2-standard-8" + disk_type: "pd-ssd" + retry: + manual: + allowed: true + - label: "Unit tests - Windows 11" + key: "unit-tests-win11" + command: ".\\.buildkite\\scripts\\steps\\unit-tests.ps1" + artifact_paths: + - "build/TEST-**" + - "build/diagnostics/*" + - "coverage.out" + agents: + provider: "gcp" + # TODO create own image + image: "family/endpoint-windows-11-tester-rel" + machine_type: "n2-standard-8" + disk_type: "pd-ssd" + retry: + manual: + allowed: true - label: "Merge coverage reports" key: "merge-coverage" @@ -107,19 +148,16 @@ steps: unit-tests-win2016 unit-tests-win2022 unit-tests-macos-13 - unit-tests-macos-13-arm + unit-tests-win10 + unit-tests-win11 " artifact_paths: - "build/TEST-**" agents: image: "golang:1.20.10" depends_on: - - unit-tests-2204 - - unit-tests-2204-arm64 - - unit-tests-win2022 - - unit-tests-win2016 - - unit-tests-macos-13 - - unit-tests-macos-13-arm + - unit-tests + - extended-windows allow_dependency_failure: true - group: "K8s tests" diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index 870e9684742..23f3ca1e707 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -186,67 +186,7 @@ pipeline { } } } - } - stage('extended windows') { - when { - // Always when running builds on branches/tags - // Enable if extended windows support related changes. - beforeAgent true - anyOf { - not { changeRequest() } - expression { return isExtendedWindowsEnabled() && env.ONLY_DOCS == "false"} - } - } - failFast false - matrix { - agent {label "${PLATFORM} && windows-immutable"} - options { skipDefaultCheckout() } - axes { - axis { - name 'PLATFORM' - values 'windows-8', 'windows-10', 'windows-11' - } - } - stages { - stage('build'){ - options { skipDefaultCheckout() } - steps { - withGithubNotify(context: "Build-${PLATFORM}") { - deleteDir() - unstashV2(name: 'source', bucket: "${JOB_GCS_BUCKET}", credentialsId: "${JOB_GCS_CREDENTIALS}") - withMageEnv(){ - dir("${BASE_DIR}"){ - cmd(label: 'Go build', script: 'mage build') - } - } - } - } - } - stage('Test') { - options { skipDefaultCheckout() } - steps { - withGithubNotify(context: "Test-${PLATFORM}") { - withMageEnv(){ - dir("${BASE_DIR}"){ - withEnv(["RACE_DETECTOR=true", "TEST_COVERAGE=${isCodeCoverageEnabled()}"]) { - cmd(label: 'Go unitTest', script: 'mage unitTest') - } - } - } - } - } - post { - always { - junit(allowEmptyResults: true, keepLongStdio: true, testResults: "${BASE_DIR}/build/TEST-*.xml") - whenTrue(isCodeCoverageEnabled()) { - coverageReport(baseDir: "**/build", reportFiles: 'TEST-go-unit.html', coverageFiles: 'TEST-go-unit-cov.xml') - } - } - } - } - } - } - } + } } post { cleanup { diff --git a/changelog/8.11.0.asciidoc b/changelog/8.11.0.asciidoc new file mode 100644 index 00000000000..30f534fe478 --- /dev/null +++ b/changelog/8.11.0.asciidoc @@ -0,0 +1,107 @@ +// begin 8.11.0 relnotes + +[[release-notes-8.11.0]] +== 8.11.0 + +Review important information about the 8.11.0 release. + +[discrete] +[[security-updates-8.11.0]] +=== Security updates + + +elastic-agent:: + +* Upgrade To Go 1.20.10. {elastic-agent-pull}https://github.com/elastic/elastic-agent/pull/3601[#https://github.com/elastic/elastic-agent/pull/3601] + +[discrete] +[[breaking-changes-8.11.0]] +=== Breaking changes + +Breaking changes can prevent your application from optimal operation and +performance. Before you upgrade, review the breaking changes, then mitigate the +impact to your application. + +// TODO: add details and impact + +all:: + +[discrete] +[[breaking-https://github.com/elastic/beats/pull/36681]] +.Enable Compression By Default For Elasticsearch Outputs. {all-pull}https://github.com/elastic/beats/pull/36681[#https://github.com/elastic/beats/pull/36681] {all-issue}https://github.com/elastic/ingest-dev/issues/2458[#https://github.com/elastic/ingest-dev/issues/2458] +[%collapsible] +==== +The default compression level for Elasticsearch outputs is changing from 0 to 1. On typical workloads this is expected to decrease network data volume by 70-80%, while increasing cpu use by 20-25% and ingestion time by 10%. The previous behavior can be restored by adding 'compression_level: 0' to the output configuration. +==== +elastic-agent:: + +[discrete] +[[breaking-https://github.com/elastic/elastic-agent/pull/3593]] +.Elastic-Agent-Autodiscover To V0.6.4. Disables Metadata For Deployment And Cronjob. {elastic-agent-pull}https://github.com/elastic/elastic-agent/pull/3593[#https://github.com/elastic/elastic-agent/pull/3593] +[%collapsible] +==== +Elastic-agent-autodiscover library by default comes with add_resource_metadata.deployment=false and add_resource_metadata.cronjob=false. Pods that will be created from deployments or cronjobs will not have the extra metadata field for kubernetes.deployment or kubernetes.cronjob respectively. +==== + + + + + +[discrete] +[[new-features-8.11.0]] +=== New features + +The 8.11.0 release adds the following new and notable features. + + + + +* Enable Tamper Protection Feature Flag By Default For Elastic Agent Version 8.11.0. {-pull}https://github.com/elastic/elastic-agent/pull/3478[#https://github.com/elastic/elastic-agent/pull/3478] +CLI:: + +* Add Colors To Agent Messages Printed By The Elastic-Agent Logs Command Based On Their Level. {CLI-pull}https://github.com/elastic/elastic-agent/pull/3345[#https://github.com/elastic/elastic-agent/pull/3345] +elastic-agent:: + +* Add Support For Processors In Hints-Based Kubernetes Autodiscover. {elastic-agent-pull}https://github.com/elastic/elastic-agent/pull/3107[#https://github.com/elastic/elastic-agent/pull/3107] {elastic-agent-issue}https://github.com/elastic/elastic-agent/issues/2959[#https://github.com/elastic/elastic-agent/issues/2959] +* Print Out Elastic Agent Installation Steps To Show Progress. {elastic-agent-pull}https://github.com/elastic/elastic-agent/pull/3338[#https://github.com/elastic/elastic-agent/pull/3338] + + +[discrete] +[[enhancements-8.11.0]] +=== Enhancements + + + + +* Fix The Kubernetes `Deploy/Kubernetes/Creator_k8.sh` Script To Correcly Exclude Configmaps. {-pull}https://github.com/elastic/elastic-agent/pull/3396[#https://github.com/elastic/elastic-agent/pull/3396] +elastic-agent:: + +* Support The Netinfo Variable In Elastic Kubernetes Manifests. Setting A New Environmental Variable `Elastic_netinfo=False` Globally Disables The `Netinfo.enabled` Parameter Of The `Add_host_metadata` Processor. This Disables The Indexing Of `Host.ip` And `Host.mac` Fields. {elastic-agent-pull}https://github.com/elastic/elastic-agent/pull/3354[#https://github.com/elastic/elastic-agent/pull/3354] +monitoring:: + +* Increase Agent Monitoring Metrics Interval From 10S To 60S To Reduce The Default Ingestion Load And Long Term Storage Requirements. {monitoring-pull}https://github.com/elastic/elastic-agent/pull/3578[#https://github.com/elastic/elastic-agent/pull/3578] + + + + +[discrete] +[[bug-fixes-8.11.0]] +=== Bug fixes + + + + +* The Elastic Agent Uninstall Process Now Finds And Kills Any Running Upgrade Watcher Process. Uninstalls Initiated Within 10 Minutes Of A Previous Upgrade Now Work As Expected. {-pull}https://github.com/elastic/elastic-agent/pull/3384[#https://github.com/elastic/elastic-agent/pull/3384] {-issue}https://github.com/elastic/elastic-agent/issues/3371[#https://github.com/elastic/elastic-agent/issues/3371] +agent:: + +* Upgrade `Elastic-Agent-Libs` To V0.6.0 To Fix The Agent Windows Service Becoming Unresponsive. Fixes Windows Service Timeouts During Wmi Queries And During Service Shutdown. {agent-pull}https://github.com/elastic/elastic-agent/pull/3632[#https://github.com/elastic/elastic-agent/pull/3632] +elastic-agent:: + +* Prevent A Standalone Elastic Agent From Being Upgraded If An Upgrade Is Already In Progress. {elastic-agent-pull}https://github.com/elastic/elastic-agent/pull/3473[#https://github.com/elastic/elastic-agent/pull/3473] {elastic-agent-issue}https://github.com/elastic/elastic-agent/issues/2706[#https://github.com/elastic/elastic-agent/issues/2706] +* Fix A Bug That Affected Reporting Progress Of The Agent Artifact Download During An Upgrade. {elastic-agent-pull}https://github.com/elastic/elastic-agent/pull/3548[#https://github.com/elastic/elastic-agent/pull/3548] +* Increase Wait Period Between Service Restarts On Failure To 15S On Windows. {elastic-agent-pull}https://github.com/elastic/elastic-agent/pull/3657[#https://github.com/elastic/elastic-agent/pull/3657] +runtime:: + +* Prevent Multiple Attempts To Stop An Already Stopped Service. {runtime-pull}https://github.com/elastic/elastic-agent/pull/3482[#https://github.com/elastic/elastic-agent/pull/3482] + +// end 8.11.0 relnotes diff --git a/changelog/8.11.0.yaml b/changelog/8.11.0.yaml new file mode 100644 index 00000000000..b20c71f12e7 --- /dev/null +++ b/changelog/8.11.0.yaml @@ -0,0 +1,182 @@ +version: 8.11.0 +entries: + - kind: feature + summary: Add support for processors in hints-based Kubernetes autodiscover. + description: "" + component: elastic-agent + pr: + - https://github.com/elastic/elastic-agent/pull/3107 + issue: + - https://github.com/elastic/elastic-agent/issues/2959 + timestamp: 1691060806 + file: + name: 1691060806-Processor-support-for-hints-autodiscover.yaml + checksum: c55b54ba6747d5ac659649fd4e4a16e941364f8e + - kind: feature + summary: Print out Elastic Agent installation steps to show progress. + description: "" + component: elastic-agent + pr: + - https://github.com/elastic/elastic-agent/pull/3338 + issue: [] + timestamp: 1693427183 + file: + name: 1693427183-install-progress.yaml + checksum: fb4c231a3a23abd6be758a4342484d10ce73aa6c + - kind: feature + summary: Add colors to Agent messages printed by the elastic-agent logs command based on their level. + description: "" + component: CLI + pr: + - https://github.com/elastic/elastic-agent/pull/3345 + issue: [] + timestamp: 1693813219 + file: + name: 1693813219-log-command-colors.yaml + checksum: 53b5a44c3799fa9888ad7beba34c2858bca7b3e2 + - kind: enhancement + summary: Support the NETINFO variable in Elastic Kubernetes manifests. Setting a new environmental variable `ELASTIC_NETINFO=false` globally disables the `netinfo.enabled` parameter of the `add_host_metadata` processor. This disables the indexing of `host.ip` and `host.mac` fields. + description: "" + component: elastic-agent + pr: + - https://github.com/elastic/elastic-agent/pull/3354 + issue: [] + timestamp: 1693920861 + file: + name: 1693920861-netinfo-manifests.yaml + checksum: e9e5a459e64a189b7b74f5689c57d41d4fbf442c + - kind: bug-fix + summary: The Elastic Agent uninstall process now finds and kills any running upgrade Watcher process. Uninstalls initiated within 10 minutes of a previous upgrade now work as expected. + description: "" + component: "" + pr: + - https://github.com/elastic/elastic-agent/pull/3384 + issue: + - https://github.com/elastic/elastic-agent/issues/3371 + timestamp: 1694187216 + file: + name: 1694187216-Uninstall-finds-and-kills-any-running-watcher-process.yaml + checksum: 31df28d9490d0c844b88d48f1aa3504cce2365ee + - kind: enhancement + summary: Fix the Kubernetes `deploy/kubernetes/creator_k8.sh` script to correcly exclude configmaps. + description: "" + component: "" + pr: + - https://github.com/elastic/elastic-agent/pull/3396 + issue: [] + timestamp: 1694439479 + file: + name: 1694439479-fix_creatork8sscript.yaml + checksum: c65f7e65ebac64f9332a407f2449c88e30bd4239 + - kind: bug-fix + summary: Prevent a standalone Elastic Agent from being upgraded if an upgrade is already in progress. + description: "" + component: elastic-agent + pr: + - https://github.com/elastic/elastic-agent/pull/3473 + issue: + - https://github.com/elastic/elastic-agent/issues/2706 + timestamp: 1695685534 + file: + name: 1695685534-standalone-prevent-quick-upgrades.yaml + checksum: 9df813abbdf5ae1558b16b1052a5bbc238cf7ae1 + - kind: feature + summary: Enable tamper protection feature flag by default for Elastic Agent version 8.11.0. + description: "" + component: "" + pr: + - https://github.com/elastic/elastic-agent/pull/3478 + issue: [] + timestamp: 1695780865 + file: + name: 1695780865-Enable-tamper-protection-feature-flag-by-default-for-Agent-8.11.0.yaml + checksum: 46eac90f4af827db837fc7d19c1131328b011952 + - kind: bug-fix + summary: Prevent multiple attempts to stop an already stopped service. + description: "" + component: runtime + pr: + - https://github.com/elastic/elastic-agent/pull/3482 + issue: [] + timestamp: 1695920792 + file: + name: 1695920792-Prevent-multiple-stops-of-services.yaml + checksum: 8a0b54855b27c98e6af3d60df1c9cb04f935efee + - kind: breaking-change + summary: Enable compression by default for Elasticsearch outputs + description: 'The default compression level for Elasticsearch outputs is changing from 0 to 1. On typical workloads this is expected to decrease network data volume by 70-80%, while increasing cpu use by 20-25% and ingestion time by 10%. The previous behavior can be restored by adding ''compression_level: 0'' to the output configuration.' + component: all + pr: + - https://github.com/elastic/beats/pull/36681 + issue: + - https://github.com/elastic/ingest-dev/issues/2458 + timestamp: 1696361138 + file: + name: 1696361138-es-default-compression.yaml + checksum: f3bee390b1af6bc5049315e25e5c14d0ff23240e + - kind: bug-fix + summary: Fix a bug that affected reporting progress of the Agent artifact download during an upgrade. + description: "" + component: elastic-agent + pr: + - https://github.com/elastic/elastic-agent/pull/3548 + issue: [] + timestamp: 1696530758 + file: + name: 1696530758-bugfix-upgrade-progress-reporter.yaml + checksum: a168f2ab2a26e7813bb230b5f663ecbeca9d624c + - kind: enhancement + summary: Increase Agent monitoring metrics interval from 10s to 60s to reduce the default ingestion load and long term storage requirements. + description: "" + component: monitoring + pr: + - https://github.com/elastic/elastic-agent/pull/3578 + issue: [] + timestamp: 1696955150 + file: + name: 1696955150-Slow-down-agent-monitoring-metrics-interval-to-60s.yaml + checksum: ead85ab471d5a6f609c367d57817d1c2299d93f2 + - kind: breaking-change + summary: Elastic-agent-autodiscover to v0.6.4. Disables metadata for deployment and cronjob + description: Elastic-agent-autodiscover library by default comes with add_resource_metadata.deployment=false and add_resource_metadata.cronjob=false. Pods that will be created from deployments or cronjobs will not have the extra metadata field for kubernetes.deployment or kubernetes.cronjob respectively. + component: elastic-agent + pr: + - https://github.com/elastic/elastic-agent/pull/3593 + issue: [] + timestamp: 1697103197 + file: + name: 1697103197-updating_agentautodiscovery_811.yaml + checksum: b937c2a635860c3497502b018b8b997143ba5fc3 + - kind: security + summary: Upgrade to Go 1.20.10. + description: "" + component: elastic-agent + pr: + - https://github.com/elastic/elastic-agent/pull/3601 + issue: [] + timestamp: 1697229987 + file: + name: 1697229987-Upgrade-to-Go-1.20.10.yaml + checksum: e31fa72a840bb0616000830d86a3b18f1a22aae6 + - kind: bug-fix + summary: Upgrade `elastic-agent-libs` to v0.6.0 to fix the Agent Windows service becoming unresponsive. Fixes Windows service timeouts during WMI queries and during service shutdown. + description: "" + component: agent + pr: + - https://github.com/elastic/elastic-agent/pull/3632 + issue: [] + timestamp: 1697662109 + file: + name: 1697662109-upgrade-elastic-agent-libs-to-v0.6.0.yaml + checksum: 70d3e8469fce838d73781c409d9dd7592adbddc8 + - kind: bug-fix + summary: Increase wait period between service restarts on failure to 15s on Windows. + description: This is the same value used by other Elastic windows services like endpoint-security. + component: elastic-agent + pr: + - https://github.com/elastic/elastic-agent/pull/3657 + issue: [] + timestamp: 1698259940 + file: + name: 1698259940-Increase-wait-period-between-service-restarts-on-failure-to-15s-on-Windows.yaml + checksum: 12985bc12758581d4f236ebf0928fde307ffceca diff --git a/changelog/fragments/1691060806-Processor-support-for-hints-autodiscover.yaml b/changelog/fragments/1691060806-Processor-support-for-hints-autodiscover.yaml deleted file mode 100644 index 0bed8190838..00000000000 --- a/changelog/fragments/1691060806-Processor-support-for-hints-autodiscover.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: feature - -# Change summary; a 80ish characters long description of the change. -summary: Add support for processors in hints' based k8s autodiscover - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: elastic-agent - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -pr: https://github.com/elastic/elastic-agent/pull/3107 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -issue: https://github.com/elastic/elastic-agent/issues/2959 diff --git a/changelog/fragments/1693427183-install-progress.yaml b/changelog/fragments/1693427183-install-progress.yaml deleted file mode 100644 index 1725e9e40f1..00000000000 --- a/changelog/fragments/1693427183-install-progress.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: feature - -# Change summary; a 80ish characters long description of the change. -summary: Print out Elastic Agent installation steps to show progress - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: elastic-agent - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -pr: https://github.com/elastic/elastic-agent/pull/3338 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 diff --git a/changelog/fragments/1693813219-log-command-colors.yaml b/changelog/fragments/1693813219-log-command-colors.yaml deleted file mode 100644 index cdd5e34a369..00000000000 --- a/changelog/fragments/1693813219-log-command-colors.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: feature - -# Change summary; a 80ish characters long description of the change. -summary: Add colors for log messages based on their level - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; a word indicating the component this changeset affects. -component: CLI -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -#pr: https://github.com/owner/repo/1234 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 diff --git a/changelog/fragments/1693920861-netinfo-manifests.yaml b/changelog/fragments/1693920861-netinfo-manifests.yaml deleted file mode 100644 index 9c167de170a..00000000000 --- a/changelog/fragments/1693920861-netinfo-manifests.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: enhancement - -# Change summary; a 80ish characters long description of the change. -summary: Setting a new environmental variable ELASTIC_NETINFO=false globally disables the netinfo.enabled parameter of add_host_metadata processor. This disables the indexing of host.ip and host.mac fields. - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: elastic-agent - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -#pr: https://github.com/owner/repo/1234 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 diff --git a/changelog/fragments/1694187216-Uninstall-finds-and-kills-any-running-watcher-process.yaml b/changelog/fragments/1694187216-Uninstall-finds-and-kills-any-running-watcher-process.yaml deleted file mode 100644 index 057cc06d33e..00000000000 --- a/changelog/fragments/1694187216-Uninstall-finds-and-kills-any-running-watcher-process.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: bug-fix - -# Change summary; a 80ish characters long description of the change. -summary: Uninstall finds and kills any running watcher process - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -pr: https://github.com/elastic/elastic-agent/pull/3384 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -issue: https://github.com/elastic/elastic-agent/issues/3371 diff --git a/changelog/fragments/1694439479-fix_creatork8sscript.yaml b/changelog/fragments/1694439479-fix_creatork8sscript.yaml deleted file mode 100644 index 7f7a9d1fd95..00000000000 --- a/changelog/fragments/1694439479-fix_creatork8sscript.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: bug - -# Change summary; a 80ish characters long description of the change. -summary: Fixing deploy/kubernetes/creator_k8.sh script to correcly exclude configmaps - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -#pr: https://github.com/owner/repo/1234 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 diff --git a/changelog/fragments/1695685534-standalone-prevent-quick-upgrades.yaml b/changelog/fragments/1695685534-standalone-prevent-quick-upgrades.yaml deleted file mode 100644 index fffa59953be..00000000000 --- a/changelog/fragments/1695685534-standalone-prevent-quick-upgrades.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: bug-fix - -# Change summary; a 80ish characters long description of the change. -summary: Prevent a standalone Elastic Agent from being upgraded if an upgrade is already in progress. - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: elastic-agent - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -pr: https://github.com/elastic/elastic-agent/pull/3473 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -issue: https://github.com/elastic/elastic-agent/issues/2706 diff --git a/changelog/fragments/1695780865-Enable-tamper-protection-feature-flag-by-default-for-Agent-8.11.0.yaml b/changelog/fragments/1695780865-Enable-tamper-protection-feature-flag-by-default-for-Agent-8.11.0.yaml deleted file mode 100644 index 75a33566b4b..00000000000 --- a/changelog/fragments/1695780865-Enable-tamper-protection-feature-flag-by-default-for-Agent-8.11.0.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: feature - -# Change summary; a 80ish characters long description of the change. -summary: Enable tamper protection feature flag by default for Agent 8.11.0 - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; a word indicating the component this changeset affects. -component: - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -pr: https://github.com/elastic/elastic-agent/pull/3478 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 diff --git a/changelog/fragments/1695920792-Prevent-multiple-stops-of-services.yaml b/changelog/fragments/1695920792-Prevent-multiple-stops-of-services.yaml deleted file mode 100644 index e15f5d6e927..00000000000 --- a/changelog/fragments/1695920792-Prevent-multiple-stops-of-services.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: bug-fix - -# Change summary; a 80ish characters long description of the change. -summary: Prevent multiple attempts to stop an already stopped service - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; a word indicating the component this changeset affects. -component: runtime - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -#pr: https://github.com/owner/repo/1234 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 diff --git a/changelog/fragments/1696319263-add-assetbeat-dependency.yaml b/changelog/fragments/1696319263-add-assetbeat-dependency.yaml deleted file mode 100644 index ab428367953..00000000000 --- a/changelog/fragments/1696319263-add-assetbeat-dependency.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: feature - -# Change summary; a 80ish characters long description of the change. -summary: Add assetbeat among the external dependencies needed to package Elastic Agent - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: elastic-agent - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -#pr: https://github.com/owner/repo/1234 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -issue: https://github.com/elastic/obs-infraobs-team/issues/1114 diff --git a/changelog/fragments/1696361138-es-default-compression.yaml b/changelog/fragments/1696361138-es-default-compression.yaml deleted file mode 100644 index 2323467e47f..00000000000 --- a/changelog/fragments/1696361138-es-default-compression.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: breaking-change - -# Change summary; a 80ish characters long description of the change. -summary: Enable compression by default for Elasticsearch outputs - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -description: "The default compression level for Elasticsearch outputs is changing from 0 to 1. On typical workloads this is expected to decrease network data volume by 70-80%, while increasing cpu use by 20-25% and ingestion time by 10%. The previous behavior can be restored by adding 'compression_level: 0' to the output configuration." - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: all - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -pr: https://github.com/elastic/beats/pull/36681 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -issue: https://github.com/elastic/ingest-dev/issues/2458 diff --git a/changelog/fragments/1696530758-bugfix-upgrade-progress-reporter.yaml b/changelog/fragments/1696530758-bugfix-upgrade-progress-reporter.yaml deleted file mode 100644 index d39f4fa2f41..00000000000 --- a/changelog/fragments/1696530758-bugfix-upgrade-progress-reporter.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: bug-fix - -# Change summary; a 80ish characters long description of the change. -summary: Periodically report progress of Elastic Agent artifact download during upgrade - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: elastic-agent - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -pr: https://github.com/elastic/elastic-agent/pull/3548 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 diff --git a/changelog/fragments/1697662109-upgrade-elastic-agent-libs-to-v0.6.0.yaml b/changelog/fragments/1697662109-upgrade-elastic-agent-libs-to-v0.6.0.yaml deleted file mode 100644 index 192434f3b5c..00000000000 --- a/changelog/fragments/1697662109-upgrade-elastic-agent-libs-to-v0.6.0.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: bug-fix - -# Change summary; a 80ish characters long description of the change. -summary: upgrade elastic-agent-libs to v0.6.0 - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -description: 'allows elastic-agent running as a windows service to receive more than one change request.' - -# Affected component; a word indicating the component this changeset affects. -component: agent - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -#pr: https://github.com/owner/repo/1234 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 diff --git a/changelog/fragments/1696955150-Slow-down-agent-monitoring-metrics-interval-to-60s.yaml b/changelog/fragments/1697662209-duplicate-tags.yaml similarity index 87% rename from changelog/fragments/1696955150-Slow-down-agent-monitoring-metrics-interval-to-60s.yaml rename to changelog/fragments/1697662209-duplicate-tags.yaml index bf86933d97e..710cc775366 100644 --- a/changelog/fragments/1696955150-Slow-down-agent-monitoring-metrics-interval-to-60s.yaml +++ b/changelog/fragments/1697662209-duplicate-tags.yaml @@ -11,22 +11,22 @@ kind: enhancement # Change summary; a 80ish characters long description of the change. -summary: Increase agent monitoring metrics interval from 10s to 60s to reduce load +summary: Remove duplicated tags when specified during the Agent enrollment. # Long description; in case the summary is not enough to describe the change # this field accommodate a description without length limits. # NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: +description: "" # Affected component; a word indicating the component this changeset affects. -component: monitoring +component: agent # PR URL; optional; the PR number that added the changeset. # If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. # NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. # Please provide it if you are adding a fragment for a different PR. -#pr: https://github.com/owner/repo/1234 +pr: https://github.com/elastic/elastic-agent/pull/3740 # Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). # If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 +issue: https://github.com/elastic/elastic-agent/issues/858 diff --git a/changelog/fragments/1698259940-Increase-wait-period-between-service-restarts-on-failure-to-15s-on-Windows.yaml b/changelog/fragments/1698259940-Increase-wait-period-between-service-restarts-on-failure-to-15s-on-Windows.yaml deleted file mode 100644 index d8c46d8e4dc..00000000000 --- a/changelog/fragments/1698259940-Increase-wait-period-between-service-restarts-on-failure-to-15s-on-Windows.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Kind can be one of: -# - breaking-change: a change to previously-documented behavior -# - deprecation: functionality that is being removed in a later release -# - bug-fix: fixes a problem in a previous version -# - enhancement: extends functionality but does not break or fix existing behavior -# - feature: new functionality -# - known-issue: problems that we are aware of in a given version -# - security: impacts on the security of a product or a user’s deployment. -# - upgrade: important information for someone upgrading from a prior version -# - other: does not fit into any of the other categories -kind: bug-fix - -# Change summary; a 80ish characters long description of the change. -summary: Increase wait period between service restarts on failure to 15s on Windows. - -# Long description; in case the summary is not enough to describe the change -# this field accommodate a description without length limits. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -description: This is the same value used by other Elastic windows services like endpoint-security. - -# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. -component: "elastic-agent" - -# PR URL; optional; the PR number that added the changeset. -# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. -# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. -# Please provide it if you are adding a fragment for a different PR. -#pr: https://github.com/owner/repo/1234 - -# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). -# If not present is automatically filled by the tooling with the issue linked to the PR number. -#issue: https://github.com/owner/repo/1234 diff --git a/internal/pkg/agent/application/info/state.go b/internal/pkg/agent/application/info/state.go index 4242be6cb54..1f72e04d4fc 100644 --- a/internal/pkg/agent/application/info/state.go +++ b/internal/pkg/agent/application/info/state.go @@ -9,6 +9,7 @@ import ( "path/filepath" "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" + "github.com/elastic/elastic-agent/pkg/utils" ) // MarkerFileName is the name of the file that's created by @@ -28,11 +29,10 @@ func RunningInstalled() bool { return true } -func CreateInstallMarker(topPath string) error { +func CreateInstallMarker(topPath string, ownership utils.FileOwner) error { markerFilePath := filepath.Join(topPath, MarkerFileName) if _, err := os.Create(markerFilePath); err != nil { return err } - - return nil + return fixInstallMarkerPermissions(markerFilePath, ownership) } diff --git a/internal/pkg/agent/application/info/state_unix.go b/internal/pkg/agent/application/info/state_unix.go new file mode 100644 index 00000000000..23f091aa4bb --- /dev/null +++ b/internal/pkg/agent/application/info/state_unix.go @@ -0,0 +1,22 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build !windows + +package info + +import ( + "fmt" + "os" + + "github.com/elastic/elastic-agent/pkg/utils" +) + +func fixInstallMarkerPermissions(markerFilePath string, ownership utils.FileOwner) error { + err := os.Chown(markerFilePath, ownership.UID, ownership.GID) + if err != nil { + return fmt.Errorf("failed to chown %d:%d %s: %w", ownership.UID, ownership.GID, markerFilePath, err) + } + return nil +} diff --git a/internal/pkg/agent/application/info/state_windows.go b/internal/pkg/agent/application/info/state_windows.go new file mode 100644 index 00000000000..7997c2d0f7d --- /dev/null +++ b/internal/pkg/agent/application/info/state_windows.go @@ -0,0 +1,16 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build windows + +package info + +import ( + "github.com/elastic/elastic-agent/pkg/utils" +) + +func fixInstallMarkerPermissions(markerFilePath string, ownership utils.FileOwner) error { + // TODO(blakerouse): Fix the market permissions on Windows. + return nil +} diff --git a/internal/pkg/agent/application/paths/paths.go b/internal/pkg/agent/application/paths/paths.go index 71089df00ed..af500b46695 100644 --- a/internal/pkg/agent/application/paths/paths.go +++ b/internal/pkg/agent/application/paths/paths.go @@ -17,6 +17,12 @@ const ( // ControlSocketPath is the control socket path used when installed. ControlSocketPath = "unix:///run/elastic-agent.sock" + // ControlSocketUnprivilegedPath is the control socket path used when installed as non-root. + // This must exist inside of a directory in '/run/' because the permissions need to be set + // on that directory during installation time, because once the service is spawned it will not + // have permissions to create the socket in the '/run/' directory. + ControlSocketUnprivilegedPath = "unix:///run/elastic-agent/elastic-agent.sock" + // ShipperSocketPipePattern is the socket path used when installed for a shipper pipe. ShipperSocketPipePattern = "unix:///run/elastic-agent-%s-pipe.sock" diff --git a/internal/pkg/agent/application/paths/paths_darwin.go b/internal/pkg/agent/application/paths/paths_darwin.go index 475f1db5ae2..54c310b8a4d 100644 --- a/internal/pkg/agent/application/paths/paths_darwin.go +++ b/internal/pkg/agent/application/paths/paths_darwin.go @@ -17,6 +17,12 @@ const ( // ControlSocketPath is the control socket path used when installed. ControlSocketPath = "unix:///var/run/elastic-agent.sock" + // ControlSocketUnprivilegedPath is the control socket path used when installed as non-root. + // This must exist inside of a directory in '/var/run/' because the permissions need to be set + // on that directory during installation time, because once the service is spawned it will not + // have permissions to create the socket in the '/var/run/' directory. + ControlSocketUnprivilegedPath = "unix:///var/run/elastic-agent/elastic-agent.sock" + // ShipperSocketPipePattern is the socket path used when installed for a shipper pipe. ShipperSocketPipePattern = "unix:///var/run/elastic-agent-%s-pipe.sock" diff --git a/internal/pkg/agent/application/paths/paths_windows.go b/internal/pkg/agent/application/paths/paths_windows.go index a4e8e0896a5..b54b1ebe898 100644 --- a/internal/pkg/agent/application/paths/paths_windows.go +++ b/internal/pkg/agent/application/paths/paths_windows.go @@ -22,6 +22,9 @@ const ( // ControlSocketPath is the control socket path used when installed. ControlSocketPath = `\\.\pipe\elastic-agent-system` + // ControlSocketUnprivilegedPath is the control socket path used when installed as non-root. + ControlSocketUnprivilegedPath = ControlSocketPath + // ShipperSocketPipePattern is the socket path used when installed for a shipper pipe. ShipperSocketPipePattern = `\\.\pipe\elastic-agent-%s-pipe.sock` diff --git a/internal/pkg/agent/application/upgrade/rollback.go b/internal/pkg/agent/application/upgrade/rollback.go index ae3c568acf1..bea5c8c4f23 100644 --- a/internal/pkg/agent/application/upgrade/rollback.go +++ b/internal/pkg/agent/application/upgrade/rollback.go @@ -12,16 +12,17 @@ import ( "strings" "time" - "github.com/elastic/elastic-agent/pkg/control" - "github.com/elastic/elastic-agent/pkg/control/v2/client" - "github.com/hashicorp/go-multierror" + "google.golang.org/grpc" "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" "github.com/elastic/elastic-agent/internal/pkg/agent/errors" "github.com/elastic/elastic-agent/internal/pkg/agent/install" "github.com/elastic/elastic-agent/internal/pkg/core/backoff" + "github.com/elastic/elastic-agent/pkg/control" + "github.com/elastic/elastic-agent/pkg/control/v2/client" "github.com/elastic/elastic-agent/pkg/core/logger" + "github.com/elastic/elastic-agent/pkg/utils" ) const ( @@ -137,7 +138,9 @@ func InvokeWatcher(log *logger.Logger) error { func restartAgent(ctx context.Context, log *logger.Logger) error { restartViaDaemonFn := func(ctx context.Context) error { c := client.New() - err := c.Connect(ctx) + connectCtx, connectCancel := context.WithTimeout(ctx, 3*time.Second) + defer connectCancel() + err := c.Connect(connectCtx, grpc.WithBlock(), grpc.WithDisableRetry()) if err != nil { return errors.New(err, "failed communicating to running daemon", errors.TypeNetwork, errors.M("socket", control.Address())) } @@ -163,6 +166,7 @@ func restartAgent(ctx context.Context, log *logger.Logger) error { signal := make(chan struct{}) backExp := backoff.NewExpBackoff(signal, restartBackoffInit, restartBackoffMax) + root, _ := utils.HasRoot() // error ignored for restartAttempt := 1; restartAttempt <= maxRestartCount; restartAttempt++ { backExp.Wait() @@ -175,19 +179,21 @@ func restartAgent(ctx context.Context, log *logger.Logger) error { } log.Warnf("Failed to restart agent via control protocol: %s", err.Error()) - // Next, try to restart Agent via the service. - log.Infof("Restarting Agent via service; attempt %d of %d", restartAttempt, maxRestartCount) - err = restartViaServiceFn(ctx) - if err == nil { - break + // Next, try to restart Agent via the service. (only if root) + if root { + log.Infof("Restarting Agent via service; attempt %d of %d", restartAttempt, maxRestartCount) + err = restartViaServiceFn(ctx) + if err == nil { + break + } + log.Warnf("Failed to restart agent via service: %s", err.Error()) } if restartAttempt == maxRestartCount { log.Error("Failed to restart agent after final attempt") return err } - - log.Warnf("Failed to restart agent via service: %s; will try again in %v", err.Error(), backExp.NextWait()) + log.Warnf("Failed to restart agent; will try again in %v", backExp.NextWait()) } close(signal) diff --git a/internal/pkg/agent/application/upgrade/step_unpack.go b/internal/pkg/agent/application/upgrade/step_unpack.go index 45d007e55f4..c418e54b182 100644 --- a/internal/pkg/agent/application/upgrade/step_unpack.go +++ b/internal/pkg/agent/application/upgrade/step_unpack.go @@ -85,11 +85,13 @@ func unzip(log *logger.Logger, archivePath string) (string, error) { if f.FileInfo().IsDir() { log.Debugw("Unpacking directory", "archive", "zip", "file.path", path) - _ = os.MkdirAll(path, f.Mode()) + // remove any world permissions from the directory + _ = os.MkdirAll(path, f.Mode()&0770) } else { log.Debugw("Unpacking file", "archive", "zip", "file.path", path) - _ = os.MkdirAll(filepath.Dir(path), f.Mode()) - f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + // remove any world permissions from the directory/file + _ = os.MkdirAll(filepath.Dir(path), f.Mode()&0770) + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()&0770) if err != nil { return err } @@ -190,11 +192,13 @@ func untar(log *logger.Logger, version string, archivePath string) (string, erro case mode.IsRegular(): log.Debugw("Unpacking file", "archive", "tar", "file.path", abs) // just to be sure, it should already be created by Dir type - if err := os.MkdirAll(filepath.Dir(abs), 0755); err != nil { + // remove any world permissions from the directory + if err := os.MkdirAll(filepath.Dir(abs), mode.Perm()&0770); err != nil { return "", errors.New(err, "TarInstaller: creating directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs)) } - wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()) + // remove any world permissions from the file + wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()&0770) if err != nil { return "", errors.New(err, "TarInstaller: creating file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs)) } @@ -209,7 +213,8 @@ func untar(log *logger.Logger, version string, archivePath string) (string, erro } case mode.IsDir(): log.Debugw("Unpacking directory", "archive", "tar", "file.path", abs) - if err := os.MkdirAll(abs, 0755); err != nil { + // remove any world permissions from the directory + if err := os.MkdirAll(abs, mode.Perm()&0770); err != nil { return "", errors.New(err, "TarInstaller: creating directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs)) } default: diff --git a/internal/pkg/agent/cmd/enroll_cmd.go b/internal/pkg/agent/cmd/enroll_cmd.go index b5992f10188..37540ae5249 100644 --- a/internal/pkg/agent/cmd/enroll_cmd.go +++ b/internal/pkg/agent/cmd/enroll_cmd.go @@ -15,6 +15,8 @@ import ( "strings" "time" + "github.com/elastic/elastic-agent/pkg/utils" + "github.com/elastic/elastic-agent/pkg/control/v2/client" "go.elastic.co/apm" @@ -260,7 +262,7 @@ func (c *enrollCmd) Execute(ctx context.Context, streams *cli.IOStreams) error { } if c.options.FixPermissions { - err = install.FixPermissions(paths.Top()) + err = install.FixPermissions(paths.Top(), utils.CurrentFileOwner()) if err != nil { return errors.New(err, "failed to fix permissions") } @@ -1064,10 +1066,15 @@ func expBackoffWithContext(ctx context.Context, init, max time.Duration) backoff func cleanTags(tags []string) []string { var r []string + // Create a map to store unique elements + seen := make(map[string]bool) for _, str := range tags { tag := strings.TrimSpace(str) if tag != "" { - r = append(r, tag) + if _, ok := seen[tag]; !ok { + seen[tag] = true + r = append(r, tag) + } } } return r diff --git a/internal/pkg/agent/cmd/enroll_cmd_test.go b/internal/pkg/agent/cmd/enroll_cmd_test.go index 189ad7b6563..0b1e7d5d4ee 100644 --- a/internal/pkg/agent/cmd/enroll_cmd_test.go +++ b/internal/pkg/agent/cmd/enroll_cmd_test.go @@ -351,9 +351,9 @@ func TestValidateArgs(t *testing.T) { require.Contains(t, cleanedTags, "production") }) - t.Run("comma separated tags are cleaned", func(t *testing.T) { + t.Run("comma separated tags and duplicated tags are cleaned", func(t *testing.T) { cmd := newEnrollCommandWithArgs([]string{}, streams) - err := cmd.Flags().Set("tag", "windows, production") + err := cmd.Flags().Set("tag", "windows, production, windows") require.NoError(t, err) args := buildEnrollmentFlags(cmd, url, enrolmentToken) require.Contains(t, args, "--tag") @@ -362,6 +362,9 @@ func TestValidateArgs(t *testing.T) { cleanedTags := cleanTags(args) require.Contains(t, cleanedTags, "windows") require.Contains(t, cleanedTags, "production") + // Validate that we remove the duplicates + require.Equal(t, len(args), 10) + require.Equal(t, len(cleanedTags), 7) }) t.Run("valid tag and empty tag", func(t *testing.T) { diff --git a/internal/pkg/agent/cmd/install.go b/internal/pkg/agent/cmd/install.go index a5b425d10ea..2b96dae2d7a 100644 --- a/internal/pkg/agent/cmd/install.go +++ b/internal/pkg/agent/cmd/install.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "github.com/spf13/cobra" @@ -21,7 +22,10 @@ import ( "github.com/elastic/elastic-agent/pkg/utils" ) -const flagInstallBasePath = "base-path" +const ( + flagInstallBasePath = "base-path" + flagInstallUnprivileged = "unprivileged" +) func newInstallCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command { cmd := &cobra.Command{ @@ -43,6 +47,7 @@ would like the Agent to operate. cmd.Flags().BoolP("force", "f", false, "Force overwrite the current installation and do not prompt for confirmation") cmd.Flags().BoolP("non-interactive", "n", false, "Install Elastic Agent in non-interactive mode which will not prompt on missing parameters but fails instead.") cmd.Flags().String(flagInstallBasePath, paths.DefaultBasePath, "The path where the Elastic Agent will be installed. It must be an absolute path.") + cmd.Flags().Bool(flagInstallUnprivileged, false, "Installed Elastic Agent will create an 'elastic-agent' user and run as that user.") addEnrollFlags(cmd) return cmd @@ -67,6 +72,12 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command) error { return fmt.Errorf("unable to perform install command, not executed with %s permissions", utils.PermissionUser) } + // only support Linux at the moment + unprivileged, _ := cmd.Flags().GetBool(flagInstallUnprivileged) + if unprivileged && runtime.GOOS != "linux" { + return fmt.Errorf("unable to perform install command, unprivileged is currently only supported on Linux") + } + topPath := paths.InstallPath(basePath) status, reason := install.Status(topPath) @@ -175,9 +186,10 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command) error { progBar := install.CreateAndStartNewSpinner(streams.Out, "Installing Elastic Agent...") + var ownership utils.FileOwner cfgFile := paths.ConfigFile() if status != install.PackageInstall { - err = install.Install(cfgFile, topPath, progBar, streams) + ownership, err = install.Install(cfgFile, topPath, unprivileged, progBar, streams) if err != nil { return fmt.Errorf("error installing package: %w", err) } @@ -225,6 +237,10 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command) error { enrollCmd.Stdin = os.Stdin enrollCmd.Stdout = os.Stdout enrollCmd.Stderr = os.Stderr + err = enrollCmdExtras(enrollCmd, ownership) + if err != nil { + return err + } progBar.Describe("Enrolling Elastic Agent with Fleet") err = enrollCmd.Start() @@ -243,7 +259,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command) error { progBar.Describe("Enroll Completed") } - if err := info.CreateInstallMarker(topPath); err != nil { + if err := info.CreateInstallMarker(topPath, ownership); err != nil { return fmt.Errorf("failed to create install marker: %w", err) } diff --git a/internal/pkg/agent/cmd/install_enroll.go b/internal/pkg/agent/cmd/install_enroll.go new file mode 100644 index 00000000000..8f7677ebed6 --- /dev/null +++ b/internal/pkg/agent/cmd/install_enroll.go @@ -0,0 +1,24 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build !windows + +package cmd + +import ( + "os/exec" + "syscall" + + "github.com/elastic/elastic-agent/pkg/utils" +) + +func enrollCmdExtras(cmd *exec.Cmd, ownership utils.FileOwner) error { + cmd.SysProcAttr = &syscall.SysProcAttr{ + Credential: &syscall.Credential{ + Uid: uint32(ownership.UID), + Gid: uint32(ownership.GID), + }, + } + return nil +} diff --git a/internal/pkg/agent/cmd/install_enroll_windows.go b/internal/pkg/agent/cmd/install_enroll_windows.go new file mode 100644 index 00000000000..8178950fc0f --- /dev/null +++ b/internal/pkg/agent/cmd/install_enroll_windows.go @@ -0,0 +1,18 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build windows + +package cmd + +import ( + "os/exec" + + "github.com/elastic/elastic-agent/pkg/utils" +) + +func enrollCmdExtras(cmd *exec.Cmd, ownership utils.FileOwner) error { + // TODO: Add ability to call enroll as non-Administrator on Windows. + return nil +} diff --git a/internal/pkg/agent/cmd/run.go b/internal/pkg/agent/cmd/run.go index 4a12bdc8540..a1162889ba3 100644 --- a/internal/pkg/agent/cmd/run.go +++ b/internal/pkg/agent/cmd/run.go @@ -49,6 +49,7 @@ import ( "github.com/elastic/elastic-agent/pkg/component" "github.com/elastic/elastic-agent/pkg/control/v2/server" "github.com/elastic/elastic-agent/pkg/core/logger" + "github.com/elastic/elastic-agent/pkg/utils" "github.com/elastic/elastic-agent/version" ) @@ -612,7 +613,7 @@ func ensureInstallMarkerPresent() error { // Otherwise, we're being upgraded from a version of an installed Agent // that didn't use an installation marker file (that is, before v8.8.0). // So create the file now. - if err := info.CreateInstallMarker(paths.Top()); err != nil { + if err := info.CreateInstallMarker(paths.Top(), utils.CurrentFileOwner()); err != nil { return fmt.Errorf("unable to create installation marker file during upgrade: %w", err) } diff --git a/internal/pkg/agent/install/install.go b/internal/pkg/agent/install/install.go index 7c3ee14b038..4152479fb11 100644 --- a/internal/pkg/agent/install/install.go +++ b/internal/pkg/agent/install/install.go @@ -19,17 +19,21 @@ import ( "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" "github.com/elastic/elastic-agent/internal/pkg/agent/errors" "github.com/elastic/elastic-agent/internal/pkg/cli" + "github.com/elastic/elastic-agent/pkg/utils" ) const ( darwin = "darwin" + + elasticUsername = "elastic-agent" + elasticGroupName = "elastic-agent" ) // Install installs Elastic Agent persistently on the system including creating and starting its service. -func Install(cfgFile, topPath string, pt *progressbar.ProgressBar, streams *cli.IOStreams) error { +func Install(cfgFile, topPath string, unprivileged bool, pt *progressbar.ProgressBar, streams *cli.IOStreams) (utils.FileOwner, error) { dir, err := findDirectory() if err != nil { - return errors.New(err, "failed to discover the source directory for installation", errors.TypeFilesystem) + return utils.FileOwner{}, errors.New(err, "failed to discover the source directory for installation", errors.TypeFilesystem) } // We only uninstall Agent if it is currently installed. @@ -44,7 +48,7 @@ func Install(cfgFile, topPath string, pt *progressbar.ProgressBar, streams *cli. err = Uninstall(cfgFile, topPath, "", pt) if err != nil { pt.Describe("Failed to uninstall current Elastic Agent") - return errors.New( + return utils.FileOwner{}, errors.New( err, fmt.Sprintf("failed to uninstall Agent at (%s)", filepath.Dir(topPath)), errors.M("directory", filepath.Dir(topPath))) @@ -52,10 +56,53 @@ func Install(cfgFile, topPath string, pt *progressbar.ProgressBar, streams *cli. pt.Describe("Successfully uninstalled current Elastic Agent") } + var ownership utils.FileOwner + username := "" + groupName := "" + if unprivileged { + username = elasticUsername + groupName = elasticGroupName + + // ensure required group + ownership.GID, err = FindGID(groupName) + if err != nil && !errors.Is(err, ErrGroupNotFound) { + return utils.FileOwner{}, fmt.Errorf("failed finding group %s: %w", groupName, err) + } + if errors.Is(err, ErrGroupNotFound) { + pt.Describe(fmt.Sprintf("Creating group %s", groupName)) + ownership.GID, err = CreateGroup(groupName) + if err != nil { + pt.Describe(fmt.Sprintf("Failed to create group %s", groupName)) + return utils.FileOwner{}, fmt.Errorf("failed to create group %s: %w", groupName, err) + } + pt.Describe(fmt.Sprintf("Successfully created group %s", groupName)) + } + + // ensure required user + ownership.UID, err = FindUID(username) + if err != nil && !errors.Is(err, ErrUserNotFound) { + return utils.FileOwner{}, fmt.Errorf("failed finding username %s: %w", username, err) + } + if errors.Is(err, ErrUserNotFound) { + pt.Describe(fmt.Sprintf("Creating user %s", username)) + ownership.UID, err = CreateUser(username, ownership.GID) + if err != nil { + pt.Describe(fmt.Sprintf("Failed to create user %s", username)) + return utils.FileOwner{}, fmt.Errorf("failed to create user %s: %w", username, err) + } + err = AddUserToGroup(username, groupName) + if err != nil { + pt.Describe(fmt.Sprintf("Failed to add user %s to group %s", username, groupName)) + return utils.FileOwner{}, fmt.Errorf("failed to add user %s to group %s: %w", username, groupName, err) + } + pt.Describe(fmt.Sprintf("Successfully created user %s", username)) + } + } + // ensure parent directory exists err = os.MkdirAll(filepath.Dir(topPath), 0755) if err != nil { - return errors.New( + return utils.FileOwner{}, errors.New( err, fmt.Sprintf("failed to create installation parent directory (%s)", filepath.Dir(topPath)), errors.M("directory", filepath.Dir(topPath))) @@ -84,7 +131,7 @@ func Install(cfgFile, topPath string, pt *progressbar.ProgressBar, streams *cli. }) if err != nil { pt.Describe("Error copying files") - return errors.New( + return utils.FileOwner{}, errors.New( err, fmt.Sprintf("failed to copy source directory (%s) to destination (%s)", dir, topPath), errors.M("source", dir), errors.M("destination", topPath), @@ -97,7 +144,7 @@ func Install(cfgFile, topPath string, pt *progressbar.ProgressBar, streams *cli. pathDir := filepath.Dir(paths.ShellWrapperPath) err = os.MkdirAll(pathDir, 0755) if err != nil { - return errors.New( + return utils.FileOwner{}, errors.New( err, fmt.Sprintf("failed to create directory (%s) for shell wrapper (%s)", pathDir, paths.ShellWrapperPath), errors.M("directory", pathDir)) @@ -110,7 +157,7 @@ func Install(cfgFile, topPath string, pt *progressbar.ProgressBar, streams *cli. // Check if previous shell wrapper or symlink exists and remove it so it can be overwritten if _, err := os.Lstat(paths.ShellWrapperPath); err == nil { if err := os.Remove(paths.ShellWrapperPath); err != nil { - return errors.New( + return utils.FileOwner{}, errors.New( err, fmt.Sprintf("failed to remove (%s)", paths.ShellWrapperPath), errors.M("destination", paths.ShellWrapperPath)) @@ -118,7 +165,7 @@ func Install(cfgFile, topPath string, pt *progressbar.ProgressBar, streams *cli. } err = os.Symlink(filepath.Join(topPath, paths.BinaryName), paths.ShellWrapperPath) if err != nil { - return errors.New( + return utils.FileOwner{}, errors.New( err, fmt.Sprintf("failed to create elastic-agent symlink (%s)", paths.ShellWrapperPath), errors.M("destination", paths.ShellWrapperPath)) @@ -130,7 +177,7 @@ func Install(cfgFile, topPath string, pt *progressbar.ProgressBar, streams *cli. shellWrapper := strings.Replace(paths.ShellWrapper, "%s", topPath, -1) err = os.WriteFile(paths.ShellWrapperPath, []byte(shellWrapper), 0755) if err != nil { - return errors.New( + return utils.FileOwner{}, errors.New( err, fmt.Sprintf("failed to write shell wrapper (%s)", paths.ShellWrapperPath), errors.M("destination", paths.ShellWrapperPath)) @@ -141,42 +188,57 @@ func Install(cfgFile, topPath string, pt *progressbar.ProgressBar, streams *cli. // post install (per platform) err = postInstall(topPath) if err != nil { - return fmt.Errorf("error running post-install steps: %w", err) + return ownership, fmt.Errorf("error running post-install steps: %w", err) } // fix permissions - err = FixPermissions(topPath) + err = FixPermissions(topPath, ownership) if err != nil { - return errors.New( - err, - "failed to perform permission changes", - errors.M("destination", topPath)) + return ownership, fmt.Errorf("failed to perform permission changes on path %s: %w", topPath, err) + } + if paths.ShellWrapperPath != "" { + err = FixPermissions(paths.ShellWrapperPath, ownership) + if err != nil { + return ownership, fmt.Errorf("failed to perform permission changes on path %s: %w", paths.ShellWrapperPath, err) + } + } + + // create socket path when installing as non-root + // now is the only time to do it while root is available (without doing this it will not be possible + // for the service to create the control socket) + // windows: uses npipe and doesn't need a directory created + if unprivileged { + err = createSocketDir(ownership) + if err != nil { + return ownership, fmt.Errorf("failed to create socket directory: %w", err) + } } // install service pt.Describe("Installing service") - svc, err := newService(topPath) + svc, err := newService(topPath, withUserGroup(username, groupName)) if err != nil { pt.Describe("Failed to install service") - return fmt.Errorf("error installing new service: %w", err) + return ownership, fmt.Errorf("error installing new service: %w", err) } err = svc.Install() if err != nil { pt.Describe("Failed to install service") - return errors.New( + return ownership, errors.New( err, fmt.Sprintf("failed to install service (%s)", paths.ServiceName), errors.M("service", paths.ServiceName)) } pt.Describe("Installed service") - return nil + return ownership, nil } // StartService starts the installed service. // // This should only be called after Install is successful. func StartService(topPath string) error { + // only starting the service, so no need to set the username and group to any value svc, err := newService(topPath) if err != nil { return fmt.Errorf("error creating new service handler: %w", err) @@ -193,6 +255,7 @@ func StartService(topPath string) error { // StopService stops the installed service. func StopService(topPath string) error { + // only stopping the service, so no need to set the username and group to any value svc, err := newService(topPath) if err != nil { return fmt.Errorf("error creating new service handler: %w", err) @@ -209,6 +272,7 @@ func StopService(topPath string) error { // RestartService restarts the installed service. func RestartService(topPath string) error { + // only restarting the service, so no need to set the username and group to any value svc, err := newService(topPath) if err != nil { return fmt.Errorf("error creating new service handler: %w", err) @@ -232,11 +296,6 @@ func StatusService(topPath string) (service.Status, error) { return svc.Status() } -// FixPermissions fixes the permissions on the installed system. -func FixPermissions(topPath string) error { - return fixPermissions(topPath) -} - // findDirectory returns the directory to copy into the installation location. // // This also verifies that the discovered directory is a valid directory for installation. diff --git a/internal/pkg/agent/install/install_unix.go b/internal/pkg/agent/install/install_unix.go index b6c7a100b4b..9840dd90ead 100644 --- a/internal/pkg/agent/install/install_unix.go +++ b/internal/pkg/agent/install/install_unix.go @@ -6,8 +6,38 @@ package install +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" + "github.com/elastic/elastic-agent/pkg/utils" +) + // postInstall performs post installation for unix-based systems. func postInstall(topPath string) error { // do nothing return nil } + +// createSocketDir creates the socket directory. +func createSocketDir(ownership utils.FileOwner) error { + path := filepath.Dir(strings.TrimPrefix(paths.ControlSocketUnprivilegedPath, "unix://")) + err := os.MkdirAll(path, 0770) + if err != nil { + return fmt.Errorf("failed to create path %s: %w", path, err) + } + err = os.Chown(path, ownership.UID, ownership.GID) + if err != nil { + return fmt.Errorf("failed to chown path %s: %w", path, err) + } + // possible that the directory existed, still set the + // permission again to ensure that they are correct + err = os.Chmod(path, 0770) + if err != nil { + return fmt.Errorf("failed to chmod path %s: %w", path, err) + } + return nil +} diff --git a/internal/pkg/agent/install/install_windows.go b/internal/pkg/agent/install/install_windows.go index be8d2fb44e5..07b43591906 100644 --- a/internal/pkg/agent/install/install_windows.go +++ b/internal/pkg/agent/install/install_windows.go @@ -13,6 +13,7 @@ import ( "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" "github.com/elastic/elastic-agent/internal/pkg/release" + "github.com/elastic/elastic-agent/pkg/utils" "github.com/elastic/elastic-agent/version" ) @@ -44,3 +45,9 @@ func postInstall(topPath string) error { return nil } + +// createSocketDir creates the socket directory. +func createSocketDir(ownership utils.FileOwner) error { + // doesn't do anything on windows, no directory is needed. + return nil +} diff --git a/internal/pkg/agent/install/installed.go b/internal/pkg/agent/install/installed.go index c483dda7bb8..c415d4e1abe 100644 --- a/internal/pkg/agent/install/installed.go +++ b/internal/pkg/agent/install/installed.go @@ -55,6 +55,7 @@ func Status(topPath string) (StatusType, string) { // checkService only checks the status of the service. func checkService(topPath string) (StatusType, string) { + // only checking the service, so no need to set the username and group to any value svc, err := newService(topPath) if err != nil { return NotInstalled, "unable to check service status" diff --git a/internal/pkg/agent/install/perms_unix.go b/internal/pkg/agent/install/perms_unix.go index e84dcd5039c..578c2af0a30 100644 --- a/internal/pkg/agent/install/perms_unix.go +++ b/internal/pkg/agent/install/perms_unix.go @@ -11,18 +11,17 @@ import ( "io/fs" "os" "path/filepath" -) -// fixPermissions fixes the permissions so only root:root is the owner and no world read-able permissions -func fixPermissions(topPath string) error { - return recursiveRootPermissions(topPath) -} + "github.com/elastic/elastic-agent/pkg/utils" +) -func recursiveRootPermissions(path string) error { - return filepath.Walk(path, func(name string, info fs.FileInfo, err error) error { +// FixPermissions fixes the permissions so only root:root is the owner and no world read-able permissions +func FixPermissions(topPath string, ownership utils.FileOwner) error { + return filepath.Walk(topPath, func(name string, info fs.FileInfo, err error) error { if err == nil { - // all files should be owned by root:root - err = os.Chown(name, 0, 0) + // all files should be owned by uid:gid + // uses `os.Lchown` so the symlink is updated to have the permissions + err = os.Lchown(name, ownership.UID, ownership.GID) if err != nil { return err } diff --git a/internal/pkg/agent/install/perms_windows.go b/internal/pkg/agent/install/perms_windows.go index 45b0d073dc4..1281479f695 100644 --- a/internal/pkg/agent/install/perms_windows.go +++ b/internal/pkg/agent/install/perms_windows.go @@ -13,19 +13,17 @@ import ( "github.com/hectane/go-acl" "golang.org/x/sys/windows" -) -// fixPermissions fixes the permissions so only SYSTEM and Administrators have access to the files in the install path -func fixPermissions(topPath string) error { - return recursiveSystemAdminPermissions(topPath) -} + "github.com/elastic/elastic-agent/pkg/utils" +) -func recursiveSystemAdminPermissions(path string) error { - return filepath.Walk(path, func(name string, info fs.FileInfo, err error) error { +// FixPermissions fixes the permissions so only SYSTEM and Administrators have access to the files in the install path +func FixPermissions(topPath string, ownership utils.FileOwner) error { + return filepath.Walk(topPath, func(name string, info fs.FileInfo, err error) error { if err == nil { // first level doesn't inherit inherit := true - if path == name { + if topPath == name { inherit = false } err = systemAdministratorsOnly(name, inherit) @@ -38,11 +36,11 @@ func recursiveSystemAdminPermissions(path string) error { func systemAdministratorsOnly(path string, inherit bool) error { // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems - systemSID, err := windows.StringToSid("S-1-5-18") + systemSID, err := windows.StringToSid(utils.SystemSID) if err != nil { return err } - administratorsSID, err := windows.StringToSid("S-1-5-32-544") + administratorsSID, err := windows.StringToSid(utils.AdministratorSID) if err != nil { return err } diff --git a/internal/pkg/agent/install/svc.go b/internal/pkg/agent/install/svc.go index 2fbc0c61803..080fd81fd0c 100644 --- a/internal/pkg/agent/install/svc.go +++ b/internal/pkg/agent/install/svc.go @@ -5,6 +5,7 @@ package install import ( + "fmt" "path/filepath" "runtime" @@ -36,14 +37,37 @@ func ExecutablePath(topPath string) string { return exec } -func newService(topPath string) (service.Service, error) { +type serviceOpts struct { + Username string + Group string +} + +type serviceOpt func(opts *serviceOpts) + +func withUserGroup(username string, group string) serviceOpt { + return func(opts *serviceOpts) { + opts.Username = username + opts.Group = group + } +} + +func newService(topPath string, opt ...serviceOpt) (service.Service, error) { + var opts serviceOpts + for _, o := range opt { + o(&opts) + } + cfg := &service.Config{ Name: paths.ServiceName, DisplayName: ServiceDisplayName, Description: ServiceDescription, Executable: ExecutablePath(topPath), WorkingDirectory: topPath, + UserName: opts.Username, Option: map[string]interface{}{ + // GroupName + "GroupName": opts.Group, + // Linux (systemd) always restart on failure "Restart": "always", @@ -74,6 +98,11 @@ func newService(topPath string) (service.Service, error) { // of the prebuilt template with added ExitTimeOut option cfg.Option["LaunchdConfig"] = darwinLaunchdConfig cfg.Option["ExitTimeOut"] = darwinServiceExitTimeout + + // Set the stdout and stderr logs to be inside the installation directory, ensures that the + // executing user for the service can write to the directory for the logs. + cfg.Option["StandardOutPath"] = filepath.Join(topPath, fmt.Sprintf("%s.out.log", paths.ServiceName)) + cfg.Option["StandardErrorPath"] = filepath.Join(topPath, fmt.Sprintf("%s.err.log", paths.ServiceName)) } return service.New(nil, cfg) @@ -97,6 +126,10 @@ const darwinLaunchdConfig = ` {{if .UserName}}UserName {{html .UserName}}{{end}} + {{if .Config.Option.GroupName -}} + GroupName + {{html .Config.Option.GroupName}} + {{- end}} {{if .ChRoot}}RootDirectory {{html .ChRoot}}{{end}} {{if .Config.Option.ExitTimeOut}}ExitTimeOut @@ -113,9 +146,9 @@ const darwinLaunchdConfig = ` StandardOutPath - /usr/local/var/log/{{html .Name}}.out.log + {{html .Config.Option.StandardOutPath}} StandardErrorPath - /usr/local/var/log/{{html .Name}}.err.log + {{html .Config.Option.StandardErrorPath}} @@ -136,6 +169,9 @@ ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}} {{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}} {{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}} {{if .UserName}}User={{.UserName}}{{end}} +{{if .Config.Option.GroupName -}} +Group={{.Config.Option.GroupName}} +{{- end}} {{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}} {{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}} {{if and .LogOutput .HasOutputFileSupport -}} diff --git a/internal/pkg/agent/install/uninstall.go b/internal/pkg/agent/install/uninstall.go index 4a453d3953c..8f2234d0f4d 100644 --- a/internal/pkg/agent/install/uninstall.go +++ b/internal/pkg/agent/install/uninstall.go @@ -35,6 +35,7 @@ import ( // Uninstall uninstalls persistently Elastic Agent on the system. func Uninstall(cfgFile, topPath, uninstallToken string, pt *progressbar.ProgressBar) error { // uninstall the current service + // not creating the service, so no need to set the username and group to any value svc, err := newService(topPath) if err != nil { return fmt.Errorf("error creating new service handler: %w", err) diff --git a/internal/pkg/agent/install/user.go b/internal/pkg/agent/install/user.go new file mode 100644 index 00000000000..96e924c9878 --- /dev/null +++ b/internal/pkg/agent/install/user.go @@ -0,0 +1,14 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package install + +import "errors" + +var ( + // ErrGroupNotFound returned when group is not found. + ErrGroupNotFound = errors.New("group not found") + // ErrUserNotFound returned when user is not found. + ErrUserNotFound = errors.New("user not found") +) diff --git a/internal/pkg/agent/install/user_darwin.go b/internal/pkg/agent/install/user_darwin.go new file mode 100644 index 00000000000..b6fa145c6e3 --- /dev/null +++ b/internal/pkg/agent/install/user_darwin.go @@ -0,0 +1,179 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build darwin + +package install + +import ( + "bufio" + "bytes" + "fmt" + "os/exec" + "sort" + "strconv" + "strings" +) + +// FindGID returns the group's GID on the machine. +func FindGID(name string) (int, error) { + records, err := dsclList("/Groups", "PrimaryGroupID") + if err != nil { + return -1, fmt.Errorf("failed listing: %w", err) + } + for _, record := range records { + if record[0] == name { + val, err := strconv.Atoi(record[1]) + if err != nil { + return -1, fmt.Errorf("failed to convert %s to int: %w", record[1], err) + } + return val, nil + } + } + return -1, ErrGroupNotFound +} + +// CreateGroup creates a group on the machine. +func CreateGroup(name string) (int, error) { + // find the next available ID + nextId, err := dsclNextID("/Groups", "PrimaryGroupID") + if err != nil { + return -1, fmt.Errorf("failed getting next gid: %w", err) + } + path := fmt.Sprintf("/Groups/%s", name) + + // create the group entry + err = dsclExec("-create", path, "PrimaryGroupID", strconv.Itoa(nextId)) + if err != nil { + return -1, err + } + + return nextId, nil +} + +// FindUID returns the user's UID on the machine. +func FindUID(name string) (int, error) { + records, err := dsclList("/Users", "UniqueID") + if err != nil { + return -1, fmt.Errorf("failed listing: %w", err) + } + for _, record := range records { + if record[0] == name { + val, err := strconv.Atoi(record[1]) + if err != nil { + return -1, fmt.Errorf("failed to convert %s to int: %w", record[1], err) + } + return val, nil + } + } + return -1, ErrUserNotFound +} + +// CreateUser creates a user on the machine. +func CreateUser(name string, gid int) (int, error) { + // find the next available ID + nextId, err := dsclNextID("/Users", "UniqueID") + if err != nil { + return -1, fmt.Errorf("failed getting next uid: %w", err) + } + path := fmt.Sprintf("/Users/%s", name) + + // create the user entry + err = dsclExec("-create", path, "UniqueID", strconv.Itoa(nextId)) + if err != nil { + return -1, err + } + + // set primary group to gid + err = dsclExec("-create", path, "PrimaryGroupID", strconv.Itoa(gid)) + if err != nil { + return -1, err + } + + // set home directory to empty + err = dsclExec("-create", path, "NFSHomeDirectory", "/var/empty") + if err != nil { + return -1, err + } + + // set to no shell + err = dsclExec("-create", path, "UserShell", "/usr/bin/false") + if err != nil { + return -1, err + } + + // set to no password (aka. cannot authenticate) + err = dsclExec("-create", path, "Password", "*") + if err != nil { + return -1, err + } + + return nextId, nil +} + +// AddUserToGroup adds a user to a group. +func AddUserToGroup(username string, groupName string) error { + // #nosec G204 -- user cannot set the groupName or username (hard coded in caller) + cmd := exec.Command("dscl", ".", "-append", fmt.Sprintf("/Groups/%s", groupName), "GroupMembership", username) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("dscl . -append failed: %w (output: %s)", err, output) + } + return nil +} + +func dsclNextID(path, field string) (int, error) { + records, err := dsclList(path, field) + if err != nil { + return -1, fmt.Errorf("failed listing: %w", err) + } + var ids []int + for _, record := range records { + id, err := strconv.Atoi(record[1]) + if err != nil { + return -1, fmt.Errorf("failed atoi for %s: %w", record[1], err) + } + ids = append(ids, id) + } + // largest id first + sort.Slice(ids, func(i, j int) bool { + return ids[j] < ids[i] + }) + if len(ids) == 0 { + // never going to happen, just be defensive + return 1, nil + } + return ids[0] + 1, nil +} + +func dsclList(path, field string) ([][]string, error) { + cmd := exec.Command("dscl", ".", "-list", path, field) + output, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("dscl . -list failed: %w", err) + } + var records [][]string + scanner := bufio.NewScanner(bytes.NewReader(output)) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) != 2 { + return nil, fmt.Errorf("got more than 2 fields reading line %q", line) + } + records = append(records, fields) + } + return records, nil +} + +func dsclExec(args ...string) error { + args = append([]string{"."}, args...) + cmd := exec.Command("dscl", args...) + output, err := cmd.CombinedOutput() + if err != nil { + command := fmt.Sprintf("dscl %s", strings.Join(args, " ")) + return fmt.Errorf("%s failed: %w (output: %s)", command, err, output) + } + return nil +} diff --git a/internal/pkg/agent/install/user_linux.go b/internal/pkg/agent/install/user_linux.go new file mode 100644 index 00000000000..0bb4ddee0d4 --- /dev/null +++ b/internal/pkg/agent/install/user_linux.go @@ -0,0 +1,94 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build linux + +package install + +import ( + "errors" + "fmt" + "os/exec" + "strconv" + "strings" +) + +// FindGID returns the group's GID on the machine. +func FindGID(name string) (int, error) { + id, err := getentGetID("group", name) + if e := (&exec.ExitError{}); errors.As(err, &e) { + if e.ExitCode() == 2 { + // exit code 2 is the key doesn't exist in the database + return -1, ErrGroupNotFound + } + } + return id, err +} + +// CreateGroup creates a group on the machine. +func CreateGroup(name string) (int, error) { + cmd := exec.Command("groupadd", "-f", name) + output, err := cmd.CombinedOutput() + if err != nil { + return -1, fmt.Errorf("groupadd -f %s failed: %w (output: %s)", name, err, output) + } + return FindGID(name) +} + +// FindUID returns the user's UID on the machine. +func FindUID(name string) (int, error) { + id, err := getentGetID("passwd", name) + if e := (&exec.ExitError{}); errors.As(err, &e) { + if e.ExitCode() == 2 { + // exit code 2 is the key doesn't exist in the database + return -1, ErrUserNotFound + } + } + return id, err +} + +// CreateUser creates a user on the machine. +func CreateUser(name string, gid int) (int, error) { + args := []string{ + "--gid", strconv.Itoa(gid), + "--system", + "--no-user-group", + "--shell", "/usr/bin/false", + name, + } + cmd := exec.Command("useradd", args...) + output, err := cmd.CombinedOutput() + if err != nil { + command := fmt.Sprintf("useradd %s", strings.Join(args, " ")) + return -1, fmt.Errorf("%s failed: %w (output: %s)", command, err, output) + } + return FindUID(name) +} + +// AddUserToGroup adds a user to a group. +func AddUserToGroup(username string, groupName string) error { + cmd := exec.Command("usermod", "-a", "-G", groupName, username) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("usermod -a -G %s %s failed: %w (output: %s)", groupName, username, err, output) + } + return nil +} + +func getentGetID(database string, key string) (int, error) { + cmd := exec.Command("getent", database, key) + output, err := cmd.Output() + if err != nil { + return -1, fmt.Errorf("getent %s %s failed: %w (output: %s)", database, key, err, output) + } + split := strings.Split(string(output), ":") + if len(split) < 3 { + return -1, fmt.Errorf("unexpected format: %s", output) + } + val, err := strconv.Atoi(split[2]) + if err != nil { + return -1, fmt.Errorf("failed to convert %s to int: %w", split[2], err) + } + return val, nil +} diff --git a/internal/pkg/agent/install/user_windows.go b/internal/pkg/agent/install/user_windows.go new file mode 100644 index 00000000000..0c68c74222f --- /dev/null +++ b/internal/pkg/agent/install/user_windows.go @@ -0,0 +1,34 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build windows + +package install + +import "errors" + +// FindGID returns the group's GID on the machine. +func FindGID(name string) (string, error) { + return "", errors.New("not implemented") +} + +// CreateGroup creates a group on the machine. +func CreateGroup(name string) (string, error) { + return "", errors.New("not implemented") +} + +// FindUID returns the user's UID on the machine. +func FindUID(name string) (string, error) { + return "", errors.New("not implemented") +} + +// CreateUser creates a user on the machine. +func CreateUser(name string, gid string) (string, error) { + return "", errors.New("not implemented") +} + +// AddUserToGroup adds a user to a group. +func AddUserToGroup(username string, groupName string) error { + return errors.New("not implemented") +} diff --git a/internal/pkg/agent/vault/vault_windows.go b/internal/pkg/agent/vault/vault_windows.go index afd484d351a..71f227aa6c0 100644 --- a/internal/pkg/agent/vault/vault_windows.go +++ b/internal/pkg/agent/vault/vault_windows.go @@ -12,6 +12,8 @@ import ( "github.com/billgraziano/dpapi" "github.com/hectane/go-acl" "golang.org/x/sys/windows" + + "github.com/elastic/elastic-agent/pkg/utils" ) func (v *Vault) encrypt(data []byte) ([]byte, error) { @@ -28,11 +30,11 @@ func tightenPermissions(path string) error { func systemAdministratorsOnly(path string, inherit bool) error { // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems - systemSID, err := windows.StringToSid("S-1-5-18") + systemSID, err := windows.StringToSid(utils.SystemSID) if err != nil { return err } - administratorsSID, err := windows.StringToSid("S-1-5-32-544") + administratorsSID, err := windows.StringToSid(utils.AdministratorSID) if err != nil { return err } diff --git a/pkg/control/addr.go b/pkg/control/addr.go index d01cf074df9..916b771097d 100644 --- a/pkg/control/addr.go +++ b/pkg/control/addr.go @@ -13,13 +13,18 @@ import ( "github.com/elastic/elastic-agent/internal/pkg/agent/application/info" "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" + "github.com/elastic/elastic-agent/pkg/utils" ) // Address returns the address to connect to Elastic Agent daemon. func Address() string { // when installed the control address is fixed if info.RunningInstalled() { - return paths.ControlSocketPath + root, _ := utils.HasRoot() // error is ignored + if root { + return paths.ControlSocketPath + } + return paths.ControlSocketUnprivilegedPath } // unix socket path must be less than 104 characters diff --git a/pkg/control/v2/server/listener.go b/pkg/control/v2/server/listener.go index 54852956280..d8b2982f17c 100644 --- a/pkg/control/v2/server/listener.go +++ b/pkg/control/v2/server/listener.go @@ -13,10 +13,10 @@ import ( "path/filepath" "strings" - "github.com/elastic/elastic-agent/pkg/control" - "github.com/elastic/elastic-agent/internal/pkg/agent/errors" + "github.com/elastic/elastic-agent/pkg/control" "github.com/elastic/elastic-agent/pkg/core/logger" + "github.com/elastic/elastic-agent/pkg/utils" ) func createListener(log *logger.Logger) (net.Listener, error) { @@ -26,7 +26,7 @@ func createListener(log *logger.Logger) (net.Listener, error) { } dir := filepath.Dir(path) if _, err := os.Stat(dir); os.IsNotExist(err) { - err = os.MkdirAll(dir, 0755) + err = os.MkdirAll(dir, 0775) if err != nil { return nil, err } @@ -35,7 +35,13 @@ func createListener(log *logger.Logger) (net.Listener, error) { if err != nil { return nil, err } - err = os.Chmod(path, 0700) + mode := os.FileMode(0700) + root, _ := utils.HasRoot() // error ignored + if !root { + // allow group access when not running as root + mode = os.FileMode(0770) + } + err = os.Chmod(path, mode) if err != nil { // failed to set permissions (close listener) lis.Close() diff --git a/pkg/control/v2/server/listener_windows.go b/pkg/control/v2/server/listener_windows.go index 27e530a4c6a..6ce0898192a 100644 --- a/pkg/control/v2/server/listener_windows.go +++ b/pkg/control/v2/server/listener_windows.go @@ -7,21 +7,16 @@ package server import ( + "fmt" "net" "os/user" "strings" - "github.com/elastic/elastic-agent/pkg/control" - - "github.com/pkg/errors" - "github.com/elastic/elastic-agent-libs/api/npipe" - "github.com/elastic/elastic-agent/pkg/core/logger" -) -const ( - NTAUTHORITY_SYSTEM = "S-1-5-18" - ADMINISTRATORS_GROUP = "S-1-5-32-544" + "github.com/elastic/elastic-agent/pkg/control" + "github.com/elastic/elastic-agent/pkg/core/logger" + "github.com/elastic/elastic-agent/pkg/utils" ) // createListener creates a named pipe listener on Windows @@ -40,7 +35,7 @@ func cleanupListener(_ *logger.Logger) { func securityDescriptor(log *logger.Logger) (string, error) { u, err := user.Current() if err != nil { - return "", errors.Wrap(err, "failed to get current user") + return "", fmt.Errorf("failed to get current user: %w", err) } // Named pipe security and access rights. // We create the pipe and the specific users should only be able to write to it. @@ -56,7 +51,7 @@ func securityDescriptor(log *logger.Logger) (string, error) { // running as SYSTEM, include Administrators group so Administrators can talk over // the named pipe to the running Elastic Agent system process // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems - descriptor += "(A;;GA;;;" + ADMINISTRATORS_GROUP + ")" + descriptor += "(A;;GA;;;" + utils.AdministratorSID + ")" } return descriptor, nil } @@ -72,7 +67,7 @@ func isWindowsAdmin(u *user.User) (bool, error) { groups, err := u.GroupIds() if err != nil { - return false, errors.Wrap(err, "failed to get current user groups") + return false, fmt.Errorf("failed to get current user groups: %w", err) } for _, groupSid := range groups { @@ -85,5 +80,5 @@ func isWindowsAdmin(u *user.User) (bool, error) { } func equalsSystemGroup(s string) bool { - return strings.EqualFold(s, NTAUTHORITY_SYSTEM) || strings.EqualFold(s, ADMINISTRATORS_GROUP) + return strings.EqualFold(s, utils.SystemSID) || strings.EqualFold(s, utils.AdministratorSID) } diff --git a/pkg/core/logger/logger.go b/pkg/core/logger/logger.go index e82bfd323b0..b7598f9ef23 100644 --- a/pkg/core/logger/logger.go +++ b/pkg/core/logger/logger.go @@ -21,6 +21,7 @@ import ( "github.com/elastic/elastic-agent-libs/logp/configure" "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" "github.com/elastic/elastic-agent/internal/pkg/agent/errors" + "github.com/elastic/elastic-agent/pkg/utils" ) const agentName = "elastic-agent" @@ -136,6 +137,12 @@ func DefaultLoggingConfig() *Config { cfg.Files.Path = paths.Logs() cfg.Files.Name = agentName cfg.Files.MaxSize = 20 * 1024 * 1024 + cfg.Files.Permissions = 0600 // default user only + root, _ := utils.HasRoot() // error ignored + if !root { + // when not running as root, the default changes to include the group + cfg.Files.Permissions = 0660 + } return &cfg } @@ -150,10 +157,16 @@ func MakeInternalFileOutput(cfg *Config) (zapcore.Core, error) { filename := filepath.Join(paths.Home(), DefaultLogDirectory, cfg.Beat) al := zap.NewAtomicLevelAt(cfg.Level.ZapLevel()) internalLevelEnabler = &al // directly persisting struct will panic on accessing unitialized backing pointer + permissions := 0600 // default user only + root, _ := utils.HasRoot() // error ignored + if !root { + // when not running as root, the default changes to include the group + permissions = 0660 + } rotator, err := file.NewFileRotator(filename, file.MaxSizeBytes(defaultCfg.Files.MaxSize), file.MaxBackups(defaultCfg.Files.MaxBackups), - file.Permissions(os.FileMode(defaultCfg.Files.Permissions)), + file.Permissions(os.FileMode(permissions)), file.Interval(defaultCfg.Files.Interval), file.RotateOnStartup(defaultCfg.Files.RotateOnStartup), file.RedirectStderr(defaultCfg.Files.RedirectStderr), diff --git a/pkg/testing/fixture_install.go b/pkg/testing/fixture_install.go index 9ec684a811b..923356779e9 100644 --- a/pkg/testing/fixture_install.go +++ b/pkg/testing/fixture_install.go @@ -57,6 +57,7 @@ type InstallOpts struct { Insecure bool // --insecure NonInteractive bool // --non-interactive ProxyURL string // --proxy-url + Unprivileged bool // --unprivileged EnrollOpts } @@ -78,6 +79,9 @@ func (i InstallOpts) toCmdArgs() []string { if i.ProxyURL != "" { args = append(args, "--proxy-url="+i.ProxyURL) } + if i.Unprivileged { + args = append(args, "--unprivileged") + } args = append(args, i.EnrollOpts.toCmdArgs()...) @@ -112,7 +116,11 @@ func (f *Fixture) Install(ctx context.Context, installOpts *InstallOpts, opts .. } // we just installed agent, the control socket is at a well-known location - c := client.New(client.WithAddress(paths.ControlSocketPath)) + socketPath := paths.ControlSocketPath + if installOpts.Unprivileged { + socketPath = paths.ControlSocketUnprivilegedPath + } + c := client.New(client.WithAddress(socketPath)) f.setClient(c) f.t.Cleanup(func() { diff --git a/pkg/utils/perm_unix.go b/pkg/utils/perm_unix.go index 5c15c9a5e69..4adcfc178aa 100644 --- a/pkg/utils/perm_unix.go +++ b/pkg/utils/perm_unix.go @@ -11,6 +11,20 @@ import ( "os" ) +// FileOwner is the ownership a file should have. +type FileOwner struct { + UID int + GID int +} + +// CurrentFileOwner returns the executing UID and GID of the current process. +func CurrentFileOwner() FileOwner { + return FileOwner{ + UID: os.Getuid(), + GID: os.Getgid(), + } +} + // HasStrictExecPerms ensures that the path is executable by the owner, cannot be written by anyone other than the // owner of the file and that the owner of the file is the same as the UID or root. func HasStrictExecPerms(path string, uid int) error { diff --git a/pkg/utils/perm_windows.go b/pkg/utils/perm_windows.go index 5fd41d1928a..6b6ac0fd1b8 100644 --- a/pkg/utils/perm_windows.go +++ b/pkg/utils/perm_windows.go @@ -6,6 +6,28 @@ package utils +const ( + // AdministratorSID is the SID for the Administrator user. + AdministratorSID = "S-1-5-32-544" + // SystemSID is the SID for the SYSTEM user. + SystemSID = "S-1-5-32-544" +) + +// FileOwner is the ownership a file should have. +type FileOwner struct { + UID string + GID string +} + +// CurrentFileOwner returns the executing UID and GID of the current process. +func CurrentFileOwner() FileOwner { + // TODO(blakerouse): Make this return the current user and group on Windows. + return FileOwner{ + UID: AdministratorSID, + GID: SystemSID, + } +} + // HasStrictExecPerms ensures that the path is executable by the owner and that the owner of the file // is the same as the UID or root. func HasStrictExecPerms(path string, uid int) error { diff --git a/pkg/utils/root_unix.go b/pkg/utils/root_unix.go index cb264cc4bb0..765483bf45c 100644 --- a/pkg/utils/root_unix.go +++ b/pkg/utils/root_unix.go @@ -16,5 +16,5 @@ const ( // HasRoot returns true if the user has root permissions. // Added extra `nil` value to return since the HasRoot for windows will return an error as well func HasRoot() (bool, error) { - return os.Getegid() == 0, nil + return os.Geteuid() == 0, nil } diff --git a/testing/integration/install_unprivileged_test.go b/testing/integration/install_unprivileged_test.go new file mode 100644 index 00000000000..6c260a42de0 --- /dev/null +++ b/testing/integration/install_unprivileged_test.go @@ -0,0 +1,198 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build integration && !windows + +package integration + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "syscall" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" + "github.com/elastic/elastic-agent/internal/pkg/agent/install" + atesting "github.com/elastic/elastic-agent/pkg/testing" + "github.com/elastic/elastic-agent/pkg/testing/define" +) + +func TestInstallUnprivilegedWithoutBasePath(t *testing.T) { + define.Require(t, define.Requirements{ + // We require sudo for this test to run + // `elastic-agent install` (even though it will + // be installed as non-root). + Sudo: true, + + // It's not safe to run this test locally as it + // installs Elastic Agent. + Local: false, + + // Only supports Linux at the moment. + OS: []define.OS{ + { + Type: define.Linux, + }, + }, + }) + + // Get path to Elastic Agent executable + fixture, err := define.NewFixture(t, define.Version()) + require.NoError(t, err) + + // Prepare the Elastic Agent so the binary is extracted and ready to use. + err = fixture.Prepare(context.Background()) + require.NoError(t, err) + + // Check that default base path is clean + var defaultBasePath string + switch runtime.GOOS { + case "darwin": + defaultBasePath = `/Library` + case "linux": + defaultBasePath = `/opt` + case "windows": + defaultBasePath = `C:\Program Files` + } + + topPath := filepath.Join(defaultBasePath, "Elastic", "Agent") + err = os.RemoveAll(topPath) + require.NoError(t, err, "failed to remove %q. The test requires this path not to exist.") + + // Run `elastic-agent install`. We use `--force` to prevent interactive + // execution. + out, err := fixture.Install(context.Background(), &atesting.InstallOpts{Force: true, Unprivileged: true}) + if err != nil { + t.Logf("install output: %s", out) + require.NoError(t, err) + } + + checkInstallUnprivilegedSuccess(t, topPath) +} + +func TestInstallUnprivilegedWithBasePath(t *testing.T) { + define.Require(t, define.Requirements{ + // We require sudo for this test to run + // `elastic-agent install` (even though it will + // be installed as non-root). + Sudo: true, + + // It's not safe to run this test locally as it + // installs Elastic Agent. + Local: false, + + // Only supports Linux at the moment. + OS: []define.OS{ + { + Type: define.Linux, + }, + }, + }) + + // Get path to Elastic Agent executable + fixture, err := define.NewFixture(t, define.Version()) + require.NoError(t, err) + + // Prepare the Elastic Agent so the binary is extracted and ready to use. + err = fixture.Prepare(context.Background()) + require.NoError(t, err) + + // Other test `TestInstallWithBasePath` uses a random directory for the base + // path and that works because its running root. When using a base path the + // base needs to be accessible by the `elastic-agent` user that will be + // executing the process, but is not created yet. Using a base that exists + // and is known to be accessible by standard users, ensures this tests + // works correctly and will not hit a permission issue when spawning the + // elastic-agent service. + var basePath string + switch runtime.GOOS { + case define.Linux: + // default is `/opt` + basePath = `/usr` + default: + t.Fatalf("only Linux is supported by this test; should have been skipped") + } + + // Run `elastic-agent install`. We use `--force` to prevent interactive + // execution. + out, err := fixture.Install(context.Background(), &atesting.InstallOpts{ + BasePath: basePath, + Force: true, + Unprivileged: true, + }) + if err != nil { + t.Logf("install output: %s", out) + require.NoError(t, err) + } + + // Check that Agent was installed in the custom base path + topPath := filepath.Join(basePath, "Elastic", "Agent") + checkInstallUnprivilegedSuccess(t, topPath) +} + +func checkInstallUnprivilegedSuccess(t *testing.T, topPath string) { + t.Helper() + + // Check that the elastic-agent user/group exist. + uid, err := install.FindUID("elastic-agent") + require.NoError(t, err) + gid, err := install.FindGID("elastic-agent") + require.NoError(t, err) + + // Path should now exist as well as be owned by the correct user/group. + info, err := os.Stat(topPath) + require.NoError(t, err) + fs, ok := info.Sys().(*syscall.Stat_t) + require.True(t, ok) + require.Equalf(t, fs.Uid, uint32(uid), "%s not owned by elastic-agent user", topPath) + require.Equalf(t, fs.Gid, uint32(gid), "%s not owned by elastic-agent group", topPath) + + // Check that a few expected installed files are present + installedBinPath := filepath.Join(topPath, exeOnWindows("elastic-agent")) + installedDataPath := filepath.Join(topPath, "data") + installMarkerPath := filepath.Join(topPath, ".installed") + _, err = os.Stat(installedBinPath) + require.NoError(t, err) + _, err = os.Stat(installedDataPath) + require.NoError(t, err) + _, err = os.Stat(installMarkerPath) + require.NoError(t, err) + + // Check that the socket is created with the correct permissions. + socketPath := strings.TrimPrefix(paths.ControlSocketUnprivilegedPath, "unix://") + require.Eventuallyf(t, func() bool { + _, err = os.Stat(socketPath) + return err == nil + }, 3*time.Minute, 1*time.Second, "%s socket never created: %s", socketPath, err) + info, err = os.Stat(socketPath) + require.NoError(t, err) + fs, ok = info.Sys().(*syscall.Stat_t) + require.True(t, ok) + require.Equalf(t, fs.Uid, uint32(uid), "%s not owned by elastic-agent user", socketPath) + require.Equalf(t, fs.Gid, uint32(gid), "%s not owned by elastic-agent group", socketPath) + + // Executing `elastic-agent status` as the `elastic-agent` user should work. + var output []byte + require.Eventuallyf(t, func() bool { + cmd := exec.Command("sudo", "-u", "elastic-agent", "elastic-agent", "status") + output, err = cmd.CombinedOutput() + return err == nil + }, 3*time.Minute, 1*time.Second, "status never successful: %s (output: %s)", err, output) + + // Executing `elastic-agent status` as the original user should fail, because that + // user is not in the 'elastic-agent' group. + originalUser := os.Getenv("USER") + if originalUser != "" { + cmd := exec.Command("sudo", "-u", originalUser, "elastic-agent", "status") + output, err := cmd.CombinedOutput() + require.Error(t, err, "running elastic-agent status should have failed: %s", output) + } +}