diff --git a/cmd/process-agent/api/config_set.go b/cmd/process-agent/api/config_set.go new file mode 100644 index 0000000000000..22f6a0fcf3d5e --- /dev/null +++ b/cmd/process-agent/api/config_set.go @@ -0,0 +1,21 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +package api + +import ( + "net/http" + + "github.com/DataDog/datadog-agent/pkg/api/util" +) + +func configSetHandler(deps APIServerDeps, w http.ResponseWriter, r *http.Request) { + if err := util.Validate(w, r); err != nil { + deps.Log.Warnf("invalid auth token for %s request to %s: %s", r.Method, r.RequestURI, err) + return + } + + deps.Settings.SetValue(w, r) +} diff --git a/cmd/process-agent/api/server.go b/cmd/process-agent/api/server.go index bdf4b1d4d105f..bfdf3155a2a8c 100644 --- a/cmd/process-agent/api/server.go +++ b/cmd/process-agent/api/server.go @@ -43,8 +43,7 @@ func SetupAPIServerHandlers(deps APIServerDeps, r *mux.Router) { r.HandleFunc("/config/all", deps.Settings.GetFullConfig("")).Methods("GET") // Get all fields from process-agent Config object r.HandleFunc("/config/list-runtime", deps.Settings.ListConfigurable).Methods("GET") r.HandleFunc("/config/{setting}", deps.Settings.GetValue).Methods("GET") - r.HandleFunc("/config/{setting}", deps.Settings.SetValue).Methods("POST") - + r.HandleFunc("/config/{setting}", injectDeps(deps, configSetHandler)).Methods("POST") r.HandleFunc("/agent/status", injectDeps(deps, statusHandler)).Methods("GET") r.HandleFunc("/agent/tagger-list", injectDeps(deps, getTaggerList)).Methods("GET") r.HandleFunc("/agent/workload-list/short", func(w http.ResponseWriter, _ *http.Request) { diff --git a/cmd/process-agent/subcommands/config/config.go b/cmd/process-agent/subcommands/config/config.go index 1acd0ec15e5e2..f7a9996ac10ab 100644 --- a/cmd/process-agent/subcommands/config/config.go +++ b/cmd/process-agent/subcommands/config/config.go @@ -181,6 +181,11 @@ func getConfigValue(deps dependencies, args []string) error { } func getClient(cfg model.Reader) (settings.Client, error) { + err := util.SetAuthToken(cfg) + if err != nil { + return nil, err + } + httpClient := apiutil.GetClient(false) ipcAddress, err := pkgconfigsetup.GetIPCAddress(pkgconfigsetup.Datadog()) diff --git a/cmd/process-agent/subcommands/status/status.go b/cmd/process-agent/subcommands/status/status.go index 8826c7547fb61..f98877e1979a2 100644 --- a/cmd/process-agent/subcommands/status/status.go +++ b/cmd/process-agent/subcommands/status/status.go @@ -21,6 +21,7 @@ import ( log "github.com/DataDog/datadog-agent/comp/core/log/def" compStatus "github.com/DataDog/datadog-agent/comp/core/status" "github.com/DataDog/datadog-agent/comp/process" + "github.com/DataDog/datadog-agent/pkg/api/util" apiutil "github.com/DataDog/datadog-agent/pkg/api/util" "github.com/DataDog/datadog-agent/pkg/collector/python" pkgconfigsetup "github.com/DataDog/datadog-agent/pkg/config/setup" @@ -147,6 +148,11 @@ func runStatus(deps dependencies) error { return err } + err = util.SetAuthToken(deps.Config) + if err != nil { + return err + } + getAndWriteStatus(deps.Log, statusURL, os.Stdout) return nil } diff --git a/cmd/process-agent/subcommands/taggerlist/tagger_list.go b/cmd/process-agent/subcommands/taggerlist/tagger_list.go index e7ec238efdeea..235a5a2b958df 100644 --- a/cmd/process-agent/subcommands/taggerlist/tagger_list.go +++ b/cmd/process-agent/subcommands/taggerlist/tagger_list.go @@ -18,6 +18,7 @@ import ( "github.com/DataDog/datadog-agent/comp/core/config" log "github.com/DataDog/datadog-agent/comp/core/log/def" "github.com/DataDog/datadog-agent/comp/core/tagger/api" + "github.com/DataDog/datadog-agent/pkg/api/util" pkgconfigsetup "github.com/DataDog/datadog-agent/pkg/config/setup" "github.com/DataDog/datadog-agent/pkg/util/fxutil" ) @@ -58,6 +59,10 @@ func taggerList(deps dependencies) error { return err } + err = util.SetAuthToken(deps.Config) + if err != nil { + return err + } return api.GetTaggerList(color.Output, taggerURL) } diff --git a/comp/process/apiserver/apiserver.go b/comp/process/apiserver/apiserver.go index 24bbe6353d06f..9c37154296f64 100644 --- a/comp/process/apiserver/apiserver.go +++ b/comp/process/apiserver/apiserver.go @@ -16,8 +16,10 @@ import ( "github.com/DataDog/datadog-agent/cmd/process-agent/api" "github.com/DataDog/datadog-agent/comp/api/authtoken" - log "github.com/DataDog/datadog-agent/comp/core/log/def" + logComp "github.com/DataDog/datadog-agent/comp/core/log/def" + "github.com/DataDog/datadog-agent/pkg/api/util" pkgconfigsetup "github.com/DataDog/datadog-agent/pkg/config/setup" + "github.com/DataDog/datadog-agent/pkg/util/log" ) var _ Component = (*apiserver)(nil) @@ -31,7 +33,7 @@ type dependencies struct { Lc fx.Lifecycle - Log log.Component + Log logComp.Component At authtoken.Component @@ -41,6 +43,7 @@ type dependencies struct { //nolint:revive // TODO(PROC) Fix revive linter func newApiServer(deps dependencies) Component { r := mux.NewRouter() + r.Use(validateToken) api.SetupAPIServerHandlers(deps.APIServerDeps, r) // Set up routes addr, err := pkgconfigsetup.GetProcessAPIAddressPort(pkgconfigsetup.Datadog()) @@ -84,3 +87,13 @@ func newApiServer(deps dependencies) Component { return apiserver } + +func validateToken(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if err := util.Validate(w, r); err != nil { + log.Warnf("invalid auth token for %s request to %s: %s", r.Method, r.RequestURI, err) + return + } + next.ServeHTTP(w, r) + }) +} diff --git a/comp/process/apiserver/apiserver_test.go b/comp/process/apiserver/apiserver_test.go index a0d94db4db4c2..34736ef24d695 100644 --- a/comp/process/apiserver/apiserver_test.go +++ b/comp/process/apiserver/apiserver_test.go @@ -11,10 +11,12 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/fx" "github.com/DataDog/datadog-agent/comp/api/authtoken/fetchonlyimpl" "github.com/DataDog/datadog-agent/comp/core" + "github.com/DataDog/datadog-agent/comp/core/config" "github.com/DataDog/datadog-agent/comp/core/settings/settingsimpl" "github.com/DataDog/datadog-agent/comp/core/status" "github.com/DataDog/datadog-agent/comp/core/status/statusimpl" @@ -22,6 +24,8 @@ import ( taggerfx "github.com/DataDog/datadog-agent/comp/core/tagger/fx" workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def" workloadmetafx "github.com/DataDog/datadog-agent/comp/core/workloadmeta/fx" + "github.com/DataDog/datadog-agent/pkg/api/util" + pkgconfigsetup "github.com/DataDog/datadog-agent/pkg/config/setup" "github.com/DataDog/datadog-agent/pkg/util/fxutil" ) @@ -29,6 +33,9 @@ func TestLifecycle(t *testing.T) { _ = fxutil.Test[Component](t, fx.Options( Module(), core.MockBundle(), + fx.Replace(config.MockParams{Overrides: map[string]interface{}{ + "process_config.cmd_port": 43424, + }}), workloadmetafx.Module(workloadmeta.NewParams()), fx.Supply( status.Params{ @@ -43,13 +50,54 @@ func TestLifecycle(t *testing.T) { fetchonlyimpl.MockModule(), )) - assert.Eventually(t, func() bool { - res, err := http.Get("http://localhost:6162/config") - if err != nil { - return false - } + assert.EventuallyWithT(t, func(c *assert.CollectT) { + req, err := http.NewRequest("GET", "http://localhost:43424/agent/status", nil) + require.NoError(c, err) + util.CreateAndSetAuthToken(pkgconfigsetup.Datadog()) + req.Header.Set("Authorization", "Bearer "+util.GetAuthToken()) + res, err := util.GetClient(false).Do(req) + require.NoError(c, err) defer res.Body.Close() + assert.Equal(c, http.StatusOK, res.StatusCode) + }, 5*time.Second, time.Second) +} + +func TestPostAuthentication(t *testing.T) { + _ = fxutil.Test[Component](t, fx.Options( + Module(), + core.MockBundle(), + fx.Replace(config.MockParams{Overrides: map[string]interface{}{ + "process_config.cmd_port": 43424, + }}), + workloadmetafx.Module(workloadmeta.NewParams()), + fx.Supply( + status.Params{ + PythonVersionGetFunc: func() string { return "n/a" }, + }, + ), + taggerfx.Module(tagger.Params{ + UseFakeTagger: true, + }), + statusimpl.Module(), + settingsimpl.MockModule(), + fetchonlyimpl.MockModule(), + )) - return res.StatusCode == http.StatusOK + assert.EventuallyWithT(t, func(c *assert.CollectT) { + // No authentication + req, err := http.NewRequest("POST", "http://localhost:43424/config/log_level?value=debug", nil) + require.NoError(c, err) + res, err := util.GetClient(false).Do(req) + require.NoError(c, err) + defer res.Body.Close() + assert.Equal(c, http.StatusUnauthorized, res.StatusCode) + + // With authentication + util.CreateAndSetAuthToken(pkgconfigsetup.Datadog()) + req.Header.Set("Authorization", "Bearer "+util.GetAuthToken()) + res, err = util.GetClient(false).Do(req) + require.NoError(c, err) + defer res.Body.Close() + assert.Equal(c, http.StatusOK, res.StatusCode) }, 5*time.Second, time.Second) } diff --git a/pkg/flare/archive.go b/pkg/flare/archive.go index b61c6d9dd6ed2..8c2ad86688e97 100644 --- a/pkg/flare/archive.go +++ b/pkg/flare/archive.go @@ -394,6 +394,11 @@ func getProcessAgentTaggerList() ([]byte, error) { return nil, fmt.Errorf("wrong configuration to connect to process-agent") } + err = apiutil.SetAuthToken(pkgconfigsetup.Datadog()) + if err != nil { + return nil, err + } + taggerListURL := fmt.Sprintf("http://%s/agent/tagger-list", addressPort) return getTaggerList(taggerListURL) } diff --git a/releasenotes/notes/process-agent-config-auth-09a3123ac6496052.yaml b/releasenotes/notes/process-agent-config-auth-09a3123ac6496052.yaml new file mode 100644 index 0000000000000..f33381d26dfd1 --- /dev/null +++ b/releasenotes/notes/process-agent-config-auth-09a3123ac6496052.yaml @@ -0,0 +1,11 @@ +# Each section from every release note are combined when the +# CHANGELOG.rst is rendered. So the text needs to be worded so that +# it does not depend on any information only available in another +# section. This may mean repeating some details, but each section +# must be readable independently of the other. +# +# Each section note must be formatted as reStructuredText. +--- +enhancements: + - | + The process agent endpoint for setting config values now uses authentication. diff --git a/test/new-e2e/tests/process/linux_test.go b/test/new-e2e/tests/process/linux_test.go index 7b5e093d5bf83..6955ff308a3d1 100644 --- a/test/new-e2e/tests/process/linux_test.go +++ b/test/new-e2e/tests/process/linux_test.go @@ -117,7 +117,7 @@ func (s *linuxTestSuite) TestProcessChecksInCoreAgent() { // Verify that the process agent is not running assert.EventuallyWithT(t, func(c *assert.CollectT) { - status := s.Env().RemoteHost.MustExecute("/opt/datadog-agent/embedded/bin/process-agent status") + status := s.Env().RemoteHost.MustExecute("sudo /opt/datadog-agent/embedded/bin/process-agent status") assert.Contains(c, status, "The Process Agent is not running") }, 1*time.Minute, 5*time.Second)