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)
+ }
+}