Skip to content

Commit 00a3402

Browse files
committed
[ACTP] add ensure-enrollment command
1 parent 9960c56 commit 00a3402

11 files changed

Lines changed: 584 additions & 27 deletions

File tree

cmd/privateactionrunner/subcommands/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ go_library(
77
visibility = ["//visibility:public"],
88
deps = [
99
"//cmd/privateactionrunner/command",
10+
"//cmd/privateactionrunner/subcommands/ensureenrollment",
1011
"//cmd/privateactionrunner/subcommands/rotateidentity",
1112
"//cmd/privateactionrunner/subcommands/run",
1213
"//cmd/privateactionrunner/subcommands/runexecutor",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
load("@rules_go//go:def.bzl", "go_library")
2+
load("//bazel/rules/go:dd_agent_go_test.bzl", "dd_agent_go_test")
3+
4+
go_library(
5+
name = "ensureenrollment",
6+
srcs = ["command.go"],
7+
importpath = "github.com/DataDog/datadog-agent/cmd/privateactionrunner/subcommands/ensureenrollment",
8+
visibility = ["//visibility:public"],
9+
deps = [
10+
"//cmd/privateactionrunner/command",
11+
"//cmd/privateactionrunner/subcommands/identity",
12+
"//comp/core",
13+
"//comp/core/config",
14+
"//comp/core/hostname",
15+
"//comp/core/hostname/hostnameimpl",
16+
"//comp/core/log/def",
17+
"//pkg/config/setup",
18+
"//pkg/privateactionrunner/enrollment",
19+
"//pkg/privateactionrunner/util",
20+
"//pkg/util/fxutil",
21+
"@com_github_spf13_cobra//:cobra",
22+
"@org_uber_go_fx//:fx",
23+
],
24+
)
25+
26+
dd_agent_go_test(
27+
name = "ensureenrollment_test",
28+
srcs = ["command_test.go"],
29+
embed = [":ensureenrollment"],
30+
deps = [
31+
"//cmd/privateactionrunner/command",
32+
"//comp/core/config",
33+
"//comp/core/hostname/hostnameinterface/mock",
34+
"//comp/core/log/def",
35+
"//comp/core/log/mock",
36+
"//pkg/privateactionrunner/enrollment",
37+
"//pkg/privateactionrunner/util",
38+
"//pkg/util/fxutil",
39+
"@com_github_stretchr_testify//assert",
40+
"@com_github_stretchr_testify//require",
41+
],
42+
)
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2025-present Datadog, Inc.
5+
6+
// Package ensureenrollment implements the 'ensure-enrollment' subcommand for the private-action-runner.
7+
package ensureenrollment
8+
9+
import (
10+
"context"
11+
"crypto/ecdsa"
12+
"errors"
13+
"fmt"
14+
15+
"github.com/spf13/cobra"
16+
"go.uber.org/fx"
17+
18+
"github.com/DataDog/datadog-agent/cmd/privateactionrunner/command"
19+
identitycmd "github.com/DataDog/datadog-agent/cmd/privateactionrunner/subcommands/identity"
20+
"github.com/DataDog/datadog-agent/comp/core"
21+
"github.com/DataDog/datadog-agent/comp/core/config"
22+
"github.com/DataDog/datadog-agent/comp/core/hostname"
23+
"github.com/DataDog/datadog-agent/comp/core/hostname/hostnameimpl"
24+
log "github.com/DataDog/datadog-agent/comp/core/log/def"
25+
pkgconfigsetup "github.com/DataDog/datadog-agent/pkg/config/setup"
26+
"github.com/DataDog/datadog-agent/pkg/privateactionrunner/enrollment"
27+
parutil "github.com/DataDog/datadog-agent/pkg/privateactionrunner/util"
28+
"github.com/DataDog/datadog-agent/pkg/util/fxutil"
29+
)
30+
31+
type enrollAndPersistFunc func(context.Context, log.Component, config.Component, *enrollment.AgentIdentifier) (*enrollment.Result, error)
32+
33+
// Commands returns the ensure-enrollment subcommand.
34+
func Commands(globalParams *command.GlobalParams) []*cobra.Command {
35+
cmd := &cobra.Command{
36+
Use: "ensure-enrollment",
37+
Short: "Ensure that the Private Action Runner has a valid identity",
38+
Long: `Reuses a persisted identity when it belongs to the current Agent host.
39+
If no usable persisted or configured identity exists, self-enrollment is performed
40+
when private_action_runner.self_enroll is enabled.`,
41+
RunE: func(_ *cobra.Command, _ []string) error {
42+
return fxutil.OneShot(run,
43+
fx.Supply(core.BundleParams{
44+
ConfigParams: config.NewAgentParams(globalParams.ConfFilePath, config.WithExtraConfFiles(globalParams.ExtraConfFilePath)),
45+
LogParams: log.ForOneShot(command.LoggerName, "info", true),
46+
}),
47+
core.Bundle(core.WithSecrets()),
48+
hostnameimpl.Module(),
49+
)
50+
},
51+
}
52+
return []*cobra.Command{cmd}
53+
}
54+
55+
func run(logger log.Component, cfg config.Component, hostnameComp hostname.Component) error {
56+
return ensureEnrollment(context.Background(), logger, cfg, hostnameComp, identitycmd.EnrollAndPersist)
57+
}
58+
59+
func ensureEnrollment(ctx context.Context, logger log.Component, cfg config.Component, hostnameComp hostname.Component, enrollAndPersist enrollAndPersistFunc) error {
60+
if !cfg.GetBool(pkgconfigsetup.PAREnabled) {
61+
return errors.New("private_action_runner.enabled is false - set it to true before ensuring enrollment")
62+
}
63+
64+
agentIdentifier, err := enrollment.GetAgentIdentifier(ctx, hostnameComp)
65+
if err != nil {
66+
return fmt.Errorf("failed to get agent identifier: %w", err)
67+
}
68+
69+
persisted, err := enrollment.GetIdentityFromPreviousEnrollment(ctx, cfg)
70+
if err != nil {
71+
return fmt.Errorf("failed to load persisted identity: %w", err)
72+
}
73+
if persisted != nil {
74+
if err := validateIdentity(persisted.URN, persisted.PrivateKey); err != nil {
75+
return fmt.Errorf("persisted identity is invalid: %w", err)
76+
}
77+
if !enrollment.ShouldReenroll(agentIdentifier, persisted) {
78+
logger.Info("Persisted identity is valid; enrollment is not required")
79+
return nil
80+
}
81+
}
82+
83+
configuredURN := cfg.GetString(pkgconfigsetup.PARUrn)
84+
configuredPrivateKey := cfg.GetString(pkgconfigsetup.PARPrivateKey)
85+
if err := validateConfiguredIdentity(configuredURN, configuredPrivateKey); err != nil {
86+
return err
87+
}
88+
if configuredURN != "" && configuredPrivateKey != "" {
89+
// Rust gives a persisted file precedence over inline configuration and does
90+
// not repeat the hostname comparison, so remove the stale file before
91+
// allowing it to fall back to this configured identity.
92+
if persisted != nil {
93+
if err := enrollment.RemoveIdentityFile(cfg); err != nil {
94+
return err
95+
}
96+
}
97+
logger.Info("Configured identity is complete; enrollment is not required")
98+
return nil
99+
}
100+
101+
if !cfg.GetBool(pkgconfigsetup.PARSelfEnroll) {
102+
return errors.New("no valid Private Action Runner identity is available and private_action_runner.self_enroll is false; configure a URN and private key or enable self-enrollment")
103+
}
104+
105+
result, err := enrollAndPersist(ctx, logger, cfg, agentIdentifier)
106+
if err != nil {
107+
return err
108+
}
109+
logger.Infof("Identity successfully enrolled. New URN: %s", result.URN)
110+
return nil
111+
}
112+
113+
func validateConfiguredIdentity(urn, privateKey string) error {
114+
if urn != "" {
115+
if _, err := parutil.ParseRunnerURN(urn); err != nil {
116+
return fmt.Errorf("configured private_action_runner.urn is invalid: %w", err)
117+
}
118+
}
119+
if privateKey != "" {
120+
if err := validatePrivateKey(privateKey); err != nil {
121+
return fmt.Errorf("configured private_action_runner.private_key is invalid: %w", err)
122+
}
123+
}
124+
return nil
125+
}
126+
127+
func validateIdentity(urn, privateKey string) error {
128+
if _, err := parutil.ParseRunnerURN(urn); err != nil {
129+
return fmt.Errorf("invalid URN: %w", err)
130+
}
131+
if err := validatePrivateKey(privateKey); err != nil {
132+
return fmt.Errorf("invalid private key: %w", err)
133+
}
134+
return nil
135+
}
136+
137+
func validatePrivateKey(encoded string) error {
138+
jwk, err := parutil.Base64ToJWK(encoded)
139+
if err != nil {
140+
return err
141+
}
142+
if _, ok := jwk.Key.(*ecdsa.PrivateKey); !ok {
143+
return errors.New("JWK does not contain an ECDSA private key")
144+
}
145+
return nil
146+
}

0 commit comments

Comments
 (0)